📝

Android Basic with ComposeのUnit4で学んだことをまとめてみる - Navigation

2024/12/09に公開

はじめに

こんにちは、某SIerでSEをやっているnekorush14です。
この記事は絶賛再*n入門しているAndroid開発について、Google公式のLearning Courseを通して学んだことをアウトプットするシリーズです。

Jetpack Composeに関するCourseのUnit4で得た知見を話します。
https://developer.android.com/courses/android-basics-compose/unit-4?hl=en

前回の記事はこちら👇
https://zenn.dev/nekorush14/articles/7323abd686c266

今回はUnit4で2つ目のテーマとなるNavigationについてまとめます。

  • Jetpack ComposeのNavigationはNavigationComponentを使用する

  • Navigation Componentには以下の要素が含まれる

    • NavController
      • スクリーン間のナビゲーションを担当する
    • NavGraph
      • スクリーンで使用するComposableの関係ををマッピングする
    • NavHost
      • 現在のComposableを表示するためのContainerとして機能するComposable
  • アプリケーション内の画面数は有限であり、そのルートの数も有限

    • enum classを使用して定義する
  • NavHostnavControllerstartDestinationmodifierを引数に取る

    • navControllerrememberNavController()を使用してNavHostControllerを取得できる

    • startDestinationはアプリ起動時に表示するScreenとなる

      enum class SampleScreen(@StringRes val title: Int) {
          Start(title = R.string.app_name)
      }
      
      Scaffold (
          // ...
      ) { innerPadding ->
          val navController = rememberNavController()
      
          NavHost(
            navController = navController,
            startDestination = SampleScreen.Start.Name,
            modifier = Modifier.padding(innerPadding)
          ) {
              // ナビゲーション先で表示するComposableを定義する
          }
      }
      
  • NavHostのコンテンツ関数内で表示する画面のComposableを指定する

    • composable()関数を使用して指定する

      • composable()routeenum classnameを指定してルーティングする
      enum class SampleScreen(@StringRes val title: Int) {
            Start(title = R.string.app_name)
        }
      
        Scaffold (
            // ...
        ) { innerPadding ->
            val navController = rememberNavController()
      
            NavHost(
              navController = navController,
              startDestination = SampleScreen.Start.Name,
              modifier = Modifier.padding(innerPadding)
            ) {
                // ナビゲーションのルーティングを定義する
                composable(route = SampleScreen.Start.name) {
                    // 画面に表示するComposable本体を定義する
                    SampleScreen()
                }
            }
        }
      
  • LocalContext.currentの参照

    • ContextはAndroidシステムが提供する抽象クラス

    • アプリケーション固有のリソース・クラスへのアクセス、Activityの起動などのコールが可能となる

      // 現在実行中のアプリにおけるコンテキストを取得する
      val context = LocalContext.current
      
      // コンテキストの中から指定したリソースIDを持つ文字列を取得する
      val sampleText = context.resources.getString(R.strings.sample_text)
      
  • navControllerを使う際、navControllerのオブジェクトを渡すのではなくnavController.navigate()をもつLambdaを関数ハンドラとして渡す

  • 前の画面に戻る場合にはnavController.popBackStackを使用する

    • 引数はrouteinclusive

      • route: 戻る先のルート文字列
      • inclusive: 指定したルートもBackStackから削除するかを示すBool値、falseの場合、指定したルートは削除せずに他のルートをBackStackから削除する
      @Composable
      fun SampleApp(
          viewModel: OrderViewModel = viewModel(),
          navController: NavHostController = rememberNavController()
      ) {
            // 引数で渡されたnavControllerのBackStackEntryを状態として保持する
            val backStackEntry by navController.currentBackStackEntryAsState()
            
            // 現在の画面をBackStackEntryから取得する
            // ここでは、BackStackEntryが何も無い場合に
            // Startで定義される画面を現在の画面とする
            val currentScreen = SampleScreen.valueOf(
                backStackEntry?.destination?.route ?: SampleScreen.Start.name
            )
      
            Scaffold(
                topBar = {
                    SampleAppBar(
                        currentScreen = currentScreen,
                        canNavigateBack = navController.previousBackStackEntry != null,
                        navigateUp = { navController.navigateUp() } // TopBarの左上に表示される戻るボタンが押されたときのコールバック
                    )
                }
            ) {
                // ナビゲーションのルーティングを定義する
                composable(route = SampleScreen.Start.name) {
                    // 画面に表示するComposable本体を定義する
                    SampleScreen(
                        // ...
                        onNextButtonClicked = { // ここでnavController自体は渡さない
                            viewModel.setQuantity(it)
                            // 引数に指定した画面へ繊維する
                            navController.navigate(SampleScreen.Detail.name)
                        },
                        modifier = Modifier
                            .fillMaxSize()
                            .padding(dimensionResource(R.dimen.padding_medium))
                    )
                }
            }
      }
      
  • ShareSheetなど別のアプリなどにナビゲートする場合はnavControllerではなくintentを使用する

  • NavHostを使用せずに、if/else等によって分岐する場合、自前でバックボタンの挙動を実装する

    • androidx.activity.compose.BackHandlerを使用する

      • BackHandler {}内でConfiguration Changeが発生し、ReCompositionが発生するようなアクションを実行する
      import androidx.activity.compose.BackHandler
      
      @Composable
      fun SampleHomeScreen(
          sampleUiState: SampleUiState,
          onTabPressed: (SampleType) -> Unit,
          onSampleCardPressed: (Int) -> Unit,
          onDetailScreenBackPressed: () -> Unit,
          modifier: Modifier = Modifier
      ) {
      
          // ...
      
          // ここでは、UiStateが持つフラグで表示内容を変化させる
          if (sampleUiState.isShowingHomepage) {
              SampleAppContent(
                  sampleUiState = sampleUiState,
                  onTabPressed = onTabPressed,
                  onSampleCardPressed = onSampleCardPressed,
                  navigationItemContentList = navigationItemContentList,
                  modifier = modifier
      
              )
          } else {
              SampleDetailsScreen(
                  sampleUiState = sampleUiState,
                  onBackPressed = onDetailScreenBackPressed,
                  modifier = modifier
              )
          }
      }
      
      @Composable
      fun SampleDetailScreen(
          sampleUiState: SampleUiState,
          onBackPressed: () -> Unit,
          modifier: Modifier = Modifier
      ) {
          // BackHandlerを使用して自前の"戻る"処理を定義する
          BackHandler {
              onBackPressed() // 引数で渡された"戻る"処理を実行する
          }
      
          // ...
      }
      
      // ...
      
      

まとめ

今回はAndroidのNavigationについて簡単にまとめてみました。
思っていたよりもシンプルに実装できることがわかったので、今後のアプリ制作などでも活用していきたいです。
また、NavHostを使用せずにwhenなどの分岐で表示するComposableを変更するなど、いくつか実装方法があることを学びました。
実装するアプリによって適切なNavigationの実装は変化すると感じました。

次回はAdaptive Layoutの話をします。

参考資料

Discussion