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一样,所以我们可以用它来执行异步任务。