アプリの基本的な画面です。

シンプルなもので眺めてみます。
■ 構成
「Column」を使って2つのパーツを縦に並べます。

メイン画面を表示する部分は
「NavHost」、
それを表示切り替えする下側に並んだ操作部分は
「BottomNavigation」
です。
コードは以下のように構成できます。
@Composable
fun MainScreen() {
  val navController = rememberNavController()
  Column {
    NavHost(
      navController = navController,
    ) {
      composable(NavigationItem.HOME.route) {
        HomeScreen()
      }
      composable(NavigationItem.CRYPT.route) {
        CryptScreen()
      }
      // ...
    }
    BottomNavigation {
      val backStack by navController.currentBackStackEntryAsState()
      val current = backStack?.destination?.route
      NavigationItem.values().forEach { item ->
        BottomNavigationItem(
          // ...
        )
      }
    }
  }
}
enum class NavigationItem(
  // ...
)
■ NavController
NavHost を BottomNavigation を紐付けるコントローラーです。
val navController = rememberNavController()
両方からアクセスしやすい場所に置くことになります。
■ NavigationItem
BottomNavigation に並べるアイテムの情報をまとめておきます。
以下、3つの組み合わせになります。
- 画面を表す内部文字列(遷移先)
- アイコン画像
- 表示する画面名称文字列
すべて定数なので enum クラスで作成します。
enum class NavigationItem(
  val route: String,
  val icon: ImageVector,
  @StringRes val label: Int,
) {
  HOME("home", Icons.Outlined.Home, R.string.nav_label_home),
  CRYPT("crypt", Icons.Outlined.MonetizationOn, R.string.nav_label_crypt),
  // ...
}
ここでは多言語化を考慮して、
遷移を表す内部で使用する文字列と、画面名称を表示する文字列を区別しています。
必要なければまとめてもいいと思います。
■ BottomNavigation
NavigationItem を BottomNavigation に配置します。
BottomNavigation {
  val backStack by navController.currentBackStackEntryAsState()
  val current = backStack?.destination?.route
  NavigationItem.values().forEach { item ->
    BottomNavigationItem(
      selected = current == item.route,
      onClick = {
        navController.navigate(item.route) {
          // no back stacks. 0 is the root navigation
          popUpTo(id = 0) {  
            saveState = true
          }
          launchSingleTop = true
          restoreState = true
        }
      },
      icon = { Icon(item.icon, null) },
      label = {
        Text(
          text = stringResource(item.label),
          maxLines = 1
        )
      },
      alwaysShowLabel = false
    )
  }
}
onClick のラムダブロックでは、
- バックスタックなし。
- 画面の state は保存/リストアする。
としています。
それぞれの画面内での遷移がない場合は、これがシンプルで自然だと思います。
画面のスクロールポジションもこれで保持できます。
■ NavHost
画面のメイン表示部分です。
NavigationItem に紐づけて各画面の Screen-level Composable を記述しておきます。
NavHost( 
) {
  composable(NavigationItem.HOME.route) {
    HomeScreen()
  }
  composable(NavigationItem.CRYPT.route) {
    CryptScreen()
  }
  // ...
}
■ まとめ
@Composable
fun MainScreen() {
  val navController = rememberNavController()
  Column(
    modifier = Modifier.fillMaxSize(),
    verticalArrangement = Arrangement.Bottom
  ) {
    NavHost(
      navController = navController,
      startDestination = NavigationItem.HOME.route,
      modifier = Modifier
        .weight(1f)
    ) {
      composable(NavigationItem.HOME.route) {
        HomeScreen()
      }
      composable(NavigationItem.CRYPT.route) {
        CryptScreen()
      }
      // ...
    }
    BottomNavigation {
      val backStack by navController.currentBackStackEntryAsState()
      val current = backStack?.destination?.route
      NavigationItem.values().forEach { item ->
        BottomNavigationItem(
          selected = current == item.route,
          onClick = {
            navController.navigate(item.route) {
              // no back stacks. 0 is the root navigation
              popUpTo(id = 0) { 
                saveState = true
              }
              launchSingleTop = true
              restoreState = true
            }
          },
          icon = { Icon(item.icon, null) },
          label = {
            Text(
              text = stringResource(item.label),
              maxLines = 1
            )
          },
          alwaysShowLabel = false
        )
      }
    }
  }
}
enum class NavigationItem(
  val route: String,
  val icon: ImageVector,
  @StringRes val label: Int,
) {
  HOME("home", Icons.Outlined.Home, R.string.nav_label_home),
  CRYPT("crypt", Icons.Outlined.MonetizationOn, R.string.nav_label_crypt),
  // ...
}
Scaffold って使い勝手悪くね?




