作者: Manuel Vivo(开发者关系工程师)
这篇文章的原文可以在
这里找到
。
为了帮助您使我们的应用架构指南现代化,我们想看看哪些不同的 UI 模式最有效,确定并总结不同替代方案之间的相似之处和不同之处,并作为建议向您展示。
为了便于说明我们的发现,我们需要一个不太复杂的熟悉业务案例示例。所以......谁不知道如何制作一个TODO 应用程序,对吧?我们选择的应用是Architecture Blueprints 。蓝图是一个很好的选择,因为它们传统上是试验多种架构的好地方。

正在运行的架构蓝图应用程序
最近,API 的使用对 UI 模式产生了巨大的影响。其中值得注意的是Jetpack Compose 的状态 API 。 Compose 可与所有单向数据流模式无缝协作,因此我使用 Compose 呈现 UI 以进行客观比较。
在这篇博文中,我将向您展示我如何将架构蓝图迁移到 Jetpack Compose。 对于使用LiveData的用户,迁移时的样例保持原样, 在重构中未修改 ViewModel 类和数据层。
此基于 LiveData 的代码库中使用的架构并未完全遵循现代架构最佳实践。特别是,您不应在数据层或领域层使用LiveData,而应使用流和协程。
现在有一个警告,让我们更深入地研究将蓝图重构为 Jetpack Compose 的过程。您可以在dev-compose 分支中看到完整的代码。
渐进式迁移计划
在实际编写代码之前,我制定了迁移计划以避免团队成员之间的混淆。最终目标是使用 composable 使 Blueprints 成为具有屏幕的单个活动应用程序,并使用推荐的Compose Navigation 库在屏幕之间导航。
幸运的是,Blueprints 已经是一个单一的活动应用程序,它使用Jetpack Navigation 在作为片段实现的不同屏幕之间导航。为了迁移到 Compose,我们遵循了推荐混合应用程序的 Navigation Compatibility Guide ,使用基于 Fragment 的导航组件,并实现了基于 View 的屏幕、Compose 屏幕以及同时使用 Views 和 Compose with Fragments 的屏幕。不幸的是,您不能在同一个导航图中混合使用 Fragment 和 Compose 目标。
逐步迁移的目标是在整个迁移过程中促进代码审查并将产品保持在可发布状态。迁移计划如下:
- 将每个屏幕的内容迁移到 Compose。每个屏幕,包括 UI 测试,都可以单独迁移。 Fragment 然后成为每个迁移屏幕的容器/主机。
- 将您的应用程序迁移到 Navigation Compose 以从您的项目中删除所有片段并将您的 Activity UI 逻辑迁移到根 compose。在此阶段还会迁移端到端测试。
- 删除 View 系统依赖项。
这就是我们所做的!回顾
2 周的快进
,我们迁移了统计屏幕 ( PR )、 添加/编辑操作屏幕 ( PR )、 操作详细信息屏幕 ( PR )、 操作屏幕 ( PR ) 和导航并且在将 Activity 逻辑迁移到 Compose 之后,我合并了最终的 PR , 甚至删除了未使用的 View 系统依赖项。

如何逐步将蓝图迁移到 Compose
迁移亮点
在迁移过程中,我们注意到 Compose 的一些显着异常。
用户界面测试
当您开始将 Compose 添加到您的应用程序时,断言 Compose UI的测试应该使用Compose 测试 API 。
在我们的UI 测试中,我们使用了createAndroidComposeRule<ComponentActivity> API,而不是launchFragmentInContainer<FragmentType> API,它允许测试获取字符串资源。 该测试可以在 Espresso 和 Robolectric 上运行。 Compose 已经支持所有这些,因此不需要进一步的更改。例如,比较迁移到AddEditTaskScreenTest的AddEditTaskFragmentTest中的代码。但是,如果您使用的是 ComponentActivity,则必须依赖androidx.compose.ui:ui-test-manifest 工件。
端到端测试和集成测试都没有发现任何问题!由于Espresso 与 Compose 的兼容性,我们使用 Espresso 断言来验证视图并使用 Compose API 来验证 Compose UI。 查看AppNavigationTest 在迁移到 Compose 的过程中是如何实现的。
ViewModel 事件
蓝图中未正确处理ViewModel 事件。 蓝图使用事件包装器解决方案,将命令从 ViewModel 发送到 UI ,这在 Compose 中效果不佳。我们将这些“事件”建模为迁移期间的状态,并且我们也将它们作为建议包含在我们最近的指南中。
通过查看Display Message on Screen事件用例, 我们将 LiveData 的 Event<Int> 类型替换为 Int?。此格式还模拟了没有要显示的消息的场景。对于这个特定的用例,您需要在显示消息时通过 UI 检查 ViewModel。请在下面的代码中查看这两种方法之间的区别。

