ComposeでもSafe Args対応が可能になりましたが、Custom NavTypeを使って独自の型の引数を渡す方法について説明します。
説明ではKotlin serializationを使っています。また、細かいSafe Args自体については省略していますので詳細は公式ドキュメント等を参考にしてください。
実装例
別の画面に User
のオブジェクトを渡したい場合を考えます。
Userの定義
@Serializable
@Parcelize
data class User(
val id: Int,
val name: String
): Parcelable
シンプルなdata classになります。@Serializable
と@Parcelize
は次のCustom NavTypeを定義するときに必要になります。
Custom NavTypeの定義
Custom NavTypeを定義するには NavType
を継承します。
// NavTypeを継承する
val UserNavType = object : NavType<User>( // 型パラメータに渡したい型を指定
// nullが許容されるかどうか
isNullableAllowed = false
) {
override fun put(bundle: Bundle, key: String, value: User) {
// Parcelableを保存するための実装
bundle.putParcelable(key, value)
}
override fun get(bundle: Bundle, key: String): User {
// Parcelableから取得するための実装
return requireNotNull(BundleCompat.getParcelable(bundle, key, User::class.java))
}
override fun serializeAsValue(value: User): String {
// JSONにしてUriエンコードする
return Uri.encode(Json.encodeToString(value))
}
override fun parseValue(value: String): User {
// JSONからUserオブジェクトに変換
return Json.decodeFromString(value)
}
}
やってることとしては、 Parcelable
への保存と、Compose Navigationのパラメータとなる文字列をJSONで生成しています。
今回はJSONにしていますが、文字列とオブジェクトを serializeAsValue
と parseValue
で変換できれば何でも良いです。
注意としては、 serializeAsValue
でUriエンコードが必要になります。
画面遷移の定義
NavGraphBuilder.composable
を使った画面遷移の定義です。
// 画面Route定義
@Serializable
data class Detail(
// 引数
val user: User
)
fun NavGraphBuilder.detailScreen() {
composable<Detail>(
// 引数のTypeとCustom NavTypeのMap
typeMap = mapOf(
typeOf<User>() to UserNavType
)
) { backStackEntry ->
// 引数の取得
val user = backStackEntry.toRoute<Detail>().user
// ...
}
}
大事になるのが、 composable
の引数の typeMap
です。ここに引数にしたいTypeと定義したCustom NavTypeの組み合わせを渡す必要があります。
画面遷移
最後に navigate
で実際に画面遷移をするだけです。
val user = User(1, "Kenji Abe")
navController.navigate(Detail(user))
最終的には以下のようなRoute文字列で画面遷移すると同じことになります。
com.example.myapplication.Detail/%7B%22id%22%3A1%2C%22name%22%3A%22Kenji%20Abe%22%7D
nullable対応
先ほどの実装例では、non nullの例でしたが、nullableにすることもできます。
// Custom NavType
val UserNavType = object : NavType<User?>( // 型パラメータをnullableに
// trueに設定する
isNullableAllowed = true
) {
// ...
// 各メソッドの引数や戻り値がnullableになる
}
// 画面Route
@Serializable
data class Detail(
val user: User? // nullableにする
)
// composable定義
composable<Detail>(
typeMap = mapOf(
typeOf<User?>() to UserNavType // 型パラメータをnullableに
)
) {
// ...
}
各箇所でnullableの対応が必要になります。
注意
この方法での画面引数を渡すのはしっかりと検討するほうが良いでしょう。source of truthかどうかなど。以下のブログを確認してください。
Note: this is supposed to be a speed bump: think long and hard whether an immutable, snapshot-in-time argument is really the source of truth for this data, or if this should really be an object you retrieve from a reactive source, such as a Flow exposed from a repository that would automatically refresh if your data changes.