3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

フラー株式会社Advent Calendar 2024

Day 12

【Android】Navigation ComposeのSafe Argsについて(フォルダの分け方など)

Last updated at Posted at 2024-12-11

はじめに

この記事はフラー株式会社 Advent Calendar 2024 12日目の記事です

11 日目は@masaya82さんのUIコンポーネントをライブラリに頼ることについてでした

1. なぜ記事を書いたか?

Navigation ComposeのSafeArgs周りでいい記事が見つからなかったから

2. 検証環境(2024/12/09 現在)

UIフレームワーク: Jetpack Compose
Kotlin 2.1.0
minSDK 24
Android Studio Ladybug | 2024.2.1
macOS Sonoma 14.7

3. ゴール

MVP(Minimum Viable Product)でNavigation ComposeのSafe Argsを体験(実装)してみる

4. 下準備(2024/12/04 現在)

  • ライブラリをインポートする(Kotlinのバージョンを上げるなどは省略)
    • kotlin SerializationとNavigation Composeを入れる
(Project) build.gradle
plugins {

    // Existing plugins
    alias(libs.plugins.compose.compiler) apply false

    // add: kotlin serialization
    kotlin("jvm") version "2.1.0"
    kotlin("plugin.serialization") version "2.1.0"
}
(Module) build.gradle
plugins {

    alias(libs.plugins.compose.compiler)

    // add: kotlin serialization 
    id("org.jetbrains.kotlin.plugin.serialization") version "2.1.0"
}

    
dependencies {

    // add: kotlin serialization
    implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.3")

    // add: navigation compose
    implementation("androidx.navigation:navigation-compose:2.8.4")
}

5. フォルダ構成

(今回 viewModelは実装していないが〇〇Screenの箇所に格納する想定)

- com.example.navArgsTest
  - navigation
      - MyAppNavHost.kt
      - Route.kt
  - screen
      - firstScreen
          - FirstScreen.kt
          - FirstScreenNavigation.kt
      - secondScreen
          - SecondScreen.kt
          - SecondScreenNavigation.kt
  - MainActivity.kt
  - MyApp.kt		

6. 処理の流れの概要

7. MainActivity

MainActivityはMyAppを参照するように実装

MainActivity.kt
class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            enableEdgeToEdge()
            ComposeNavigationSafeArgsTestTheme {
                MyApp()
            }
        }
    }
}

8. MyApp

MyAppにnavControllerを定義
MyAppNavHostにnavControllerを渡すように実装

MyApp.kt
@Composable
fun MyApp() {
    val navController = rememberNavController()
    MyAppNavHost(navController)
}

9. MyAppNavHost

画面の格納をしているイメージ
NavHostの引数はnavControllerとstartDestination(どの画面が最初に来るか?)を渡す

firstScreen()secondScreen()はNavGraphBuilderの拡張関数
リファクタリングをしてこのような形としている

MyAppNavHost.kt
@Composable
fun MyAppNavHost(
    navController: NavHostController = rememberNavController()
) {
    NavHost(
        navController = navController,
        startDestination = Route.FirstScreenRoute,
    ) {
    
        // firstScreen
        firstScreen(navController)

        // secondScreen
        secondScreen(navController)
    }
}

10. Route

Routeを定義することによってタイポを減らす目的がある.

Route.kt
object Route {
    @Serializable
    object FirstScreenRoute

    @Serializable
    data class SecondScreenRoute(
        val name: String,
        val id: String
    )
}

11. 画面の定義

First Screen

FirstScreen.kt
@Composable
fun FirstScreen(navController: NavController) {
    Box(
        modifier = Modifier
            .padding(16.dp)
            .fillMaxSize(), 
        contentAlignment = Alignment.Center
    ) {
        Column {
            Text(
                "This is First Screen",
                modifier = Modifier.padding(8.dp),
                fontWeight = FontWeight.Bold
            )
            Button(
                onClick = {
                    navController.navigateToSecondScreen(name = "John", id = "012345FF")
                },
            ) { Text("Go To Second Screen") }
        }
    }
}

Second Screen

SecondScreen.kt
@Composable
fun SecondScreen(name: String, id: String, navController: NavController) {
    Column(
        modifier = Modifier.fillMaxSize(),
        verticalArrangement = Arrangement.spacedBy(16.dp, Alignment.CenterVertically),
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Text(text = "This is Second Screen", fontWeight = FontWeight.Bold)
        Text(text = "name: $name")
        Text(text = "id: $id")
        Button(onClick = { navController.navigateToFirstScreen() }) {
            Text(text = "Go To First Screen")
        }
    }
}

12. Navigation定義

ポイント

  1. Navigationをするにあたってnavigate先をJsonSerializableオブジェクトを指定する(今回の場合 Route.〇〇Route)
  2. 値を渡すときはJsonSerializableのdata classを定義しそこから値を渡す(SecondScreenNavigation参照)
  3. backStackEntry.toRoute()でそれぞれの値を取得して画面遷移時に渡す(SecondScreenNavigation参照)
FirstScreenNavigation.kt

fun NavController.navigateToFirstScreen() {
    navigate(Route.FirstScreenRoute)
}

fun NavGraphBuilder.firstScreen(navController: NavController) {
    composable<Route.FirstScreenRoute> {
        FirstScreen(navController)
    }
}
SecondScreenNavigation.kt

// navControllerの拡張関数(navigateのラッパーみたいなイメージ)
fun NavController.navigateToSecondScreen(name: String, id: String) {
    navigate(Route.SecondScreenRoute(name = name, id = id))
}

// navGraphBuilderの拡張関数(画面定義のラッパーみたいなイメージ)
fun NavGraphBuilder.secondScreen(navController: NavController) {
    composable<Route.SecondScreenRoute> { backStackEntry ->
        val secondScreenRouteArgs: Route.SecondScreenRoute = backStackEntry.toRoute()
        SecondScreen(secondScreenRouteArgs.name, secondScreenRouteArgs.id, navController)
    }
}

13. 参考文献

3
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?