MVIKotlin学习笔记(2):Store
Store
Store
用来写业务逻辑。
在MVIKotlin
中用Store
接口表示。
接口特性
- 三个参数:输入
Intent
、输出State
、Label
。 - 属性
state
返回当前Store
的State
。 - 可以在任何线程实例化。
- 函数
states(Observer
用于订阅) State
的更新。订阅时他会发出Store
的State
。可以在任何线程调用。States
总是在主线程上发出。 - 函数
labels(Observer
用于订阅Labels
。可以在任何线程调用。Labels
总是在主线程上发出。 - 函数
accept(Intent)
用于给Store
补给Intents
。只能在主线程调用。 - 函数
init()
用于初始化Store
。如果可以的话会触发Bootstrapper
。只能在主线程调用。 - 函数
dispose()
用于释放Store
并取消它的所有异步操作。只能在主线程调用。
states(Observer
与) labels(Observer
通常不直接使用。可以使用扩展Reaktive
与kotlinx.coroutines
类库(详见生命周期)。只有在自定义扩展时会用到这些函数。
组件
任何Store
最多只有三个组件:引导程序(Bootstrapper
)、执行者(Executor
)与缩减器(Reducer
)。
Reducer
为什么叫缩减器:https://blog.csdn.net/uwenhao2008/article/details/79613717
Bootstrapper
用于快速启动Store
。
如果Bootstrapper
被传递给StoreFactory
,它会在Store
的初始化期间被执行。
Bootstrapper
生产Actions
给Executor
处理。
Bootstrapper
总是在主线程执行,Actions
也只能在主线程调度。在Bootstrapper
执行时可以自由切换线程。
Bootstrapper
是有状态的,不能作为单例使用。
Executor
Executor
用来写业务逻辑,所有的异步操作都发生在这里。
Executor
接收并处理来自外部的Intents
与来自内部的Actions
。
Executor
有两种输出:Messages
与Labels
。
-
Messages
被传递给Reducer
。 -
Labels
被直接发送到外部。
Executor
持续访问Store
的State
。在Message
被调度后,新的State
对Executor
来说是可见的。
Executor
总是在主线程执行,Messages
与Labels
也只能在主线程调度。在Action
与Intents
处理时可以自由切换线程。
Executor
是有状态的,不能作为单例使用。
Reducer
Reducer
是一个函数。它接收来自Executor
的Message
与Store
的State
作为参数,返回一个新的State
。
Reducer
在任何Message
被生产后调用,调用返回后应用并发送新的State
。
Reducer
总是在主线程调用。
创建Store
通常不需要直接实现Store
接口,应该使用StoreFactory
来创建,只需要将Bootstrapper
、Executor
和Reducer
传入并初始化State
即可。可以在不同的情况下使用不同的StoreFactory
并在需要的时候组合他们。
一些由MVIKotlin
提供的Factory:
DefaultStoreFactory
创建默认的Store
实例。由mvikotlin-main
模块提供。LoggingStoreFactory
包装另一个StoreFactory
并添加日志记录。由mvikotlin-logging
模块提供。- TimeTravelStoreFactory 创建具有时间旅行功能的
Store
。由mvikotlin-timetravel
模块提供。
初始化Store
默认情况下,Stores
由StoreFactory
自动初始化。可以通过设置StoreFactory.create(...)
函数的autoInit
参数为false
来禁用自动初始化。
如果自动初始化被禁用,你应该使用
Store.init()
函数进行手动初始化。
IDEA动态模板
用于快速创建新的Store
:https://gist.github.com/arkivanov/34bb84e73e56c22a4e7c752421d5f02c
最简单的例子
这个例子会创建一个简单的计数器Store
,它可以实现增加或减少它的值。
定义接口
首先,定义一个接口,它看起来像这样:
internal interface CalculatorStore : Store {
sealed class Intent {
object Increment : Intent()
object Decrement : Intent()
}
data class State(
val value: Long = 0L
)
}
CalculatorStore
接口本身可以标记为internal
,因此这是一个模块的具体实现。
同时,CalculatorStore
有两个Intents
(Increment
和Decrement
)并且State
有一个Long
类型的属性value
。这是这个Store
的公开API部分。
实现工厂
接下来是Store
的实例化工厂:
internal class CalculatorStoreFactory(private val storeFactory: StoreFactory) {
fun create(): CalculatorStore =
object : CalculatorStore, Store by storeFactory.create(
name = "CounterStore",
initialState = State(),
reducer = ReducerImpl
) {
}
private object ReducerImpl : Reducer {
override fun State.reduce(msg: Intent): State =
when (msg) {
is Intent.Increment -> copy(value = value + 1L)
is Intent.Decrement -> copy(value = value - 1L)
}
}
}
我们只需要Reducer
组件。它接受Intents
并且通过递增或递减value
的值来修改State
。
工厂的create()
函数使用作为依赖项传递的StoreFactory
。
增加Executor
目前CalculatorStore
只能递增或递减它的值。接下来我们需要实现计算从1加到value
的总和。我们需要一个新的Intent
:
internal interface CalculatorStore : Store {
sealed class Intent {
object Increment : Intent()
object Decrement : Intent()
data class Sum(val n: Int): Intent() // <-- 增加了这行
}
data class State(
val value: Long = 0L
)
}
目前的想法是CalculatorStore
会接收Intent.Sum(N)
,计算从1加到value
的总和并使用计算得到的值更新State
。但是这个计算是很耗时的,所以我们应该让它在后台线程中执行。因此我们需要Executor
。
为了让Executor
可以和Reducer
进行通信,我们需要Messages
:
internal class CalculatorStoreFactory(private val storeFactory: StoreFactory) {
private sealed class Msg {
class Value(val value: Long) : Msg()
}
}
我们需要一个新的Reducer
,现在它可以通过接收Messages
来代替Intents
了:
internal class CalculatorStoreFactory(private val storeFactory: StoreFactory) {
private sealed class Msg {
class Value(val value: Long) : Msg()
}
private object ReducerImpl : Reducer {
override fun State.reduce(msg: Msg): State =
when (msg) {
is Msg.Value -> copy(value = msg.value)
}
}
}
Msg.Value(Long)
用来替换State
中value
的值。
接下来是Executor
。我们不需要实现整个接口,只需要扩展基本实现即可。
两个由MVIKotlin
提供的Executors
基本实现:
- ReaktiveExecutor 基于Reaktive库实现。由
mvikotlin-extensions-reaktive
模块提供。 - CoroutineExecutor 基于协程实现。由
mvikotlin-extensions-coroutines
模块提供。
两个都来尝试一下:
ReaktiveExecutor
internal class CalculatorStoreFactory(private val storeFactory: StoreFactory) {
// ...
private class ExecutorImpl : ReaktiveExecutor() {
override fun executeIntent(intent: Intent, getState: () -> State) =
when (intent) {
is Intent.Increment -> dispatch(Msg.Value(getState().value + 1))
is Intent.Decrement -> dispatch(Msg.Value(getState().value - 1))
is Intent.Sum -> sum(intent.n)
}
private fun sum(n: Int) {
singleFromFunction { (1L..n.toLong()).sum() }
.subscribeOn(computationScheduler)
.map(Msg::Value)
.observeOn(mainScheduler)
.subscribeScoped(onSuccess = ::dispatch)
}
}
// ...
}
ExecutorImpl
继承自ReaktiveExecutor
并实现了executeIntent
方法。executeIntent
提供了一个Intent
和一个当前State
的提供者。对于Intent.Increment
和Intent.Decrement
只需要简单地发送一个带有由dispatch
函数包装的新的值的Message
。但对于Intent.Sum
需要用到Reaktive
来进行多线程处理:在computationScheduler
中求和,然后转换到mainScheduler
并使用dispatch
来发送Message
。
ReaktiveExecutor
实现了Reaktive
的 DisposableScope,它提供了许多扩展函数。我们使用了其中之一:subscribeScoped
,这样可以确保在Store
被释放时订阅也可以被释放。
CoroutineExecutor
internal class CalculatorStoreFactory(private val storeFactory: StoreFactory) {
// ...
private class ExecutorImpl : CoroutineExecutor() {
override fun executeIntent(intent: Intent, getState: () -> State) =
when (intent) {
is Intent.Increment -> dispatch(Msg.Value(getState().value + 1))
is Intent.Decrement -> dispatch(Msg.Value(getState().value - 1))
is Intent.Sum -> sum(intent.n)
}
private fun sum(n: Int) {
scope.launch {
val sum = withContext(Dispatchers.Default) { (1L..n.toLong()).sum() }
dispatch(Msg.Value(sum))
}
}
}
// ...
}
ExecutorImpl
继承自CoroutineExecutor
。求和过程在Default
调度器中被执行,Message
在Main
线程中被调度。
CoroutineExecutor
提供了名为scope
的CoroutineScope
属性,我们可以用它来执行异步任务。scope
的默认调度器是Dispatchers.Main
,可以通过传给CoroutineExecutor
的构造函数不同的CoroutineContext
来重写默认调度器。在Store
被释放时scope
会自动取消。
发布Labels
Labels
是由Store
(或Executor
)生产的一次性事件。一旦Labels
被发布,它们会被当前所有的订阅者接收并且不会缓存。Executor
有一个专门用来发布Label
的函数:publish(Label)
。
创建Store
我们需要将Executor
的构建工厂交给StoreFactory
:
internal class CalculatorStoreFactory(private val storeFactory: StoreFactory) {
fun create(): CalculatorStore =
object : CalculatorStore, Store by storeFactory.create(
name = "CounterStore",
initialState = State(),
executorFactory = ::ExecutorImpl, // <-- 交付Executor的工厂
reducer = ReducerImpl
) {
}
// ...
}
为什么要使用构建工厂而不是直接使用一个Executor
的实例呢?因为前者可以实现时间旅行功能。在调试时间旅行事件时,它会在必要时创建单独的执行器实例,并伪造它们的States
。
增加Bootstrapper
当我们需要创建Store
的一个新的实例时,它会保持一个初始化State
并什么都不做,直到你提供了一个Intent
。但有时需要引导启动一个Store
,在它被创建时做一些额外的事情。例如,它可以开始从服务端接收事件,或从数据库中读取一些数据。这就是Bootstrapper
要做的事:生产Actions
并交给Executor
运行,就像Intents
一样。
CalculatorStore
能够计算从1到N的和,目前它会在接收到Intent.Sum(N)
时执行这一步骤。让我们使用Bootstrapper
来让CalculatorStore
被创建时计算sum(100)
。Executor
已经实现了计算的具体过程,所以我们只要发送一个触发Action
给Executor
,就像Intent.Sum(N)
。
首先,添加一个Action
:
internal class CalculatorStoreFactory(private val storeFactory: StoreFactory) {
// ...
private sealed class Action {
class Sum(val n: Int): Action()
}
// ...
}
然后,在ReaktiveExecutor
中处理这个Action
:
internal class CalculatorStoreFactory(private val storeFactory: StoreFactory) {
// ...
private class ExecutorImpl : ReaktiveExecutor() {
override fun executeAction(action: Action, getState: () -> State) =
when (action) {
is Action.Sum -> sum(action.n)
}
// ...
}
// ...
}
或者在CoroutineExecutor
中来处理,是一样的:
internal class CalculatorStoreFactory(private val storeFactory: StoreFactory) {
// ...
private class ExecutorImpl : CoroutineExecutor() {
override fun executeAction(action: Action, getState: () -> State) =
when (action) {
is Action.Sum -> sum(action.n)
}
// ...
}
// ...
}
最后,我们需要触发这个Action
。我们需要将一个Bootstrapper
传给StoreFactory
。对于这种简单的情况,我们只需要使用SimpleBootstrapper
:
internal class CalculatorStoreFactory(private val storeFactory: StoreFactory) {
fun create(): CalculatorStore =
object : CalculatorStore, Store by storeFactory.create(
name = "CounterStore",
initialState = State(),
bootstrapper = SimpleBootstrapper(Action.Sum(100)), // <-- 增加了这行
executorFactory = ::ExecutorImpl,
reducer = ReducerImpl
) {
}
// ...
}
SimpleBootstrapper
只调度了提供的Actions
,但有时我们可能需要其他的引导程序,例如后台工作:
使用来自mvikotlin-extensions-reaktive
模块的ReaktiveBootstrapper
:
internal class CalculatorStoreFactory(private val storeFactory: StoreFactory) {
fun create(): CalculatorStore =
object : CalculatorStore, Store by storeFactory.create(
name = "CounterStore",
initialState = State(),
bootstrapper = BootstrapperImpl, // <-- 传BootstrapperImp给 StoreFactory
executorFactory = ::ExecutorImpl,
reducer = ReducerImpl
) {
}
private sealed class Action {
class SetValue(val value: Long): Action() // <-- 使用另一个Action
}
// ...
private class BootstrapperImpl : ReaktiveBootstrapper() {
override fun invoke() {
singleFromFunction { (1L..1000000.toLong()).sum() }
.subscribeOn(computationScheduler)
.map(Action::SetValue)
.observeOn(mainScheduler)
.subscribeScoped(onSuccess = ::dispatch)
}
}
private class ExecutorImpl : ReaktiveExecutor() {
override fun executeAction(action: Action, getState: () -> State) =
when (action) {
is Action.SetValue -> dispatch(Msg.Value(action.value)) // <-- 处理Action
}
// ...
}
// ...
}
ReaktiveBootstrapper
也实现了DisposableScope
,就像ReaktiveExecutor
一样。所以我们也可以在这里使用subscribeScoped
函数。
使用来自mvikotlin-extensions-coroutines
模块的CoroutineBootstrapper
:
internal class CalculatorStoreFactory(private val storeFactory: StoreFactory) {
fun create(): CalculatorStore =
object : CalculatorStore, Store by storeFactory.create(
name = "CounterStore",
initialState = State(),
bootstrapper = BootstrapperImpl,
executorFactory = ::ExecutorImpl,
reducer = ReducerImpl
) {
}
private sealed class Action {
class SetValue(val value: Long): Action()
}
// ...
private class BootstrapperImpl : CoroutineBootstrapper() {
override fun invoke() {
scope.launch {
val sum = withContext(Dispatchers.Default) { (1L..1000000.toLong()).sum() }
dispatch(Action.SetValue(sum))
}
}
}
private class ExecutorImpl : CoroutineExecutor() {
override fun executeAction(action: Action, getState: () -> State) =
when (action) {
is Action.SetValue -> dispatch(Msg.Value(action.value))
}
// ...
}
// ...
}
CoroutineBootstrapper
也提供了名为scope
的CoroutineScope
属性,就和CoroutineExecutor
一样,所以我们可以用它来执行异步任务。