Safe ArgsでCustom NavTypeを使う

Kenji Abe
7 min readOct 20, 2024

--

Photo by Nick Fewings on Unsplash

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.

参考

--

--

Kenji Abe
Kenji Abe

Written by Kenji Abe

Programmer / Gamer / Google Developers Expert for Android, Kotlin / @STAR_ZERO

No responses yet