如何使用 kotlin 协程处理回调

Fai*_*sal 8 android callback kotlin firebase-realtime-database kotlin-coroutines

以下代码段在顺序代码流中将结果返回为“null”。我知道协程可能是异步处理回调的可行解决方案。


    fun getUserProperty(path: String): String? {
        var result: String? = null
        database.child(KEY_USERS).child(getUid()).child(path)
            .addListenerForSingleValueEvent(object : ValueEventListener {
                override fun onCancelled(error: DatabaseError) {
                    Log.e(TAG, "error: $error")
                }

                override fun onDataChange(snapshot: DataSnapshot) {
                    Log.w(TAG, "value: ${snapshot.value}")
                    result = snapshot.value.toString()
                }
            })
        return result
    }
Run Code Online (Sandbox Code Playgroud)

在这种情况下,协程是否可以帮助等待回调(onDataChange()/onCancelled())的结果?

Dou*_*son 9

由于 Firebase 实时数据库 SDK 不提供任何挂起功能,因此协程在处理其 API 时没有帮助。您需要将回调转换为挂起函数,以便您能够在协程中等待结果。

这是一个执行此操作的挂起扩展功能(我通过谷歌搜索发现了一个解决方案):

suspend fun DatabaseReference.getValue(): DataSnapshot {
    return async(CommonPool) {
        suspendCoroutine<DataSnapshot> { continuation ->
            addListenerForSingleValueEvent(FValueEventListener(
                    onDataChange = { continuation.resume(it) },
                    onError = { continuation.resumeWithException(it.toException()) }
            ))
        }
    }.await()
}

class FValueEventListener(val onDataChange: (DataSnapshot) -> Unit, val onError: (DatabaseError) -> Unit) : ValueEventListener {
    override fun onDataChange(data: DataSnapshot) = onDataChange.invoke(data)
    override fun onCancelled(error: DatabaseError) = onError.invoke(error)
}
Run Code Online (Sandbox Code Playgroud)

有了这个,您现在getValue()可以在协程中等待 DatabaseReference 上的可疑方法。


Oma*_*dan 5

singleValueEvent 的@Doug示例如果您想继续列出,您可以使用协程流程,如下所示:

@ExperimentalCoroutinesApi
inline fun <reified T> DatabaseReference.listen(): Flow<DataResult<T?>> =
  callbackFlow {
    val valueListener = object : ValueEventListener {
      override fun onCancelled(databaseError: DatabaseError) {
        close(databaseError.toException())
      }

      override fun onDataChange(dataSnapshot: DataSnapshot) {
        try {
          val value = dataSnapshot.getValue(T::class.java)
          offer(DataResult.Success(value))
        } catch (exp: Exception) {
          Timber.e(exp)
          if (!isClosedForSend) offer(DataResult.Error(exp))
        }
      }
    }
    addValueEventListener(valueListener)

    awaitClose { removeEventListener(valueListener) }
  }
Run Code Online (Sandbox Code Playgroud)