Compose单Activity架构的设计与实现
随着Compose技术的普及,项目中使用Compose的业务模块也越来越多。我们都知道在“XML时代”,Google提倡使用单Activity架构即:单个Activity+多个Fragment,使用Navigation来管理各页面之间的路由关系。
单Activity架构这个“单”是相对的,可以在一个业务模块中做到单Activity,也可以在一个独立功能中做到单Activity。
如果你正在做一个全新的业务模块或者一个全新的功能,并且不仅只有一个页面,建议你考虑使用Compose下的单Activity架构,即单个Activity+多个ComposeView,使用Compose Navigation来管理各页面之间的路由关系。
我们不可能让一个Compose页面对应一个Activity入口或者再用多fragment那一套去包裹一层。
Compose下实现单Activity架构的核心点是Compose Navigation,所以我们需要了解Navigation的用法。
我们以A、B两个Compose页面为例,使用Compose编写UI时,我们都知道这样一段代码:
composeView.setContent {RainbowTheme() {Surface(color = Color.Transparent, modifier = Modifier.fillMaxSize()) {A()}}}
此时页面中展示的就是A Compose页面的内容,在A Compose函数中有一个点击事件跳转到B Compose页面。
@ComposablefunA() { Button(onClick = { }) { Text("跳转到B") }}
@ComposablefunB() { Text("B页面")}
首先我们来定义一个NavHost,代码如下所示:
@ComposablefunNavHostDemo() {val navController = rememberNavController()NavHost(navController = navController, startDestination = xxx) {}}
NavHost 中的startDestination为起始路由地址,设置为A的路由地址就默认显示A页面,设置为B的路由地址就默认显示B页面,与XML中startDestination用法一致。可以根据业务需求来动态修改起始路由。
将所有Compose页面都定义在NavHost中,并且每个页面都持有navController,方便在各自的页面执行跳转事件。
@ComposablefunNavHostDemo() {val navController = rememberNavController()NavHost(navController = navController, startDestination = A的路由地址) {composable(A的路由地址) {A(navController)}composable(B的路由地址) {B(navController)}}}
路由地址是字符串格式的,我们可以定义在单独的配置文件中。我们将定义NavHost的方法放到执行Compose的起始位置,默认显示的就是起始路由地址对应的Compose页面。
composeView.setContent {RainbowTheme() {Surface(color = Color.Transparent, modifier = Modifier.fillMaxSize()) {A() -> NavHostDemo()}}}
此时A、B两个Compose页面都持有一个navController,借助navController我们可以执行跳转事件,这里不详细的介绍navController的用法,我们只关注当前我们能用到的两个核心方法:
跳转到xxx页面:navController.navigate(xxxx)
返回上一页面:navController.popBackStack()
更多使用方法,感兴趣的可以看我很久之前分享过的在Compose中使用Navigation导航,看这一篇就够了。
所以,如果我们现在想在A页面中跳转到B页面,只需要添加这一行代码即可:
@ComposablefunA(navController:NavController) { Button(onClick = { navController.navigate(B的路由地址) }) { Text("跳转到B") }}
这样我们就完成了在单Activity架构中从A Compose页面跳转到B Compose页面的功能,但每个Compose函数都持有一个navController并不是最好的方式。
假如我们有A、B、C、D四个Compose页面,A和D页面都需要跳转到B页面,这个时候在A和D的方法中都会有这样一行代码:
navController.navigate(B的路由地址)
如果以后需要添加对应的传参数或者更改跳转到B页面的行为,也需要在每个调用处都修改,并且不同的研发可能会写出相同的功能不同的方法,造成代码重复。
我们可以将所有的路由方法都封装在Action中,让每个Compose函数都持有一个Action。
以具体需求的实现为例,创建一个Action对象,在Action中定义相关方法,代码如下所示:
class MessengerAction(private val navController: NavController, activity: Activity) { /** * 进入sms页面 */ val enterSmsPage: () -> Unit = { navController.navigate(MessengerRouteConfig.SEND_SMS_MAIN_PAGE) } /** * 退出activity */ val finishActivity: () -> Unit = { activity.finish() }}
在定义NavHost的地方创建Action,同时让所有Compose方法都持有这个Action。
@ComposablefunMessengerNavHost( startDestination: String intent: Intent) { val navController = rememberNavController() val messengerAction = MessengerAction(navController, this) NavHost(navController = navController, startDestination) { //主页面 composable(MESSENGER_MAIN_PAGE) { MessengerView(messengerAction) } //发送短信主页面 composable(SEND_SMS_MAIN_PAGE) { SmsMainPage(messengerAction) } }}
如果在MessengerView中想跳转到SmsMainPage页面只需要执行下列代码:
messengerAction.enterSmsPage()
我们也可以根据具体业务拆分成多个Action,确保职责单一。
我们已经知道在单独的业务模块中可以定义NavHost来进行路由管理,那如果一个全新的轻量级项目,就一个Activity,或者多个业务模块共享同一个Activity,各个moudle之间的路由怎么关联起来呢?
我们只需要在声明模块NavHost的时候声明为NavGraphBuilder.扩展方法即可。以我很久之前的一个开源项目为例,
在两个module中分别声明各自模块的navhost。
然后在app主模块内集中声明各个模块的navhost即可。
如果一个全新的业务模块使用Compose来实现,我想Compose-单Activity架构是必要的,因为除此之外,我们只能选择一个Activity承载一个Compose页面,或者一个Activity多个Fragment来包裹每个Compose页面。这是与我们使用Compose的初衷是相违背的。