Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Road to Single Activity Uncovered

Sponsored · Ship Features Fearlessly Turn features on and off without deploys. Used by thousands of Ruby developers.
Avatar for yurihondo yurihondo
October 11, 2024

Road to Single Activity Uncovered

DroidKaigiの「Road to Single Activity」のセッションで時間の関係でカバーできなかったトピックについての発表です。
https://2024.droidkaigi.jp/timetable/690950/

具体的には以下の内容を扱います。
- Typesafe navigation arguments
- Complex Navigating

Avatar for yurihondo

yurihondo

October 11, 2024
Tweet

More Decks by yurihondo

Other Decks in Technology

Transcript

  1. Agenda • Recap of Road to Single Activity • Typesafe

    navigation arguments • Complex Navigating
  2. Navigation ~2.7.x fun NavController.navigateToHogeRoute( arg: String, navOptions: NavOptions? = null

    ) { this.navigate( route = "hoge_route/$arg", navOptions = navOptions ) }
  3. Navigation ~2.7.x fun NavController.navigateToHogeRoute( arg: String, navOptions: NavOptions? = null

    ) { this.navigate( route = "hoge_route/$arg", navOptions = navOptions ) } ←型を定義
  4. Navigation ~2.7.x composable( ... ) { entry -> HogeRoute( arg

    = entry.arguments?.getString("hoge_arg")!!, ... ) }
  5. Navigation ~2.7.x composable( ... ) { entry -> HogeRoute( arg

    = entry.arguments?.getString("hoge_arg")!!, ... ) } ←型を指定
  6. Navigation 2.8.0~ @Serializable data class HogeDestination( @SerialName("arg") val arg: String,

    ) navController.navigate(HogeDestination("hoge")) Routeを定義
  7. Navigation 2.8.0~ @Serializable data class HogeDestination( @SerialName("arg") val arg: String,

    ) navController.navigate(HogeDestination("hoge")) ←引数を定義
  8. Navigation 2.8.0~ @Serializable data class HogeDestination( @SerialName("arg") val arg: String,

    ) navController.navigate(HogeDestination("hoge"))←Newして渡す
  9. Navigation 2.8.0~ composable<HogeDestination>( … ) { backStackEntry -> HogeRoute( arg

    = backStackEntry.toRoute<HogeDestination>().arg, … ) }
  10. Navigation 2.8.0~ composable<HogeDestination>( … ) { backStackEntry -> HogeRoute( arg

    = backStackEntry.toRoute<HogeDestination>().arg, … ) } ↑型を指定
  11. Navigation 2.8.0~ composable<HogeDestination>( … ) { backStackEntry -> HogeRoute( arg

    = backStackEntry.toRoute<HogeDestination>().arg, … ) } ↑型を指定 記述を 間違ったら...
  12. Navigation 2.8.0~ composable<HogeDestination>( … ) { backStackEntry -> HogeRoute( arg

    = backStackEntry.toRoute(), … ) } ← Runtimeでクラッシュ💥
  13. Navigation 2.8.0~ composable<HogeDestination>( … ) { backStackEntry -> HogeRoute( arg

    = backStackEntry.toRoute(), … ) } ← Runtimeでクラッシュ💥 引数 受け取りも 型安全に実装したい
  14. Compose Destinations @Destination<HogeGraph> @Composable internal fun HogeRoute( arg: String, …

    ) {...} destinationsNavigator.navigate(HogeRouteDestination("hoge"))
  15. Compose Destinations @Destination<HogeGraph> @Composable internal fun HogeRoute( arg: String, …

    ) {...} destinationsNavigator.navigate(HogeRouteDestination("hoge")) ← 引数を設定
  16. Compose Destinations @Destination<HogeGraph> @Composable internal fun HogeRoute( arg: String, …

    ) {...} destinationsNavigator.navigate(HogeRouteDestination("hoge")) 引数を渡すため Routeオブジェクトが 自動生成される ↓
  17. Compose Destinations @Destination<HogeGraph> @Composable internal fun HogeRoute( arg: String, …

    ) {...} destinationsNavigator.navigate(HogeRouteDestination("hoge")) ロジック不要 型安全に受け取れる
  18. Compose Destinations @Destination<HogeGraph> @Composable internal fun HogeRoute( arg: String, …

    ) {...} destinationsNavigator.navigate(HogeRouteDestination("hoge")) 一体どうやって... ↓
  19. Compose Destinations public data object HogeRouteDestination : BaseRoute(), TypedDestinationSpec<HogeRouteDestinationNavArgs> {

    public operator fun invoke( arg: String, ): Direction { return Direction( route = "$baseRoute" + "/${stringNavType.serializeValue("arg", arg)}" ) } @Composable override fun DestinationScope<HogeRouteDestinationNavArgs>.Content() { val dependencyContainer = buildDependencies() val (arg) = navArgs HogeRoute( arg = arg, … ) } }
  20. Compose Destinations public data object HogeRouteDestination : BaseRoute(), TypedDestinationSpec<HogeRouteDestinationNavArgs> {

    public operator fun invoke( arg: String, ): Direction { return Direction( route = "$baseRoute" + "/${stringNavType.serializeValue("arg", arg)}" ) } @Composable override fun DestinationScope<HogeRouteDestinationNavArgs>.Content() { val dependencyContainer = buildDependencies() val (arg) = navArgs HogeRoute( arg = arg, … ) } } ← 👀
  21. Compose Destinations public data object HogeRouteDestination : …{ public operator

    fun invoke( arg: String, ): Direction { return Direction( route = "$baseRoute" + "/${stringNavType.serializeValue("arg", arg)}" ) } } ↓引数あり URLを自動生成
  22. Compose Destinations public data object HogeRouteDestination : BaseRoute(), TypedDestinationSpec<HogeRouteDestinationNavArgs> {

    public operator fun invoke( arg: String, ): Direction { return Direction( route = "$baseRoute" + "/${stringNavType.serializeValue("arg", arg)}" ) } @Composable override fun DestinationScope<HogeRouteDestinationNavArgs>.Content() { val dependencyContainer = buildDependencies() val (arg) = navArgs HogeRoute( arg = arg, … ) } } ← 👁 👁
  23. Compose Destinations @Composable override fun DestinationScope<HogeRouteDestinationNavArgs>.Content() { val dependencyContainer =

    buildDependencies() val (arg) = navArgs HogeRoute( arg = arg, … ) } ←引数を取り出してセット
  24. Compose Destinations @Composable override fun DestinationScope<HogeRouteDestinationNavArgs>.Content() { val dependencyContainer =

    buildDependencies() val (arg) = navArgs HogeRoute( arg = arg, … ) } ←引数を取り出してセット 自動生成で型安全な 引数 受け取りを実現
  25. Typesafe navigation arguments アプリ規模 画面数 ナビゲーションの複雑さ Vanilla Navigation Compose Compose

    Destinations 小規模アプリ 10画面未満 シンプル ◎ 最適 △ オーバースペック気味 中規模アプリ 10〜30画面 中程度 〇 使いやすい 〇 開発効率が向上 大規模アプリ 31画面以上 複雑 △ 管理が大変になる可能性 ◎ 型安全・管理が容易
  26. data class BridgeState( val hasBridged: Boolean = false, val request:

    Request? = null ) : Serializable { @Parcelize sealed interface Request : Parcelable @Parcelize data class ShowHoge( val arg: String, ) : Request } private var state = BridgeState()
  27. data class BridgeState( val hasBridged: Boolean = false, val request:

    Request? = null ) : Serializable { @Parcelize sealed interface Request : Parcelable @Parcelize data class ShowHoge( val arg: String, ) : Request } private var state = BridgeState() ← 遷移したかどうか 状態
  28. data class BridgeState( val hasBridged: Boolean = false, val request:

    Request? = null ) : Serializable { @Parcelize sealed interface Request : Parcelable @Parcelize data class ShowHoge( val arg: String, ) : Request } private var state = BridgeState() ← 遷移リクエスト ← 画面引数など
  29. class BridgeActivity : AppCompatActivity() { companion object { private const

    val KEY_REQUEST = "key_request" fun createIntentForHoge( activityContext: Context, arg: String, ): Intent { return Intent(activityContext, BridgeActivity::class.java).apply { putExtra(KEY_REQUEST, ShowHoge(arg)) } } } }
  30. class BridgeActivity : AppCompatActivity() { companion object { private const

    val KEY_REQUEST = "key_request" fun createIntentForHoge( activityContext: Context, arg: String, ): Intent { return Intent(activityContext, BridgeActivity::class.java).apply { putExtra(KEY_REQUEST, ShowHoge(arg)) } } } }
  31. class BridgeActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) {

    if (savedInstanceState == null) { state = BridgeState( hasBridged = false, request = IntentCompat.getParcelableExtra(intent, ...) ) } setContent { Surface(…) { MainNavHost( startDirection = NavGraphs.Bridge … ) } } } }
  32. class BridgeActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) {

    if (savedInstanceState == null) { state = BridgeState( hasBridged = false, request = IntentCompat.getParcelableExtra(intent, ...) ) } setContent { Surface(…) { MainNavHost( startDirection = NavGraphs.Bridge … ) } } } } ↑ Intentからstateを生成
  33. class BridgeActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) {

    if (savedInstanceState == null) { state = BridgeState( hasBridged = false, request = IntentCompat.getParcelableExtra(intent, ...) ) } setContent { Surface(…) { MainNavHost( startDirection = NavGraphs.Bridge … ) } } } } ←MainGraphを呼び出す
  34. class BridgeActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) {

    if (savedInstanceState == null) { state = BridgeState( hasBridged = false, request = IntentCompat.getParcelableExtra(intent, ...) ) } setContent { Surface(…) { MainNavHost( startDirection = NavGraphs.Bridge … ) } } } } ↑BridgeRouteを渡し、 NavHost StartDestinationに設定する
  35. @Destination<BridgeNavGraph>(start = true) @Composable internal fun BridgeRoute( mainNavigator: MainNavigator, )

    { if (state.hasBridged) { LocalContext.current.findActivity().finish() return } when (val request = state.request) { is ShowHoge -> { mainNavigator.navigateToHoge( arg = request.arg, ) } else -> LocalContext.current.findActivity().finish() } state = state.copy(hasBridged = true) }
  36. @Destination<BridgeNavGraph>(start = true) @Composable internal fun BridgeRoute( mainNavigator: MainNavigator, )

    { if (state.hasBridged) { LocalContext.current.findActivity().finish() return } when (val request = state.request) { is ShowHoge -> { mainNavigator.navigateToHoge( arg = request.arg, ) } else -> LocalContext.current.findActivity().finish() } state = state.copy(hasBridged = true) } ←RequestみてHogeScreenを起動
  37. @Destination<BridgeNavGraph>(start = true) @Composable internal fun BridgeRoute( mainNavigator: MainNavigator, )

    { if (state.hasBridged) { LocalContext.current.findActivity().finish() return } when (val request = state.request) { is ShowHoge -> { mainNavigator.navigateToHoge( arg = request.arg, ) } else -> LocalContext.current.findActivity().finish() } state = state.copy(hasBridged = true) } ←遷移させたらhasBridgedをtrueに
  38. @Destination<BridgeNavGraph>(start = true) @Composable internal fun BridgeRoute( mainNavigator: MainNavigator, )

    { if (state.hasBridged) { LocalContext.current.findActivity().finish() return } when (val request = state.request) { is ShowHoge -> { mainNavigator.navigateToHoge( arg = request.arg, ) } else -> LocalContext.current.findActivity().finish() } state = state.copy(hasBridged = true) } ←BridgeRouteに戻ってきたら BridgeActivity自体を閉じる