[Android]Jetpack Compose Bottom navigation

![](https://velog.velcdn.com/images/rmsxo5678/post/2864f065-780c-4d41-af85-651fa17995ad/image.gif)


既存のXMLでボトムナビゲーションを開発する場合、該当アクティビティのxmlにボトムナビゲーションとNavHostFragmentを設定し、xmlフォルダにnavigationを追加して開発を進めます。しかし、Jetpack Composeでは宣言型UIとしてKotlinコードで記述するため、コード量も減り、整合性が高まります。

## 📌 プロジェクト設定

#### 1. libs.versions
![](https://velog.velcdn.com/images/rmsxo5678/post/3a076dc8-b156-47d2-b003-86d1e5b3b94b/image.png)

![](https://velog.velcdn.com/images/rmsxo5678/post/67aa00cf-c277-4a00-8999-1ccd1ecf9c6d/image.png)

* まず最近ではライブラリ追加をこのように`libs.versions`に該当ライブラリバージョンとプラグインライブラリを追加し、gradleに名前を入力して使用しています。
* この方式が推奨される理由は、依存性管理及びバージョンとの衝突も最小化され、維持管理に便利であるため、最近ではこの方式が多く使用されているそうです。


</br>

#### 2. build.gradle.kts
![](https://velog.velcdn.com/images/rmsxo5678/post/fa11c135-7066-4fb7-920a-3f29c6de713a/image.png)

![](https://velog.velcdn.com/images/rmsxo5678/post/3447e551-ddd4-4bed-9e89-a6ae685b68ae/image.png)


- **implementation(libs.navigation.compose)** : 基本的なナビゲーション関連コンポーネントを提供

- **implementation(libs.hilt.navigation.compose)** : HiltとJetpackナビゲーションを統合するライブラリで、**`hiltViewModel()`** 関数を使用できるようにし、ナビゲーションに簡単にビューモデルを注入できるようになります。

</br>

#### 3. build.gradle.kts(Project)

![](https://velog.velcdn.com/images/rmsxo5678/post/5e131c45-3b9f-4843-95d5-f0fef287eae7/image.png)

</br>


## 📌 Hilt 設定

### 1. プロジェクトにHiltを設定
```kotlin
@HiltAndroidApp
class App : Application() {}
```

- Applicationクラスを生成し、@HiltAndroidAppアノテーションを追加します。

### 2. メインアクティビティ設定

```kotlin
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            JetPack_BottomNavigationTheme {
                MainScreen()
            }
        }
    }
}
```
- Applicationクラスを生成し、@HiltAndroidAppアノテーションを追加します。

### 3. ViewModel 設定

```kotlin
@HiltViewModel
class HomeViewModel @Inject constructor() : ViewModel() {}
```
- ViewModelを生成し、@HiltViewModelアノテーションを追加します。

</br>


## 📌 ボトムナビゲーション設定

### 1. ボトムナビゲーションアイテム設定

```kotlin
sealed class BottomNavItem(val route: String, val icon: ImageVector, val title: String) {
    data object Home : BottomNavItem("home", Icons.Default.Home, "Home")
    data object Search : BottomNavItem("search", Icons.Default.Search, "Search")
    data object Profile : BottomNavItem("profile", Icons.Default.Person, "Profile")
}
```

> この方式は `sealed class` 方式で、各オブジェクトに対して異なるプロパティやメソッドを追加できるため、より柔軟ですが、単に固定された値集合が必要な場合は `enum class`の方が適しています。将来的に追加の動作や属性が必要な場合は`sealed class`が柔軟です。
> 

- コンストラクタパラメータ
	- `route` : 各画面の固有の経路を表す文字列
	- `icon` : 各項目のアイコンを表すImageVectorオブジェクト
	- `title` : 各項目のタイトルを表す文字列

- data object キーワード
	- Kotlin 1.9から導入された新しいキーワード
	- シングルトンオブジェクトを生成しながら、equals()、hashCode()、toString()メソッドを自動で生成

</br>    

### 2. ボトムナビゲーションコンポーザブル作成

```kotlin
@Composable
fun BottomNavigation(navController: NavHostController) {
    val items = listOf(
        BottomNavItem.Home,
        BottomNavItem.Search,
        BottomNavItem.Profile
    )
    NavigationBar {
        val navBackStackEntry by navController.currentBackStackEntryAsState()
        val currentRoute = navBackStackEntry?.destination?.route
        items.forEach { item ->
            NavigationBarItem(
                icon = { Icon(item.icon, contentDescription = item.title) },
                label = { Text(item.title) },
                selected = currentRoute == item.route,
                onClick = {
                    navController.navigate(item.route) {
                        popUpTo(navController.graph.findStartDestination().id) {
                            saveState = true
                        }
                        launchSingleTop = true
                        restoreState = true
                    }
                }
            )
        }
    }
}
```

> この関数でナビゲーションバーを構成し、画面間の移動を可能にします。ナビゲーションバーの項目は`BottomNavItem` クラスで定義された画面で構成されます。
>  

- `items` : ボトムナビゲーションに表示される項目をリストで定義
- `navBackStackEntry` : バックスタックの状態を観察し、画面が変更されるたびに値が更新される
- `currentRoute` : アクティブな経路を取得
- `selected = currentRoute == item.route` : 現在の経路がこの項目の経路と一致する場合、選択された状態で表示
- `onClick {...}` : 項目がクリックされたときの動作を定義し、popUpTo部分で開始と目的地までバックスタックをポップし、状態を保存する。これはスタックが積み重なるのを防ぐ。
- `launchSingleTop = true` : 同じ目的地がすでにスタックの最上位にある場合、新しいインスタンスを作成しない
- `restoreState = true` : 前の保存された状態を復元

</br>

### 3. メイン画面設定

```kotlin
@SuppressLint("UnusedMaterial3ScaffoldPaddingParameter")
@Composable
fun MainScreen() {
    val navController = rememberNavController()
    Scaffold(
        bottomBar = { BottomNavigation(navController) }
    ) {
        NavHost(
            navController = navController,
            startDestination = BottomNavItem.Home.route
        ) {
            composable(BottomNavItem.Home.route) { HomeScreen() }
            composable(BottomNavItem.Search.route) { SearchScreen() }
            composable(BottomNavItem.Profile.route) { ProfileScreen() }
        }
    }
}
```

> Scaffoldを使用して基本レイアウトを作成し、下部にナビゲーションバーを配置します。NavHostを通じて各経路に対応する画面を定義し、ユーザーがボトムナビゲーションを通じて他の画面に移動できるようにします。
>  

- `val navController = rememberNavController()` : ナビゲーションコントローラを作成し、記憶して画面間の移動を管理
- `bottomBar = { BottomNavigation(navController) }` : Scaffoldの下部に前述のBottomNavigationコンポーザブルを配置
- `NavHost(...)` : ナビゲーショングラフを定義するコンポーザブルで、各経路に対応するコンポーザブルを指定
- `navController = navController` : アクティブな経路を取得
- `startDestination = BottomNavItem.Home.route` : アプリが起動時に最初に表示される画面の経路を指定
- `lcomposable(...)` : 各ナビゲーション項目に対する目的地を指定

</br>


### 4. 画面コンポーザブル作成

```kotlin
@Composable
fun HomeScreen(homeViewModel: HomeViewModel = hiltViewModel()) {
    Text("Home Screen")
}

@Composable
fun SearchScreen() {
    Text(text = "Search Screen")
}

@Composable
fun ProfileScreen() {
    Text(text = "Profile Screen")
}
```

> このボトムナビゲーションの各アイテムの画面で、現在は非常に簡単な形態ですが、実際のアプリではさらに拡張される可能性があります。
>  

- 各画面により複雑なUI要素を追加できます。
- ViewModelを通じてデータをロードし、状態を管理
- ユーザー入力を処理し、その入力に応じてUIを更新できる

</br>


## 📌 まとめ

最近サブプロジェクトに参加し、Jetpack ComposeとHiltを使用してボトムナビゲーションを作業しましたが、既存のXMLに慣れているためか、Jetpack Composeで行うことがコードが簡潔だとされていますが、慣れていませんでした。しかし、ナビゲーションにHiltを使用して依存性注入を簡単に処理できるため、コードの保守性と拡張性を高めることができると思います。

---

GitHub : https://github.com/GEUN-TAE-KIM/Jetpack-Compose_BottomNavigation_Sample