1
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?

More than 1 year has passed since last update.

Compose Navigation と Activity遷移

  • 別Activityで表示したい画面が複数存在する
  • 別Activityでも複数画面の切り替えは Compose Navigation で実装する
  • 画面には引数を渡したい

ところが NavHostの startDestination には引数込みのパスを指定できないため、特別な対応が必要。

今回は startDestination で指定した画面に引数を渡す実装を考えます

実装の詳細・完全なコードはGitHubを参照してください

他にも解決策は考えられます

  • そもそもActivityを別に分けない
  • Activityに載せる画面をひとつに限定してNavHostを使わない
  • 引数をHiltで渡す

画面と引数のサンプル

以下のような2つの画面・引数を考えます。

sealed interface ResultArg {
    data class A(
        val name: String,
        val checked: Boolean,
    ) : ResultArg

    data class B(
        val name: String,
        val age: Int,
    ) : ResultArg
}

@Composable
fun ResultBScreen(
    arg: ResultArg.B,
    modifier: Modifier = Modifier,
) {
  // ... some composables ...
}

@Composable
fun ResultBScreen(
    arg: ResultArg.B,
    modifier: Modifier = Modifier,
) {
  // ... other composables ...
}

引数を渡す実装

基本的な方針は、NavArgumentBuilderで引数のデフォルト値を代わりに指定します

NavHost(
    navController = navController,
    startDestination = "result/a/{resultArgA}"
) {
    composable(
        route = "result/a/{resultArgA}",
        arguments = listOf(
            navArgument("resultArgA") {
                defaultValue = yourArg // ← 追加
            }
        ),
    ) { entry ->
        // ... your composable ...
    }
}

引数 ⇄ String型を変換する

data class はnavArgumentに指定できないため今回は kotlin-serialization でString型に変換して渡します。

+ @Serializable
  sealed interface ResultArg {
+     @Serializable
+     @SerialName("a")
      data class A(
          val name: String,
          val checked: Boolean,
      ) : ResultArg
 
+     @Serializable
+     @SerialName("b")
      data class B(
          val name: String,
          val age: Int,
      ) : ResultArg
  }

defaultValueを指定する

次に、変換された引数をnavArgumentのdefaultValueに指定します。 NavHostに直書きすると見通し悪いので、専用の関数を用意しておきます。

// ResultArg.B も用意する
fun resultArgA(defaultArg: ResultArg.A? = null) = navArgument("resultArgA") {
    type = NavType.StringType
    defaultArg?.let {
        defaultValue = Uri.encode(Json.encodeToString(it))
    }
}

引数をBackStackから読み出す

navArgumentで渡した引数を読み出し、元の data class にデコードします。

// ResultArg.B も用意する
val NavBackStackEntry.resultArgA: ResultArg.A?
    get() = arguments?.getString("resultArgA")?.let {
        Json.decodeFromString(Uri.decode(it))
    }

@Composable
fun `引数をBackStackから読み出す`(
    arg: ResultArg,
){
    val navController = rememberNavController()
    NavHost(
        navController = navController,
        startDestination = "result/a/{resultArgA}",
    ) {
        composable(
            route = "result/a/{resultArgA}",
            arguments = listOf(resultArgA(arg as? ResultArg.A))
        ) { entry ->
            ResultAScreen(
                arg = requireNotNull(entry.resultArgA)
            )
        }
    }
}

完成

Activityの遷移から Compose Navigation に引数を渡すまでの一連の実装です。

遷移元
    // Composable 内部を想定
    val context = LocalContext.current
    Intent(context, ResultActivity::class.java).apply {
        putExtra("arg", Json.encodeToString(arg))
    }.also(context::startActivity)
遷移先

class ResultActivity : ComponentActivity() {

    private val arg: ResultArg by lazy {
        val str = intent.getStringExtra("arg") ?: throw IllegalArgumentException("arg not found")
        Json.decodeFromString(str)
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            YourTheme {
                Surface(
                    modifier = Modifier.fillMaxSize(),
                    color = MaterialTheme.colorScheme.background
                ) {
                    val navController = rememberNavController()
                    val start = remember {
                        when (arg) {
                            is ResultArg.A -> "result/a/{resultArgA}"
                            is ResultArg.B -> "result/b/{resultArgB}"
                        }
                    }
                    NavHost(
                        navController = navController,
                        startDestination = start,
                    ) {
                        composable(
                            route = "result/a/{resultArgA}",
                            arguments = listOf(resultArgA(arg as? ResultArg.A))
                        ) { entry ->
                            ResultAScreen(
                                arg = requireNotNull(entry.resultArgA)
                            )
                        }
                        composable(
                            route = "result/b/{resultArgB}",
                            arguments = listOf(resultArgB(arg as? ResultArg.B))
                        ) { entry ->
                            ResultBScreen(
                                arg = requireNotNull(entry.resultArgB)
                            )
                        }
                    }
                }
            }
        }
    }
}
1
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
1
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?