簡體   English   中英

如何使用 kotlin 協程處理回調

[英]how to handle callback using 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
    }

在這種情況下,協程是否可以幫助等待回調的結果(onDataChange()/onCancelled())?

由於 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)
}

有了這個,您現在可以在協程中等待 DatabaseReference 上的getValue()可疑方法。

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) }
  }

如果有人仍然使用原始答案的代碼但需要更新它以匹配Coroutines的非實驗版本,我是這樣更改它的:

import com.google.firebase.database.DataSnapshot
import com.google.firebase.database.DatabaseError
import com.google.firebase.database.DatabaseReference
import com.google.firebase.database.ValueEventListener
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
import kotlin.coroutines.suspendCoroutine

suspend fun DatabaseReference.getSnapshotValue(): DataSnapshot {
    return withContext(Dispatchers.IO) {
        suspendCoroutine<DataSnapshot> { continuation ->
            addListenerForSingleValueEvent(FValueEventListener(
                onDataChange = { continuation.resume(it) },
                onError = { continuation.resumeWithException(it.toException()) }
            ))
        }
    }
}

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)
}

然后使用它就像: val snapshot = ref.getSnapshotValue()

更新

我還需要觀察一個節點並使用 Omar 的答案來做到這一點。 如果有人需要一個如何在此處使用它的示例,它是:

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

        override fun onDataChange(dataSnapshot: DataSnapshot) {
            try {
                val value = dataSnapshot.getValue(T::class.java)
                offer(value)
            } catch (exp: Exception) {
                if (!isClosedForSend) offer(null)
            }
        }
    }
    addValueEventListener(valueListener)
    awaitClose { removeEventListener(valueListener) }
}

然后在 Activity 或 Fragment 中調用它,您可以像這樣創建您的偵聽器:

var listener =  FirebaseUtils.databaseReference
   .child(AppConstants.FIREBASE_PATH_EMPLOYEES)
   .child(AuthUtils.retrieveUID()!!).listen<User>()

然后在你的函數中調用它:

CoroutineScope(IO).launch {
    withContext(IO) {
        listener?.collect{
            print(it)
        }
    }
}

然后在onStop()內部處理:

override fun onStop(){
    listener = null
    super.onStop()
}

@狗的答案是正確的,但很難理解。 這是有關如何將回調轉換為協程的說明https://codelabs.developers.google.com/codelabs/kotlin-coroutines/#6

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM