以下代码段在顺序代码流中将结果返回为“ 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())的结果吗?
答案 0 :(得分:4)
由于Firebase Realtime Database SDK不提供任何暂停功能,因此协程在处理其API时没有帮助。您需要将回调转换为暂停函数,以便能够在协程中等待结果。
这是一个暂停扩展功能,可以执行此操作(我通过Google搜索discovered a solution进行了操作):
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)
}
有了这个,您现在如何在协程中等待getValue()
在DatabaseReference上的可疑方法。
答案 1 :(得分:3)
singleValueEvent的 @Doug 示例,如果要保留列表,可以使用 coroutine flow ,如下所示:
@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) }
}
答案 2 :(得分:2)
如果有人仍然使用原始答案的代码,但需要更新它以匹配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()
}