如何基于Compose Destinations Library实现Compose项目的优雅导航?
优化Compose Destinations下ViewModel与Composable的导航通信
我在开发纯Compose项目时,总觉得ViewModel和Composable之间的导航逻辑耦合性太高,不够优雅。理想状态是让ViewModel直接决定跳转目标(比如调用navManager.navigate(LoginDestination)),而我用的是Compose Destinations库。找了不少示例仓库,但都没找到适配这个库的优雅实现,自己写了一套方案,不过还想再简化。
我当前的实现代码如下:
@Composable fun DestinationWithNav( navState: StateFlow<StateEventWithContent<NavigationState>>, navigator: DestinationsNavigator, consume: () -> Unit, screen: @Composable () -> Unit ) { val state by navState.collectAsStateWithLifecycle() EventEffect(event = state, onConsumed = consume) { navigator.navigate( direction = it.direction, builder = it.builder ) } screen() }
下面是更简洁的优化方案,核心是把导航逻辑抽象到ViewModel基类,用事件流处理一次性导航动作:
1. 定义导航事件密封类
用密封类统一管理所有导航动作类型:
sealed class NavEvent { data class NavigateTo(val direction: Direction) : NavEvent() data class NavigateToWithArgs(val direction: Direction, val args: NavArgs) : NavEvent() object NavigateUp : NavEvent() }
2. 封装导航ViewModel基类
把导航逻辑封装到基类,让业务ViewModel直接继承即可复用:
import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.launch abstract class BaseNavViewModel : ViewModel() { private val _navEvents = MutableSharedFlow<NavEvent>() val navEvents = _navEvents.asSharedFlow() protected fun navigateTo(direction: Direction) { viewModelScope.launch { _navEvents.emit(NavEvent.NavigateTo(direction)) } } protected fun navigateTo(direction: Direction, args: NavArgs) { viewModelScope.launch { _navEvents.emit(NavEvent.NavigateToWithArgs(direction, args)) } } protected fun navigateUp() { viewModelScope.launch { _navEvents.emit(NavEvent.NavigateUp) } } }
3. 编写导航事件处理扩展函数
用Compose的LaunchedEffect处理事件流,替代自定义的EventEffect:
import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import com.ramcosta.composedestinations.navigation.DestinationsNavigator @Composable fun handleNavEvents(navEvents: SharedFlow<NavEvent>, navigator: DestinationsNavigator) { LaunchedEffect(navigator) { navEvents.collect { event -> when (event) { is NavEvent.NavigateTo -> navigator.navigate(event.direction) is NavEvent.NavigateToWithArgs -> navigator.navigate(event.direction, event.args) NavEvent.NavigateUp -> navigator.navigateUp() } } } }
4. 在业务屏幕中使用
ViewModel中直接调用导航方法,屏幕Composable只需一行代码处理事件:
@Destination @Composable fun HomeScreen( viewModel: HomeViewModel = hiltViewModel(), navigator: DestinationsNavigator ) { // 处理导航事件 handleNavEvents(viewModel.navEvents, navigator) // 屏幕UI内容 Button(onClick = { viewModel.navigateToLogin() }) { Text("跳转至登录页") } } // 业务ViewModel class HomeViewModel : BaseNavViewModel() { fun navigateToLogin() { // 直接调用基类方法,无需关心Composable层的导航实现 navigateTo(LoginDestination) } }
优化点说明
- 用
SharedFlow替代StateFlow:导航事件是一次性动作,不需要保留状态,SharedFlow更适合这类事件流场景 - 抽象基类减少重复:所有需要导航的ViewModel只需继承
BaseNavViewModel,不用重复写事件发送逻辑 - 简化Composable层:屏幕无需嵌套
DestinationWithNav,只需调用扩展函数即可处理导航,代码更清爽 - 符合Compose生命周期:用
LaunchedEffect收集事件,自动跟随Composable生命周期管理协程
内容的提问来源于stack exchange,提问作者Teo Vladušić
相关产品推荐
相关产品推荐

