我们好啊,我是运用 Compose 时长两年半的 Android 开发者,今日来点我们想看的东西啊,间隔上次文章也现已曩昔一段时刻了,是时分再次总结一下了。
期间一直在实践着之前文章说的运用 Compose 编写事务逻辑,但随着事务逻辑和页面越来越杂乱,在运用的过程中也遇到了一些问题。
Compose Presenter
上一篇文章中有说到的用 Compose 写事务逻辑是这样写的:
@Composable
fun Presenter(
action: Flow<Action>,
): State {
var count by remember { mutableStateOf(0) }
action.collectAction {
when (this) {
Action.Increment -> count++
Action.Decrement -> count--
}
}
return State("Clicked $count times")
}
优点在之前的文章中也说到过了,这儿就不再赘述,说一下这段时刻实践下来发现的缺点:
- 事务杂乱后会拆分出十分多的 Presenter,导致在最终组合 Presenter 的时分会十分杂乱,特别是对于子 Presenter 的 Action 处理
- 假如 Presenter 有 Action,这样的写法并不能很好的处理 early return。
一个一个说
组合 Action 处理
每调用一个带 Action 的子 Presenter,就至少需求新建一个 Channel 以及对应的 Flow,并且需求添加一个对应的 Action 处理,举个比如
@Composable
fun FooPresenter(
action: Flow<FooAction>
): FooState {
// ...
// 创立子 Presenter 需求的 Channel 和 Flow
val channel = remember { Channel<Action>(Channel.UNLIMITED) }
val flow = remember { channel.consumeAsFlow() }
val state = Presenter(flow)
LaunchedEffect(Unit) {
action.collect {
when (it){
// 处理并传递 Action 到子 Presenter中
is FooAction.Bar -> channel.trySend(it.action)
}
}
}
// ...
return FooState(
state = state,
// ...
)
}
假如页面和事务逻辑杂乱之后,组合 Presenter 会带来十分多的冗余代码,这些代码仅仅做桥接,没有任何的事务逻辑。并且在 Compose UI 中发起子 Presenter 的 Action 时也需求桥接调用,最终很容易导致冗余代码过多。
Early return
假如一个 Presenter 中有 Action 处理,那么需求十分当心的处理 early return,例如:
@Composable
fun Presenter(
action: Flow<Action>,
): State {
var count by remember { mutableStateOf(0) }
if (count == 10) {
return State("Woohoo")
}
action.collectAction {
when (this) {
Action.Increment -> count++
Action.Decrement -> count--
}
}
return State("Clicked $count times")
}
当 count == 10
时会直接 return,跳过后边的 Action 事情订阅,造成后续的事情永久无法触发。所以一切的 return 必须在 Action 事情订阅之后。
当事务杂乱之后,上面两个缺点就成为了最大的痛点。
处理方案
有一天半夜我看到了 Slack 的 Circuit 是这样写的:
object CounterScreen : Screen {
data class CounterState(
val count: Int,
val eventSink: (CounterEvent) -> Unit,
) : CircuitUiState
sealed interface CounterEvent : CircuitUiEvent {
object Increment : CounterEvent
object Decrement : CounterEvent
}
}
@Composable
fun CounterPresenter(): CounterState {
var count by rememberSaveable { mutableStateOf(0) }
return CounterState(count) { event ->
when (event) {
is CounterEvent.Increment -> count++
is CounterEvent.Decrement -> count--
}
}
}
这 Action 本来还能够在 State 里边以 Callback 的形式处理,瞬间两眼放光,一次性处理了两个痛点:
- 子 Presenter 不再需求 Action Flow 作为参数,事情处理直接在 State Callback 里边完成,减少了大量的冗余代码
- 在 return 的时分就附带 Action 处理,early return 不再是问题。
好了,之后的 Presenter 就这么写了。期待再过半年的我能再总结出来一些坑吧。
为什么 Early return 会导致事情订阅失效
或许有人会好奇这一点,Presenter 内不是现已订阅过了吗,怎么还会失效。
我们仍是从 Compose 的原理开始说起吧。
先免责声明一下:以下是我对 Compose 完成原理的了解,难免会有过错的当地。
网上叙述 Compose 原理的文章都十分多了,这儿就不再赘述,中心思维是:Compose 的状况由一个 SlotTable 保护。
仍是结合 Early return 的比如来说,我稍微画了一下 SlotTable 在不同时分的状况:
@Composable
fun Presenter(
action: Flow<Action>, count != 10 | count == 10
): State {
var count by remember { mutableStateOf(0) } | State | State |
if (count == 10) { | State | State |
return State("Woohoo") | Empty | State |
} | | |
action.collectAction { | State | Empty |
when (this) { | State | Empty |
Action.Increment -> count++ | State | Empty |
Action.Decrement -> count-- | State | Empty |
} | | |
} | | |
return State("Clicked $count times") | State | Empty |
}
当 count != 10
的时分,SlotTable 内部保存的状况是包含 Action 事情订阅的,可是当 count == 10
之后,SlotTable 就会清空一切之后语句对应的状况,而之后正好包含了 Action 事情订阅,所以订阅就失效了。
我觉得这是 Compose 和 React Hooks 又一个十分相似的当地,React Hooks 的状况也是由一个列表保护的。
再举一个比如:
@Composable
fun Greeting(name: String, modifier: Modifier = Modifier) {
Column {
var boolean by remember {
mutableStateOf(true)
}
Text(
text = "Hello $name!",
modifier = modifier
)
Button(onClick = {
boolean = !boolean
}) {
Text(text = "Hide counter")
}
if (boolean) {
var a by remember {
mutableStateOf(0)
}
Button(onClick = {
a++
}) {
Text(text = "Add")
}
Text(text = "a = $a")
}
}
}
这段代码我们也能够试试。当我做如下操作时:
- 点击 Add 按钮,此刻显现
a = 1
- 点击 Hide counter 按钮,此刻 counter 被躲藏
- 再次点击 Hide counter 按钮,此刻 counter 显现,其间
a = 0
由于当 counter 被躲藏时,包含变量 a
在内一切的状况都从 SlotTable 里边清除了,那么新出现的变量 a
其实是完全一个新初始化的一个变量,和之前的变量没有任何关系。
总结
过了大半年,也算是对 Compose 内部完成原理又有了一个十分深刻的认识,特别是当我用 C# 自己完成一遍声明式 UI 之后,然后再次感叹:SlotTable 真是天才般的处理思路,本质上并不杂乱,但大大简化了声明式 UI 的状况管理。