未知渐渐变成了已知所以活着很无聊。但是世界上依旧存在很多未知,只要还有好奇心就能找到那些角落里的谜题,解开这些谜题的过程很让人上瘾,以此让自己想起解谜的乐趣:
谜题1.诡异的SnackBar
谷歌官方的代码里给了一个把状态委派给其他类的例子,还算是比较容易理解,例子里面的showSnackbar方法,到了真正实现的时候大概也就是开一个协程然后调用scaffoldState.snackbarHostState.showSnackbar(message)
然后就看了官方给的某个示例源码(JetSnack)中的实现,结果诡异的事情出现了:
这里直接开了个协程,然后还单独写了一个SnackManager,里面维护了一个用于存放Message的List,Message是作者自己写的数据类,两个参数一个是UUID一个是要显示的文字的资源id。
如无必要,勿增实体的原则在编程中也是适用的,明明调用一个方法就能解决的问题,为什么这里要单独开一个类,还要维护一个List,甚至还用上了Flow?这显然是一个谜题。
最初的疑问是Message的id,但是看到后面的代码这个谜题很快解开了:
id的作用是区分不同的Message对象便于删除。
但是,如果是这样直接用hashCode不是更方便,有必要整一个UUID吗?这里依旧是谜题。
最大的一个疑问是,为什么要维护一个List?如果说想用Flow,直接把要显示的文字作为MutableStateFlow就好了,用了List结果还是取第一个元素,目的是什么?
过程我忘记的差不多了,直接说结论:
1.当collect块里面调用了挂起函数,在那个函数完成以前无论怎么更新数据都不会触发收集。
2.基于上面的原因,加上我直到今天才发现,scaffoldState.snackbarHostState.showSnackbar()是一个挂起函数,答案就显然易见了。
3.List是为了防止某些手贱的用户点的太快,导致SnackBar被吞。如果把要显示的内容作为StateFlow,在collect块挂起期间用户多次触发显示SnackBar的逻辑,就只会显示最后一次触发时的文字。我开始以为调用几次update就会触发几次collect,没有万恶的挂起函数是这样,但是一旦挂起,无论调用多少次update挂起结束时都只会触发一次collect并且数据以最后一次update为准。
那么问题又回到了最初,为什么非要用Flow把显示SnackBar这件事变得复杂呢?
还是忘记了过程,直接说答案:为了解耦。
试想一个场景,一个输入框要输入电话号码,如果用户点击确定时号码不足11位,那么此时要弹一个Toast。极其简单的场景,但是按照MVVM的原则,手机号码是数据,数据的持有者一定是ViewModel,而且检测号码是否合法的逻辑也应该写在ViewModel里面。
ViewModel肯定不能直接弹Toast,那么解决方案可以是提供一个回调,让UI那边来弹出。但这样还是有问题,这个逻辑还是在ViewModel里面调用的,而且Compose下这个方法不行,因为回调脱离了Compose的作用域,其次还有协程作用域之类的问题......
这个时候,直接让ViewModel根据号码是否合法去更新一个boolean变量,然后让UI根据这个变量来决定是否弹出就行了。虽然在ViewModel里面写一个检测是否合法的方法,让UI来调用也是一样,但是讲道理这部分逻辑应该是对UI完全屏蔽的。
回到原来的问题,其实就是因为这个源码里面弹出SnackBar的调用方是ViewModel,另外弹出Toast算一个公共的操作,所以提取到了StateHolder里面并以一种麻烦的方式实现了弹出。
谜题1解开了。