首页 > 解决方案 > Kotlin Flow:带有回调对象延迟初始化器的 callbackFlow

问题描述

Kotlin Flow我想在我的 Android 项目中使用反应范式。我有一个基于外部回调的 API,所以我的选择是callbackFlow在我的Repository课堂上使用。

我已经在没有帮助的情况下深入地阅读了一些适当的文档:

我想要达到的目标:

目前我的Repository班级看起来像这样(简化代码):

lateinit var callback: ApiCallback

fun someFlow() = callbackFlow<SomeModel> {
    callback = object : ApiCallback {
        override fun someApiMethod() {
            offer(SomeModel())
        }
    }

    awaitClose { Log.d("Suspending flow until methods aren't invoked") }
}

suspend fun someUnfortunateCallbackDependentCall() {
    externalApiClient.externalMethod(callback)
}

someUnfortunateCallbackDependentCall被调用比收集更快时会出现问题someFlow()。现在为了避免UninitializedPropertyAccessException我在调用之前在协程中添加了一些延迟,someUnfortunateCallbackDependentCall但这对我来说是一种 hack/code 的味道。

我的第一个想法是使用by lazy而不是lateinit var因为这是我想要的 - 回调对象的延迟初始化。但是,我无法完全编写代码。我想从中发出/提供/发送一些数据someApiMethod以形成数据流,但超出范围则callbackFlow需要ProducerScope在其中。另一方面,someUnfortunateCallbackDependentCall它根本不是基于 Kotlin Flow 的(最多可以使用CoroutinesAPI 暂停)。

有可能吗?也许使用其他一些 Kotlin 代表?任何帮助,将不胜感激。

标签: androidkotlinkotlin-flowkotlin-coroutines

解决方案


要从技术上回答您的问题,您当然可以懒惰地或使用 lateinit 初始化回调,但您不能这样做并同时共享协程范围(一个用于 Flow,一个用于挂起函数) - 您需要自己建立某种同步。

下面我对你想要达到的目标做了一些假设,也许它们对你来说并不完美,但希望能给你一些启发如何改进。

由于它是您正在创建的存储库,我将首先假设您正在寻找存储SomeModel并允许应用程序的其余部分观察对其的更改。如果是这样,最简单的方法是使用MutableStateFlow属性而不是callbackFlow


interface Repository {
    val state: Flow<SomeModel>
    suspend fun reload()
}

class RepositoryImpl(private val service: ApiService) : Repository {

    override val state = MutableStateFlow(SomeModel())

    override suspend fun reload() {
        return suspendCoroutine { continuation ->
            service.callBackend(object : ApiCallback {
                override fun someApiMethod(data: SomeModel) {
                    state.value = data
                    if (continuation.context.isActive)
                        continuation.resume(Unit)
                }
            })
        }
    }
}

interface ApiCallback {
    fun someApiMethod(data: SomeModel)
}

data class SomeModel(val data: String = "")

interface ApiService {
    fun callBackend(callback: ApiCallback)
}

此解决方案的缺点是您必须调用reload()才能真正调用后端,仅收集 Flow 是不够的。

myrepository.state.collect {} 
myrepository.reload()

另一个解决方案,同样取决于您想要实现的目标,是提供两种调用后端的方法:


interface Repository {
    fun someFlow(): Flow<SomeModel>
    suspend fun reload(): SomeModel
}

class RepositoryImpl(private val service: ApiService) : Repository {

    override fun someFlow() = callbackFlow<SomeModel> {
        service.callBackend(object : ApiCallback {
            override fun someApiMethod(data: SomeModel) {
                offer(data)
            }
        })
        awaitClose {
            Log.d("TAG", "Callback Flow is closed")
        }
    }

    override suspend fun reload(): SomeModel {
        return suspendCoroutine<SomeModel> { continuation ->
            service.callBackend(object : ApiCallback {
                override fun someApiMethod(data: SomeModel) {
                    if (continuation.context.isActive)
                        continuation.resume(data)
                }
            })
        }
    }
}

interface ApiCallback {
    fun someApiMethod(data: SomeModel)
}

data class SomeModel(val data: String = "")

interface ApiService {
    fun callBackend(callback: ApiCallback)
}

现在您可以调用reload() someFlow()检索SomeModel()并且Repository保持没有“状态”。

请注意,该reload()函数只是该callbackFlow想法的“协程”版本。


推荐阅读