Getting our apps ready for Jetpack Compose
作者:Joe Birch
自从我们发布完Jetpack Compose,和alpha版本框的发布,关注我们的开发者们已经迫不及待想要把Compose接入到自己的APP了。将你的项目迁移到Compose之后,你会发现从开发效率,应用稳定性和可维护性都会大大的提高,当然可能还有一些其他的一些效果,比如招聘方面(谁不想去一个用这些新技术的公司呢?)但是除了这些主题,我经常被问到的一个关于Compose的问题是:什么时候发布稳定版本?其实这也是我想知道的。。。当然正式版本不可能立马就能发布,我觉得我们现在应该做的是集中精力去看怎么让我们APP适配Compose。等真正release发布那天,如果我们想让我们的APP适配的更顺畅的话,我们还有很多事情要做。可能我们现在不会把我们整个应用全部迁移到Compose上(尽管很吸引人),我们准备的任何事情都会减少我们最终迁移可能的摩擦,同时也能提高我们的代码库。
注意:本文我会谈一谈Compose运行方式的基本原理。如果你还没开始探索Compose,也不知道它的工作原理,我建议你先看一下官方文档。
开始用声明式方式考虑UI
尽管我们还没开始用Compose支持的声明式UI,我们可以在脑子里尝试用声明式的方式来开发我们的APP了,声明式最重要的一个概念就是状态(state)——我们的UI组件是通过我们提供的数据组装(compose)起来的,而不是值改变的时候手动的去修改属性(setText,setVisibility 等)。单一数据源的状态概念会让状态管理更加容易,就好比UI页面的不同组件都引用的是同一个状态对象,而不是各个组件各自修改。
在声明式还没开始流行之前(之前的ViewModel和LiveData),我们还是习惯以不同的数据源分开设置View的属性。比如,一旦某个数据可用时就执行一个提交数据的操作,最终的结果会作用到某些View上面。随着调用的地方变多,一个屏幕上不同的部分就会变得很零碎,因为它们都是各自操作的。
这里有一个问题就是你的UI不可能只展示一种状态,由于这些独立的数据流构成了UI组件,很容易导致内容不能及时同步也会让其变得难以预测,因为数据可能在任何地方传进来。在Compose里面,这更是一个问题,因为你整个UI都会随着状态的改变都会重新渲染,所以当数据变化的时候同步机制就非常重要了,这样你才能保证其他UI会按预期的方式进行渲染。拥有单一数据源的状态不仅能保证这种情况,还能让Compose更好的关注到UI。
我们可以看到,现在我们只有一个状态引用,被屏幕上所有的操作控制着。然后这个状态引用去控制屏幕上所有的组件,而不是每个组件都各自维护一个状态。这个可以让这些组件是无状态的,可以简化我们的状态所代表内容的描述。这种方式对一个Composable的UI是很完美的,因为Composable就是用它提供的状态进行渲染,单一数据源意味着当我们需要重新渲染屏幕的时候我们还可以用相同的数据源,这样能保证我们的状态能正确的被反映到UI上面。
单向数据流
单项数据流这个主题本身完全可以单独写一篇文章,但是既然谈到了状态,我觉得有必要提一下这个概念——单向数据流跟单一数据源的关系也很紧密。可以通过收看David González这次演讲了解Android上的单向数据流
简单来说,单向数据流的目的限制数据流只能从一个方向流向屏幕的其他部分或者其他应用。那么关于view和数据之间的数据流,让我们看下用户点击按钮,被监听到后,触发了viewmodel里面的某个函数,然后发起一个repo的请求。请求返回来之后,修改我们viewmodel的状态,最终将返回结果显示到点击的按钮上面。在这里我们能看到数据流是循环的流动,当然实际的实际的实现跟使用的架构和框架有关,但是关键的在这个循环概念都是一样的。
这个对我们的UI组件同样适用——当我们的UI重新渲染,触发了按钮的状态只够,父组件接收到了新的状态会把这个状态传递给它的子组件——再说一次,保持我们的UI组件是无状态的,用来简化我们的状态描述。然后如果这些子组件想自己触发一些事件,比兔它们自己的按钮被点击了,那么这个事件会被传递给自己的父组件,然后触发整个事件循环。
基于这种限制,任何组件的交互其事件必须通过父组件触发,然后来控制状态,接着再更新UI。现在我们的UI状态更符合预期了,因为我们知道状态在哪,怎样被操作的。在Compose里面这个概念尤为关键。对于Jetpack Compose,Composable是通过状态以及其变化进行渲染的,因此可以通过单向数据流保证可预期性和有效性。在UI组件上坚持无状态的原则,可以让我们更容易执行自动化测试,因为我们只需要关注渲染和行为,而不需要关心状态管理。
解耦UI组件
Jetpack Compose值关注应用的UI渲染,也就是说它对应用相关的数据流,业务逻辑等其他东西不负责。目的就是为了实现轻量级的Composable,它跟项目里的其他东西是解耦的。按照这种说法,它其实跟我们现在应用里面希望我们的view类实现的目的是一样的,就算没有compose,这种思想也能解耦这些类,来提高项目的稳定性和可测试性。当真正要适配compose的时候,如果你的view跟compose的UI组件这种能力是一样的,或者说非常接近的话,会给你把这个组件切换到Composable减少很多麻烦。引入compose,我们有很多事情能做。
当你正在做一个现有的项目,一点点的重构都会让后期适配compose变得简单。作为一个联系,你可以快速看一个你APP里面的view组件,然后想想如果要转到compose上面,需要做哪些事情?如果啥也没有,那就太棒了。但是,大部分情况下,组件都是自己管理自己的状态,直接出发一个repo,展示一个dialog或者触发其他操作来修改屏幕显示的状态。因为这些能改变状态,但是如果你想继续了解本文的其他东西,这些都是必须要改变过来了。
尽管这些可以在迁移到compose的时候一次性的完成,但是会增加麻烦,而且会扩大影响范围——进而导致我们代码的退化。在此期间我们等做的是做一点小重构让后面的迁移更简单一点——这些小重构可以是把dialog移到父类里面,触发显示通过回调的形式,或者是把view状态的管理移到父类,改成父类往子类传递的形式。
当还没有采用compose之前新增view,一定要问下自己这个view的职责是什么。记住这一点,在制定技术规范和代码评审的时候,你可能会想到下面的一些问题:
- 这块业务逻辑应该包含在view内部吗?能不能把逻辑放到父类,然后数据通过参数传递进来呢?
- 状态能不能通过状态传递进来,而不是view自身来管理?
- 为什么事件触发不能通过监听的形式传递事件的流,而不是直接在view内部执行呢?
我们可以看到,这些示例问题跟本文上面提到的概念有直接的关系。可能还有一些其他东西我们能想到的,我这里只是举一些例子。重要的是当我们在写代码的时候能想到这些,后期要适配compose需要改变多少,现在我们能做些什么去减少将来适配compose的技术债?
不要强调每个细节
尽管我们在尝试解耦我们的UI组件并且开始思考compose的同等实现,但是如果你想一下子就能重新用compose做出来几乎是不可能,或者说你不知道能立马取得什么样的一个效果。由于compose和传统的view是可以互操作的,我们可以把现有的组件直接嵌套到我们的Composable UI里面。因此,如果你计划将一个非常漂亮复杂的UI迁移到compose的话,你可以一点一点的来改。这就比你必须把你面前的所有全部处理掉nice多了,有时候实际一点还是很重要的。只在某些地方采用compose对在团队内造势还是很好的,但是如果在你的APP里面需要增加UI互操作性,那我觉得还不如不接compose。
开始你的接入计划
记住所有上面提到的,我们知道该怎么去整理我们的代码来准备Jetpack Compose了。但是还是有必要给你的适配列一个计划,因为在可能在宏大目标下会迷失。如果你的项目比较大的话,更有可能发生。跟知道怎么把项目迁移到compose相比,知道我们APP现在是什么一种状况相对来说更难。话说回来,记得退一步以更小的模块去考虑问题。我们应该做的是从现在的一步一步的,走向我们能把compose用起来很舒服的时候。记住把UI转变成声明式只是compose迁移的一部分——如果没有本文提到的其他概念,适配compose会变得很难。
作为计划的一部分还应该包括,坚持单向数据流的原则,解耦UI组件,随着你现在开始适配的越多,后续compose到来的时候需要适配的工作量会很少。当你在review一个pr或者是给一个需求准备技术文档的时候,尽量确保你的这次合并不会给未来的适配工作加工作量。尽管这是我们一直努力在做的事情,但是声明式的思想改变的东西并不多。如果有了这种思想,同时在你的APP管理状态和数据了的时候有些某种形式的标准,在你的代码里面引入compose会变得更加自然。
用compose来做UI只是拼图里面的一小块,你还需要考虑很多事情,比如在你的团队内树立上面的这些概念,compose的框架以及引入的一些其他变动(比如给Composable写单元测试)。
准备适配minSdk=21(Lollipop)
Jetpack Compose要求最低sdk版本是21,也就是Android Lollipop。但是我们已经发布了很多版本,可能还有一部分用户的设备低于这个版本,并且也有APP是支持低版本使用的。但是不管怎么说,如果没有太多适配工作,这个都有可能阻塞你去适配compose。因为还有一点时间才会发布稳定的版本,现在你就可以看下你还在支持低于21的版本吗。
- 为什么我们现在还要支持21以下的版本?是否有业务需求,还是说只是我们很久没更新了?
- 低于21版本的用户有多少?我们知道了解这些用户吗,这些用户在我们APP上的行为又是怎样的?他们是不是我们的目标用户
- 有没有其他问题是因为21之前的设备引起的,我们现在知道的或者计划去处理的?
- 迁移到compose带来的收益能不能超过我们支持21以前版本?
- 如果我们升到21,我们现在应该做些什么能保证那些不能升级的用户继续使用我们APP?
适配低版本的Android用户会给开发和测试过程中造成一些困难。有时候我们不能用一些库,或者遇到一些只在低版本遇到的问题。这些不仅会影响开发的产出,还会影响整个团队的输出。记住这一点,考虑下为什么还需要支持,质疑有些东西是不是还有必要。如果已经决定更新最低版本,你就可以开始准备你还有哪些工作区确保那些不能更新的用户还能继续使用(比如修复一些严重的bug,在升级之前确保还有一个稳定版本)。尽快的升级最新的版本,也会让后期迁移到compose更容易一点。
在你的团队内造势
因为Jetpack Compose不仅需要我们在开发UI思维方式上有一个转变,还有一系列新的API需要我们去学习和理解。尽管框架现在还是处于alpha状态并且有些东西可能会有一点变化,但是compose的概念和调用的方式基本上不会变了,因为声明式UI也基本上就这样了。也正是因为这样,现在去调研compose不会浪费你的时间。
想为了在团队内使用compose进一步造势。可能你还需要在团队内做一个简短的关于这些概念的宣讲,甚至举办一些类似“黑客日”让大家亲手去学习和使用Compose。因为Compose将会是我们未来开发APP的方式,所以这一切对于公司都是值得的。比如,Snapp 移动已经举办了好几次这种“黑客日”,让大家相互协作一起探索Compose。因为Compose短时间内可能还不会发布稳定的版本,所以举办类似这些活动,能团队逐渐的适应声明式UI并了解Compose这个框架。
在Compose Academy,我们持续更新发布了很多案例,能让你的团队更快的适应那个框架。如果你们团队感兴趣的hauler可以和我们联系。
保持耐心
还有很重要的一点就是,对于新的这种新构建UI的方式保持耐心。尽管新的框架让人感到兴奋,但是传统的UI不可能马上就消失。所以我们有充足的时间去适配compose。另外对于新的东西也不能说就有压倒性优势,你不可能把现有的代码立马全部迁移到compose上去。而且你也不可能一开始就在每个需求都用compose。不仅是因为有些东西compose不可能立马去适配,还有有一些更高优先级的需要对客户产生价值的东西去实现。
尽管compose最终会成为我们构建Android UI的标准方式,但是这个过程不是冲刺而是一个马拉松,所以不要急,尽情享受这个过程吧。
当然准备Jetpack Compose我们有好多事情需要去做,上面只是我能想到一些内容作了给大家作了个讲解,也是一些我们需要适配compose的一些关键所在。你是否在考虑让你的团队使用Compose,或者说你是否在学习一些东西让你团队走向一个正确的方向?如果是的我很开心能收到你们的回复。