乍一看,这似乎需要更多的工作,但这样您就可以毫无例外地将您的消息显示在屏幕上!
如果您希望事件在您的 UI 代码中只处理一次,有一种方法可以调用 event.getContentIfNotHandled()。这种方法在 Fragment 中工作得很好, 但在Compose 中完全没用!在 Compose 中随时可能发生重组,因此事件包装器不是有效的解决方案。当事件被处理并且函数被重建(在测试这个方法时经常发生),snackbar 被取消并且用户可能会错过消息。这是一个永远不应该发生的用户体验问题! 您不得在 Compose 应用程序中使用事件包装器解决方案。
在某些情况下,可以编写不重新组织某些功能的 Compose 代码,但在这种情况下,事件包装器解决方案可能会限制您的 UI 实现。 Compose 不建议使用事件包装器解决方案。
比较下面代码片段中的之前(事件包装器)和之后(将事件建模为状态)代码。 在屏幕上显示消息是UI 逻辑,屏幕组合变得越来越复杂,因此我们使用通用状态保持器类简化了代码(例如,参见AddEditTaskState )。

有疑问时选择应用程序的准确性
在重构时,您可能希望将所有内容迁移到 Compose。 当然,这很好,但不应损害用户体验或应用程序的准确性。逐步迁移的关键是让您的应用始终准备好发布。
在将一些屏幕迁移到 Compose 时,我们也有同样的冲动。但是,我不能同时做太多事情,所以在从事件包装器迁移之前,我将一些屏幕迁移到 Compose 。在 Compose 中使用事件包装器无法为用户提供最佳体验,因此我将其余代码留在 Compose 中,并继续在 Fragment 中处理这些消息。例如,查看迁移期间 TasksFragment的状态。
挑战
并非一切都像看起来那样顺利。
将 Fragment 内容转换为 Compose 很简单,但是从 Navigation Fragment 迁移到 Navigation Compose 需要相当长的时间,并且需要考虑更多的事情。
也有声音认为该指南需要通过多种方式进行扩展和改进,以便将来更容易迁移到 Compose。这项工作引发了讨论,所以希望新的指南很快就会出来! 
作为 Navigation 的新手和做过
Navigation Compose 迁移的人,我遇到了以下问题。
- 文档中没有代码显示如何使用可选参数进行导航! 最后,感谢Tivi 的 Navigation graph ,我得到了线索并解决了问题(您可以在此处查看问题并提供反馈)。
- 从基于 XML 的导航图和 SafeArgs 迁移到 Kotlin DSL 是一项简单而机械的任务。但是, 对于没有参与之前实施的我来说,这并不是一件容易的事。如果在此任务上有正确的指南会非常有帮助(您可以在此处检查问题并提供反馈)。
- 这个问题比直接的挑战更容易被忽视!尽管NavigationUI 已经为您处理了一些与导航相关的任务, 但 Compose 中不提供此 UI,因此您必须在关注问题的同时手动处理导航。例如,在 Drawer 屏幕之间移动时,需要特殊的 NavigationOptions 来保持后台堆栈的有序性(请参见此处的示例)。这已经在文档中进行了介绍, 但值得关注!
结论
从 Navigation Fragment 迁移到 Navigation Compose 很有趣!与实际迁移项目相比,花更多时间获得同行评审也是一种意外的乐趣。通过规划迁移并确保每个人都熟悉它,我们能够尽早设定期望并获得大量评论。
我们希望您喜欢阅读我们的 Compose 迁移挑战,我们很高兴能分享我们将来在架构蓝图中进行的一些实验和改进。
如果您想查看使用 Compose 代码实现的蓝图, 请查看dev-composebranch 。 如果你想看渐进式迁移的 PR,请看下面的列表。
- 统计屏幕( PR ),
- 添加/编辑操作画面 ( PR ),
- 工作详情屏幕( PR );
- 工作屏幕( PR ),
- 将 Navigation 和 Activity 逻辑迁移到Compose后, 最终PR .

评论
发表评论