Jetpack Composeを使ってみたい⑤ Navigation

Jetpack Composeを使ってみたい!と思ってCodelabを元に勉強してみた記録。
Jetpack Compose for Android Developers

JetpackComposeでNavigationコンポーネントを使って画面遷移を実装する。

準備

アプリレベルのbuild.gradleに依存関係を追加。

dependencies {
    def nav_version = "2.5.2"
    implementation "androidx.navigation:navigation-compose:$nav_version"
}

基本的なNavigation

アプリ内の画面を構成するコンポーザブルのバックスタックと各画面の状態を追跡するNavControllerを作成する。
他のコンポーザブルから参照できるよう上位のコンポーザブルに定義する。

val navController = rememberNavController()

ナビゲーショングラフを関連付けるためのコンテナであるNavHostをNavControllerに関連付ける。
NavHostにはNavControllerと最初の画面になるコンポーザブルのルートを渡す。
(ルートはコンポーザブルへのパスを定義するStringで、コンポーザブルごとに一意)
ナビゲーショングラフには目的地となるコンポーザブルを指定する。

NavHost(navController = navController, startDestination = "profile") {
    composable(route = "profile") { Profile(/*...*/) }
    composable(route = "friendslist") { FriendsList(/*...*/) }
    /*...*/
}

//目的地になるコンポーザブルは別で作成
@Composable
fun Profile(){
}
@Composable
fun FriendList(){
}

目的地に移動するときは各コンポーザブルに設定したルートを使用する。

navController.navigate("friendslist")

引数を使用したNavigation

コンポーザブルに引数を渡したい時は、ルートにプレースホルダーを追加する。
引数のタイプはデフォルトで文字列になっているが、argumentsを使用して明確に指定できる。
引数の値は、composable() 関数のラムダで使用可能なNavBackStackEntry から抽出する。

composable(
    "profile/{userId}",
    arguments = listOf(navArgument("userId") { type = NavType.StringType })
){ backStackEntry ->
    Profile(backStackEntry.arguments?.getString("userId"))
}

//引数を受け取るコンポーザブル
@Composable
fun FriendList(userId: String){
}

サンプルアプリ

HomeScreen ⇄ NextScreen の2つのScreenを遷移するアプリ。
HomeScreenはTextFieldに入力された文字を引数としてNextScreenに渡し、
NextScreenは受け取った文字列を表示する。

画面を管理するenum class
enum class NavigationScreen(){
    Home,
    Next
}
@Composable
fun NavigationApp() {
    val navController = rememberNavController()

    val backStackEntry by navController.currentBackStackEntryAsState()

    Scaffold(
        topBar = {
            //この後アプリバーを作成
            MyAppBar(
                canNavigateBack = backStackEntry?.destination?.route != NavigationScreen.Home.name,
                onClick = { navController.popBackStack() }
            )
        }
    ) { innerPadding ->
        NavHost(
            navController = navController,
            startDestination = NavigationScreen.Home.name,
            modifier = Modifier.padding(innerPadding)
        ) {
            composable(NavigationScreen.Home.name) {
                HomeScreen(
                    //NavContorollerを直接渡さず、コールバックで渡すのが良さそう
                    onClick = { navController.navigate("${NavigationScreen.Next.name}/$it") }
                )
            }
            composable(
                "${NavigationScreen.Next.name}/{text}",
                arguments = listOf(navArgument("text") { type = NavType.StringType })
            ) {
                NextScreen(it.arguments?.getString("text").toString())
            }
        }
    }
}
アプリバー
@Composable
fun MyAppBar(
    canNavigateBack: Boolean,
    onClick:() -> Unit
){

    //currentDestinationがHomeScreenじゃなければアプリバーに戻るボタンを表示
    val navigationIcon: (@Composable () -> Unit)? = {
        if (canNavigateBack) {
            IconButton(onClick = onClick) {
                Icon(
                    imageVector = Icons.Outlined.ArrowBack,
                    contentDescription = "Back"
                )
            }
        } else {
            null
        }
    }

    TopAppBar(
        title = { Text(text = stringResource(id = R.string.app_name)) },
        navigationIcon = navigationIcon
    )
}
HomeScreen
@Composable
fun HomeScreen(
    onClick: (String) -> Unit
) {
    var name by remember { mutableStateOf("") }

    Column(
        modifier = Modifier
            .fillMaxSize()
            .padding(8.dp),
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Text(text = "Welcome Home!")

        Spacer(modifier = Modifier.padding(16.dp))

        OutlinedTextField(
            value = name,
            onValueChange = { name = it },
            label = {
                Text(text = "Enter your name")
            },
            singleLine = true
        )
        Button(
            onClick = { onClick(name) }
        ) {
            Text(text = "Next")
        }
    }
}
NextScreen
@Composable
fun NextScreen(text: String) {
    Column(
        modifier = Modifier
            .fillMaxSize()
            .padding(8.dp),
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Text(text = "Hello, $text")
    }
}


参考
Jetpack Compose Navigation  |  Android Developers
Compose を使用したナビゲーション  |  Jetpack Compose  |  Android Developers


前の記事
mtnmr.hatenablog.com