Skip to content

导航:Voyager/Decompose

Kotlin Multiplatform 项目中有多个导航库可选。Voyager 和 Decompose 是两个流行的解决方案。

Voyager (推荐新手)

源:Voyager Documentation

Voyager 是专为 Compose Multiplatform 设计的轻量级导航库,API 简洁易用。

依赖配置

toml
[versions]
voyager = "1.1.0-beta03"

[libraries]
voyager-navigator = { module = "cafe.adriel.voyager:voyager-navigator", version.ref = "voyager" }
voyager-tab-navigator = { module = "cafe.adriel.voyager:voyager-tab-navigator", version.ref = "voyager" }
voyager-transitions = { module = "cafe.adriel.voyager:voyager-transitions", version.ref = "voyager" }
voyager-koin = { module = "cafe.adriel.voyager:voyager-koin", version.ref = "voyager" }
kotlin
kotlin {
    sourceSets {
        commonMain.dependencies {
            implementation(libs.voyager.navigator)
            implementation(libs.voyager.tab.navigator)
            implementation(libs.voyager.transitions)
            implementation(libs.voyager.koin)  // 如果使用 Koin
        }
    }
}

最新版本查询:https://github.com/adrielcafe/voyager/releases

基础使用

kotlin
// commonMain
import cafe.adriel.voyager.core.screen.Screen
import cafe.adriel.voyager.navigator.Navigator
import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.currentOrThrow

// 定义 Screen
class HomeScreen : Screen {
    @Composable
    override fun Content() {
        val navigator = LocalNavigator.currentOrThrow
        
        Column {
            Text("Home Screen")
            Button(onClick = { navigator.push(DetailScreen("123")) }) {
                Text("Go to Detail")
            }
        }
    }
}

data class DetailScreen(val id: String) : Screen {
    @Composable
    override fun Content() {
        val navigator = LocalNavigator.currentOrThrow
        
        Column {
            Text("Detail: $id")
            Button(onClick = { navigator.pop() }) {
                Text("Back")
            }
        }
    }
}

// 应用入口
@Composable
fun App() {
    Navigator(HomeScreen())
}

Tab 导航

kotlin
import cafe.adriel.voyager.navigator.tab.*

// 定义 Tab
object HomeTab : Tab {
    override val options: TabOptions
        @Composable
        get() = TabOptions(
            index = 0u,
            title = "Home",
            icon = rememberVectorPainter(Icons.Default.Home)
        )
    
    @Composable
    override fun Content() {
        Text("Home Tab")
    }
}

object ProfileTab : Tab {
    override val options: TabOptions
        @Composable
        get() = TabOptions(
            index = 1u,
            title = "Profile",
            icon = rememberVectorPainter(Icons.Default.Person)
        )
    
    @Composable
    override fun Content() {
        Text("Profile Tab")
    }
}

// 使用 TabNavigator
@Composable
fun App() {
    TabNavigator(HomeTab) {
        Scaffold(
            bottomBar = {
                BottomNavigation {
                    TabNavigationItem(HomeTab)
                    TabNavigationItem(ProfileTab)
                }
            }
        ) {
            CurrentTab()
        }
    }
}

@Composable
fun RowScope.TabNavigationItem(tab: Tab) {
    val tabNavigator = LocalTabNavigator.current
    
    BottomNavigationItem(
        selected = tabNavigator.current == tab,
        onClick = { tabNavigator.current = tab },
        icon = { Icon(tab.options.icon!!, null) },
        label = { Text(tab.options.title) }
    )
}

过渡动画

kotlin
import cafe.adriel.voyager.transitions.SlideTransition

@Composable
fun App() {
    Navigator(HomeScreen()) { navigator ->
        SlideTransition(navigator)
    }
}

Koin 集成

kotlin
import cafe.adriel.voyager.koin.getScreenModel
import org.koin.core.component.KoinComponent

class UserScreen : Screen, KoinComponent {
    @Composable
    override fun Content() {
        val viewModel = getScreenModel<UserViewModel>()
        // 使用 ViewModel
    }
}

Decompose (推荐复杂项目)

源:Decompose Documentation

Decompose 是更强大的架构库,支持组件化和生命周期管理。

依赖配置

toml
[versions]
decompose = "3.2.0-beta02"

[libraries]
decompose = { module = "com.arkivanov.decompose:decompose", version.ref = "decompose" }
decompose-compose = { module = "com.arkivanov.decompose:extensions-compose", version.ref = "decompose" }

基础使用

kotlin
import com.arkivanov.decompose.ComponentContext
import com.arkivanov.decompose.router.stack.*
import com.arkivanov.decompose.value.Value

// 定义组件
class RootComponent(
    componentContext: ComponentContext
) : ComponentContext by componentContext {
    
    private val navigation = StackNavigation<Config>()
    
    val stack: Value<ChildStack<*, Child>> = childStack(
        source = navigation,
        serializer = Config.serializer(),
        initialConfiguration = Config.Home,
        handleBackButton = true,
        childFactory = ::child
    )
    
    private fun child(config: Config, componentContext: ComponentContext): Child {
        return when (config) {
            Config.Home -> Child.Home(HomeComponent(componentContext, ::onNavigateToDetail))
            is Config.Detail -> Child.Detail(DetailComponent(componentContext, config.id))
        }
    }
    
    private fun onNavigateToDetail(id: String) {
        navigation.push(Config.Detail(id))
    }
    
    @Serializable
    sealed class Config {
        @Serializable
        data object Home : Config()
        
        @Serializable
        data class Detail(val id: String) : Config()
    }
    
    sealed class Child {
        class Home(val component: HomeComponent) : Child()
        class Detail(val component: DetailComponent) : Child()
    }
}

// UI 层
@Composable
fun RootContent(component: RootComponent) {
    Children(stack = component.stack) {
        when (val child = it.instance) {
            is RootComponent.Child.Home -> HomeScreen(child.component)
            is RootComponent.Child.Detail -> DetailScreen(child.component)
        }
    }
}

最佳实践

✅ 实践 1:传递序列化参数

kotlin
// Voyager
@Serializable
data class DetailScreen(val userId: String, val name: String) : Screen

// Decompose
@Serializable
data class DetailConfig(val userId: String, val name: String) : Config

✅ 实践 2:处理返回结果

kotlin
// Voyager
navigator.push(DetailScreen { result ->
    // 处理返回值
})

// Decompose - 使用 StateFlow
class DetailComponent(
    private val onResult: (String) -> Unit
) {
    fun finish(result: String) {
        onResult(result)
    }
}

✅ 实践 3:深度链接处理

kotlin
// 解析深度链接并导航
fun handleDeepLink(url: String, navigator: Navigator) {
    when {
        url.contains("/user/") -> {
            val userId = url.substringAfterLast("/")
            navigator.push(UserScreen(userId))
        }
        url.contains("/post/") -> {
            val postId = url.substringAfterLast("/")
            navigator.push(PostScreen(postId))
        }
    }
}

库对比

特性VoyagerDecompose
学习曲线
生命周期管理简单完整
组件化架构⚠️
序列化支持
文档质量良好优秀
适用场景简单导航复杂应用

Voyager 适合快速开发,Decompose 适合需要严格架构的大型项目。根据项目复杂度选择。