use*_*450 3 android functional-programming io-monad arrow-kt kotlin-coroutines
我正在尝试学习 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)
}
}
}
Run Code Online (Sandbox Code Playgroud)
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 */ }
}
Run Code Online (Sandbox Code Playgroud)
我很难找到使用 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) }
}
}
}
Run Code Online (Sandbox Code Playgroud)
我不知道我应该如何让这个 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
}
Run Code Online (Sandbox Code Playgroud)
我不知道如何将上述内容合并到我的活动中。
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?
}
}
// ...
}
Run Code Online (Sandbox Code Playgroud)
我的理解是 IO 块中的任何内容都不会立即运行(即,它很懒惰)。您必须调用 try() 或 unsafeRunSync() 来获取要评估的内容。
viewModel.instruct从 Activity调用意味着我需要创建一些范围并调用Dispatchers.IO对吗?这是坏(TM) 吗?我能够使用“传统”方法将协程完全限制在视图模型中。
我在哪里合并Dispatchers.IO以复制我所做的viewModelScope.launch(Dispatchers.IO)?
这是您在使用 Arrow 时构建单元测试的方式IO吗?
这确实是一篇非常值得阅读的文章。我还建议深入研究我编写的使用 ArrowFx 的示例应用程序。
https://github.com/JorgeCastilloPrz/ArrowAndroidSamples
我肯定还建议阅读完整的 ArrowFx 文档以更好地理解:https ://arrow-kt.io/docs/fx/我认为这会有所帮助。
有关使用函数式编程和 Arrow 到 Android 的方法的更多想法,您可以查看我的博客https://jorgecastillo.dev/我的计划是围绕 2020 年开始编写深入的内容,因为有很多人感兴趣。
另一方面,您可以在 Kotlinlang JetBrains Slack 中找到我或任何其他 Arrow 团队维护人员,我们可以在那里进行更详细的对话或尝试解决您可能遇到的任何疑问 https://kotlinlang.slack.com/
最后澄清一下:函数式编程只是一种范式,可以解决异步、线程、并发、依赖注入、错误处理等通用问题。这些问题可以在任何程序中找到,而与平台无关。即使在 Android 应用程序中。这就是为什么 FP 是一个与其他任何选项一样适用于移动设备的选项,但我们仍在探索以更符合人体工程学的方式提供最好的 API 来满足通常的 Android 需求。我们正处于这个意义上的探索过程中,2020年将是充满希望的一年。
希望这有帮助!您的想法似乎与这种方法的整体运作方式非常一致。