问题描述
我正在使用 Microsoft 的 msal 库让用户登录他们的帐户。我正在使用 MVVM,因此我需要该函数返回一个 Resource<String>
,但该函数在调用 msal 回调并更新我需要返回的值之前返回一个值。我试过了:
- 使用
Coroutinescope(IO).launch {}
并在其中运行登录功能,但不会异步返回。 - 在回调中使用
Coroutinescope(IO).async {}
然后 return@async Resource。我编译器抱怨,因为回调是 void 类型。 - 使用 Kotlin Flow
并从回调内部发出值。
任何人都可以帮助我解决如何让它异步运行或从回调中返回值的任何想法吗?谢谢!
suspend fun microsoftSignIn(activity: Activity): Resource<String> {
var resource: Resource<String> = Resource.Error(Throwable(""))
Coroutinescope(dispatchers.IO).async {
PublicclientApplication.createSingleAccountPublicclientApplication(context,R.raw.auth_config_single_account,object : IPublicclientApplication.ISingleAccountApplicationCreatedListener {
override fun onCreated(application: ISingleAccountPublicclientApplication?) {
application?.signIn((activity as AllActivityBase),null,Array(1) {"api://ScopeBlahBlah"},object : AuthenticationCallback {
override fun onSuccess(authenticationResult: IAuthenticationResult?) {
Log.i("TOKEN_MSAL ${authenticationResult?.accesstoken}" )
authenticationResult?.accesstoken?.let { storetoken(it) }
///// I need this to run first before "return resource" runs!!! /////
resource = Resource.Success("Success")
}
override fun onCancel() {
resource = Resource.Error(Throwable(""))
}
override fun onError(exception: MsalException?) {=
resource = Resource.Error(Throwable(""))
}
})
}
override fun onError(exception: MsalException?) {
Log.i("TOKEN_MSAL_A EX ${exception?.message}")
resource = Resource.Error(Throwable(""))
}
})
}
return resource
}
解决方法
如果您需要使用传统的异步 API 并使其可挂起,最简单的方法是使用 suspendCoroutine() 或 CompletableDeferred。我无法轻松地重用您的代码,因此我只会为您提供一个通用的解决方案,但您应该可以轻松地根据需要对其进行调整。
suspendCoroutine()
:
suspend fun test1(): String {
return withContext(Dispatchers.IO) {
suspendCoroutine { cont ->
doSomethingAsync(object : Callback {
override fun onSuccess() {
cont.resume("success")
}
override fun onError() {
cont.resume("error")
}
})
}
}
}
CompletableDeferred
:
suspend fun test2(): String {
val result = CompletableDeferred<String>()
withContext(Dispatchers.IO) {
doSomethingAsync(object : Callback {
override fun onSuccess() {
result.complete("success")
}
override fun onError() {
result.complete("error")
}
})
}
return result.await()
}
如您所见,两种解决方案都非常相似。我不确定它们之间是否有很大的不同。我想不会,所以用你更喜欢的那个。
此外,在这两种情况下,您都可以抛出异常而不是返回错误。您可以将 resumeWithException()
用于第一个示例,将 completeExceptionally()
用于第二个示例。
最后一点:您在示例中使用了 Dispatchers.IO
,所以我也这样做了,但我怀疑这里是否需要它。根据定义,异步操作不会阻塞线程,因此可以从任何调度程序/线程运行它们。
要将基于回调的函数转换为可以在协程中正确使用的函数,您需要使用 suspendCoroutine
或 suspendCancellableCoroutine
。假设你上面的代码是正确的(我不熟悉 msal),它应该看起来像这样。您有两个回调要转换。这些是顶级实用程序函数,您可以将它们放在项目中的任何位置:
suspend fun createSingleAccountPublicClientApplication(
context,@RawRes authConfig: Int
): ISingleAccountPublicClientApplication = suspendCoroutine { continuation ->
PublicClientApplication.createSingleAccountPublicClientApplication(context,authConfig,object : IPublicClientApplication.ISingleAccountApplicationCreatedListener {
override fun onCreated(application: ISingleAccountPublicClientApplication) {
continuation.resume(application)
}
override fun onError(exception: MsalException) {
continuation.resumeWithException(exception)
}
})
}
suspend fun ISingleAccountPublicClientApplication.signInOrThrow(
activity: AllActivityBase,// or whatever type this is in the API
someOptionalProperty: Any?,// whatever this type is in the API
vararg args: String
): IAuthenticationResult = suspendCancellableCoroutine { continuation ->
signIn(activity,someOptionalProperty,args,object: : AuthenticationCallback {
override fun onSuccess(authenticationResult: IAuthenticationResult) {
continuation.resume(authenticationResult)
}
override fun onCancel() = continuation.cancel()
override fun onError(exception: MsalException) {
continuation.resumeWithException(exception)
}
})
}
然后你就可以在任何协程中使用这个函数了,而且由于它是一个合适的挂起函数,你不必担心尝试异步运行它或指定 Dispatchers。
根据我认为您在代码中所做的事情,用法可能如下所示:
suspend fun microsoftSignIn(activity: Activity): Resource<String>
try {
val application = createSingleAccountPublicClientApplication(context,R.raw.auth_config_single_account)
val authenticationResult = application.signInOrThrow((activity as AllActivityBase),null,"api://ScopeBlahBlah")
Log.i(TAG,"TOKEN_MSAL ${authenticationResult.accessToken}" )
authenticationResult.accessToken?.let { storeToken(it) }
return Resource.Success("Success")
} catch (e: MsalException) {
return Resource.Error(e)
}
}