在Android中,网络操作通常在ViewModel
中完成。这确保了即使在重新创建Activity
或Fragment
时(例如,当设备旋转时(,网络呼叫也会继续进行而不会被取消。
现在将网络请求的结果从ViewModel
提交到视图(Activity
/Fragment
(。你有一个反应性组件,比如LiveData
或Observable
来设置它的值
val resultLiveData = MutableLiveData<Result>()
fun doNetworkRequest() {
repository.requestSomeResult() // Assume this returns Arrow's IO
.unsafeRunAsync { eitherResult ->
eitherResult.fold({ error ->
// Handle Error
}, { result ->
resultLiveData.value = result
})
}
}
我想知道是否有一种方法可以使val resultLiveData = MutableLiveData<Result>()
不与特定的实现(如LiveData
(绑定,而类似于返回higher kind
、Kind<F, Result>
。
有没有办法我可以做:
val result = Kind<F, Result>()
fun doNetworkRequest() {
repository.requestSomeResult() // Assume this returns Arrow's IO
.unsafeRunAsync { eitherResult ->
eitherResult.fold({ error ->
// Handle Error
}, { result ->
resultLiveData.sendValue(result) // Or however it should be done
})
}
}
所以我可以稍后用我想要的实现来定义Kind<F, Result>
?
谢谢你的提问!这是我最近一直在做的事情。关于这一点,需要提及的几件事:
在接下来的几天里,我们将发布Arrow的KotlinX集成模块。我将允许您在CoroutineScope中确定IO任务的范围。I.e:
yourIOTask().unsafeRunScoped(scope) { cb -> }
如果所提供的范围被取消,这将确保您的IO任务被取消。这意味着您可以使用视图模型范围来确定"视图模型"操作的范围,如本例中的doNetworkRequest
,并且您将确保这些操作在配置更改后仍然有效,并在视图模型发布时被取消。
话虽如此,如果我们观察Android ViewModels目前的工作方式,正如你所提到的,你仍然需要一个"中间层缓存"来将结果传递到,以确保这些结果始终被传递,并且一旦视图开始观察,我就会获得最新的数据。通过将此机制与上一段中提到的范围结合起来,您可以确保您的长期运行任务始终能够产生结果,无论它们是在配置更改之前、期间还是之后完成的。
从这个意义上说,如果你想继续使用Android ViewModel,你可以使用箭头编码,比如:
interface ViewStateCache<ViewState> {
val cacheScope: CoroutineScope
fun observeViewState(observer: LifecycleOwner, renderingScope: CoroutineScope, render: (ViewState) -> IO<Unit>): IO<Unit>
fun updateViewState(transform: (ViewState) -> ViewState): IO<ViewState>
}
我们可以使用这个合同来确保ViewModels以纯粹的方式使用。所有ViewModel都可以实现这个合约,比如:
class ViewModelViewStateCache<ViewState>(initialState: ViewState) : ViewModel(), ViewStateCache<ViewState> {
override val cacheScope: CoroutineScope = viewModelScope
private val _viewState = MutableLiveData<ViewState>(initialState)
private val viewState: LiveData<ViewState> = _viewState
override fun updateViewState(transform: (ViewState) -> ViewState) =
IO {
val transformedState = transform(viewState.value!!)
_viewState.postValue(transformedState)
transformedState
}
override fun observeViewState(observer: LifecycleOwner, renderingScope: CoroutineScope, render: (ViewState) -> IO<Unit>) =
IO {
viewState.observe(observer, Observer<ViewState> { viewState ->
viewState?.let { render(it).unsafeRunScoped(renderingScope) {} }
})
}
}
这样,您就可以有效地获得使用Android ViewModel实现的视图状态缓存。这是一个实现细节,所以你可以注入它。你的程序将针对接口工作。
在这里,ViewModel仅作为向传递结果的缓存,并且通过将其观察视图状态并将其更新为IO
的操作包装起来,使其变得纯粹。
有了这样的东西,你就可以有纯函数来编码你的表示和线程协调逻辑,并将结果传递到提到的缓存中,比如:
fun doNetworkRequest(): IO<Unit> = IO.fx {
!viewStateCache.updateViewState { Loading }
!repository.requestSomeResult().redeemWith(
ft = {
viewStateCache.updateViewState { ErrorViewState(ServerError) }
},
fe = { error ->
viewStateCache.updateViewState { ErrorViewState(error) }
},
fb = { data ->
viewStateCache.updateViewState { SuccessfulViewState(data) }
}
)
}
这些函数不需要存在于视图模型中,而是将缓存用作向其传递结果的委托,因此可以将其作为实现细节注入。
您还需要在创建视图后立即开始观察视图状态缓存,因此这与您已经对视图模型进行的操作类似。请注意,我们有意将作用域作为缓存契约的一部分公开,这样您就可以从外部访问它。
这是一个如何包装当前ViewModel api以继续使用它们并确保其配置更改得到支持的示例,主要考虑逐步迁移到Arrow。
这种方法更像是一种方便的方法,可能需要一些打磨,但应该有效。我们目前正在探索Android常见的问题,如配置更改,以通过扩展或类似的集成库为用户提供无缝体验。
希望这足够有用,如果没有,请告诉我🙏