About
一个精简的 Redux 库,去掉了一些 Redux 官方实例中冗余的设计
基础概念
GUI 开发,通常有3个角色:
state: 描述数据状态
action: 管理 state
view: 消费 state,触发 action 逻辑
换成 Android 开发容易理解的话,就是
state = immutable data class
actions = repository, viewmodel, presenter, service, controller, manager
view = view, activity, fragment
一个简单的 counter 示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| data class State( val count: Int = 0 )
class CounterViewModel { privte val stateListeners = mutableList<(State)->Unit>()
var state = State() private set
fun increment() { state = state.copy(count = state.count + 1)
stateListeners.foreach { it(state) } }
fun onStateChange(listener: (State)->Unit) { stateListeners.add(listener)
listenter(state) } }
class CounterFrament(viewModel: CounterViewModel) { init { viewMode.onStateChange { counterText = viewmodel.state.count }
setOnClickListener { viewModel.increment() } } }
|
因为 Android Architect 里边,把数据层称为 Repository,为了更符合Android 平台的称呼,所以接下来说的 Repsitory = Action。
核心
看了上面的示例,就会发现,核心只有两个:
- immutable data class( = state)
- Repository( = action): 管理数据
实现
state 是 任意 immutable data class 就可以。不需要另外多余的抽象。
所以把实现的重点放到 Repository
Repository 需要关心:
- 存储 state
- 分发 state
- 更新 state
逻辑的生命周期管理
分发 state,对应就有 接收state,这里是 1:N 的关系,1 个 state 可能有 N 个接收者,
Repository 作为 state 生产者,定位类似 kotlin flow, 只管分发数据,消费者决定怎么使用数据,
通常只有使用者知道什么时候不需要数据了,所以我们可以把监听数据这部分的生命周期管理交给使用者,这样就可以借助 kotlin coroutine scope,消费者自己管理监听&取消监听数据。
Repository 作为数据容器,action 可能很简单,也可能很复杂,
业务上很可能是需要有 诸如 启动,销毁的流程,但是 RxStateRepository 不知道如何处理,这部分属于不能抽象的部分,只能留给使用方(RxStateRepository 的 子类等)处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
| package xyz.icode.mvs
import kotlinx.coroutines.flow.Flow import kotlin.reflect.KProperty1
interface RxState
interface RxRepo<State : RxState> { val state: State
fun RxRepo<State>.setState(reducer: State.() -> State)
fun asFlow(): Flow<State>
suspend fun <A : Any> onEach(property: KProperty1<State, A>, action: (A) -> Unit)
suspend fun <A : Any> onEach(property: KProperty1<State, A>): Flow<A>
suspend fun onEach(action: (State) -> Unit) }
fun <State: RxState> RxRepo(initState: State): RxRepo<State> = RxRepoImpl(initState)
|
实现类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| internal class RxRepoImpl<State : RxState>(initState: State) : RxRepo<State> { private val stateFlow = MutableStateFlow(initState)
override val state: State get() = stateFlow.value
override fun asFlow(): Flow<State> = stateFlow.asStateFlow()
@Suppress("UNCHECKED_CAST") override fun RxRepo<State>.setState(reducer: State.() -> State) { stateFlow.value = reducer(state) }
override suspend fun <A : Any> onEach( property: KProperty1<State, A>, action: (A) -> Unit ) { childScope().launch { stateFlow.collectLatest { action(property.get(it)) } } }
override suspend fun onEach(action: (State) -> Unit) { childScope().launch { stateFlow.collectLatest(action) } }
override suspend fun <A : Any> onEach(property: KProperty1<State, A>): Flow<A> { val scope = childScope()
return flow { scope.launch { stateFlow .collectLatest { emit(property.get(it)) } } } }
private suspend inline fun childScope(): CoroutineScope { return CoroutineScope(context = currentCoroutineContext()) } }
|
使用 Demo
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| data class CounterState( val count:Int = 0 )
class CounterRepo: RxStateRepo(CounterState(0)) { fun increment() { setState { copy( count = count + 1) } } }
class CounterView { private val counter = CounterRepo() private val scope = MainScope()
init { scope.launch { counter.onEach(CounterState::count) { text = "Click $it" } }
setOnClickListener { counter.increment() } } }
class FooViewModel: ViewModel(), RxStateRepo by RxStateRepo(FooState()) { fun updateFooToBar(bar: Any) { setState { copy(foo = bar) } } }
|
参考
- Redux Essentials
- Mavericks: Android on Autopilot
- 源码 & Demo: RxState for Android