我正在尝试学习 Arrow 库并通过将我的一些 Android Kotlin 代码从更命令式风格转换为函数式风格来改进我的函数式编程。我一直在应用程序中进行一种 MVI 编程,以使测试更简单。
“传统”方法
视图模型
我的视图模型有一个LiveData
视图的状态加上一个公共方法将用户交互从视图传递到视图模型,因此视图模型可以以任何合适的方式更新状态。
class MyViewModel: ViewModel() {
val state = MutableLiveData(MyViewState()) // MyViewState is a data class with relevant data
fun instruct(intent: MyIntent) { // MyIntent is a sealed class of data classes representing user interactions
return when(intent) {
is FirstIntent -> return viewModelScope.launch(Dispatchers.IO) {
val result = myRoomRepository.suspendFunctionManipulatingDatabase(intent.myVal)
updateStateWithResult(result)
}.run { Unit }
is SecondIntent -> return updateStateWithResult(intent.myVal)
}
}
}
活动
Activity 订阅LiveData
并且在状态更改时,它使用状态运行渲染函数。该活动还将用户交互作为意图传递给视图模型(不要与 Android 的Intent
类混淆)。
class MyActivity: AppCompatActivity() {
private val viewModel = MyViewModel()
override fun onCreateView() {
viewModel.state.observe(this, Observer { render(it) })
myWidget.onClickObserver = {
viewModel.instruct(someIntent)
}
}
private fun render(state: MyViewState) { /* update view with state */ }
}
Arrow.IO 函数式编程
我很难找到使用 Arrow 的 IO monad 使副作用明显且可进行单元测试的不纯函数的示例。
查看模型
到目前为止,我已经把我的视图模型变成了:
class MyViewModel: ViewModel() {
// ...
fun instruct(intent: MyIntent): IO<Unit> {
return when(intent) {
is FirstIntent -> IO.fx {
val (result) = effect { myRoomRepository.suspendFunctionManipulatingDatabase(intent.myVal) }
updateStateWithResult(result)
}
is SecondIntent -> IO { updateStateWithResult(intent.myVal) }
}
}
}
我不知道我应该如何让这些 IO 东西Dispatcher.IO
像我一直在做的那样运行viewModelScope.launch
。我找不到如何使用 Arrow 执行此操作的示例。进行 API 调用的似乎都不是 Android 应用程序,因此没有关于 Android UI 与 IO 线程的指导。
查看模型单元测试
现在,因为我看到的一个好处是,当我编写视图模型的单元测试时,我可以进行测试。如果我模拟存储库以检查是否suspendFunctionManipulatingDatabase
使用预期参数调用。
@Test
fun myTest() {
val result: IO<Unit> = viewModel.instruct(someIntent)
result.unsafeRunSync()
// verify suspendFunctionManipulatingDatabase argument was as expected
}
活动
我不知道如何将上述内容合并到我的活动中。
class MyActivity: AppCompatActivity() {
private val viewModel = MyViewModel()
override fun onCreateView() {
viewModel.state.observe(this, Observer { render(it) })
myWidget.onClickObserver = {
viewModel.instruct(someIntent).unsafeRunSync() // Is this how I should do it?
}
}
// ...
}
我的理解是 IO 块中的任何内容都不会立即运行(即它是惰性的)。您必须调用 attempt() 或 unsafeRunSync() 来获取要评估的内容。
从 Activity调用
viewModel.instruct
意味着我需要创建一些范围并调用Dispatchers.IO
对吗?这是 Bad(TM) 吗?我能够使用“传统”方法将协程完全限制在视图模型中。我在哪里合并
Dispatchers.IO
来复制我所做的viewModelScope.launch(Dispatchers.IO)
?这是您在使用 Arrow 时应该构建单元测试的方式
IO
吗?