MVIKotlin学习笔记(2):Store


Store

Store用来写业务逻辑。

MVIKotlin中用Store接口表示。

接口特性

  • 三个参数:输入Intent、输出StateLabel
  • 属性state返回当前StoreState
  • 可以在任何线程实例化。
  • 函数states(Observer)用于订阅State的更新。订阅时他会发出StoreState。可以在任何线程调用。States总是在主线程上发出。
  • 函数labels(Observer用于订阅Labels。可以在任何线程调用。Labels总是在主线程上发出。
  • 函数accept(Intent)用于给Store补给Intents。只能在主线程调用。
  • 函数init()用于初始化Store。如果可以的话会触发Bootstrapper。只能在主线程调用。
  • 函数dispose()用于释放Store并取消它的所有异步操作。只能在主线程调用。

states(Observer)labels(Observer通常不直接使用。可以使用扩展Reaktivekotlinx.coroutines类库(详见生命周期)。只有在自定义扩展时会用到这些函数。

组件

任何Store最多只有三个组件:引导程序(Bootstrapper)、执行者(Executor)与缩减器(Reducer)。

Reducer为什么叫缩减器:https://blog.csdn.net/uwenhao2008/article/details/79613717

Store

Bootstrapper

用于快速启动Store

如果Bootstrapper被传递给StoreFactory,它会在Store的初始化期间被执行。

Bootstrapper生产ActionsExecutor处理。

Bootstrapper总是在主线程执行,Actions也只能在主线程调度。在Bootstrapper执行时可以自由切换线程。

Bootstrapper是有状态的,不能作为单例使用。

Executor

Executor用来写业务逻辑,所有的异步操作都发生在这里。

Executor接收并处理来自外部的Intents与来自内部的Actions

Executor有两种输出:MessagesLabels

  • Messages被传递给Reducer

  • Labels被直接发送到外部。

Executor持续访问StoreState。在Message被调度后,新的StateExecutor来说是可见的。

Executor总是在主线程执行,MessagesLabels也只能在主线程调度。在ActionIntents处理时可以自由切换线程。

Executor是有状态的,不能作为单例使用。

Reducer

Reducer是一个函数。它接收来自ExecutorMessageStoreState作为参数,返回一个新的State

Reducer在任何Message被生产后调用,调用返回后应用并发送新的State

Reducer总是在主线程调用。

创建Store

通常不需要直接实现Store接口,应该使用StoreFactory来创建,只需要将BootstrapperExecutorReducer传入并初始化State即可。可以在不同的情况下使用不同的StoreFactory并在需要的时候组合他们。

一些由MVIKotlin提供的Factory:

  • DefaultStoreFactory 创建默认的Store实例。由mvikotlin-main模块提供。
  • LoggingStoreFactory 包装另一个StoreFactory并添加日志记录。由mvikotlin-logging模块提供。
  • TimeTravelStoreFactory 创建具有时间旅行功能的Store。由mvikotlin-timetravel模块提供。

初始化Store

默认情况下,StoresStoreFactory自动初始化。可以通过设置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(IncrementDecrement)并且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)用来替换Statevalue的值。

接下来是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.IncrementIntent.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调度器中被执行,MessageMain线程中被调度。

CoroutineExecutor提供了名为scopeCoroutineScope属性,我们可以用它来执行异步任务。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已经实现了计算的具体过程,所以我们只要发送一个触发ActionExecutor,就像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也提供了名为scopeCoroutineScope属性,就和CoroutineExecutor一样,所以我们可以用它来执行异步任务。