Navigation Architecture Component によるアプリ内遷移の管理

Navigation Architecture Component によるアプリ内遷移の管理

2019/02/08(金) に DroidKaigi 2019 で使用したスライドです

Management of In-App Transition using Navigation Architecture Component

29ce13d154890c7dea2c285909215868?s=128

Yuta Takahashi

February 08, 2019
Tweet

Transcript

  1. Navigation Architecture Component による アプリ内遷移の管理 Yuta Takahashi

  2. @yt_hizi @yt-tkhs 髙橋 佑太 2018年4⽉, 株式会社サイバーエージェントに新卒⼊社 CATS(Client Advanced Technology Studio)

    に所属 MotionLayout と Navigation に注⽬ 技術書典5 で MotionLayout のことを書きました✏ Yuta Takahashi
  3. ΞδΣϯμ • Navigation Architecture Component の概要 • 設計上の概念とデザイン原則 • 画⾯遷移を実装してみる

    • マルチモジュールにおける遷移を考える • まとめ
  4. Navigation Architecture Component

  5. Source: https://android.jlelse.eu/what-is-android-jetpack-737095e88161

  6. Navigation Architecture Component • Google I/O 2018 で発表 • アプリケーション内における画⾯遷移を簡単に実装する


    ためのライブラリとそのツール群 • 現在の最新版は 1.0.0-beta01 2019/2/4 に beta になりました
  7. 画⾯遷移における問題 • FragmentTransaction • Deep Link • 画⾯間の引数渡し • Up

    と Back etc Navigation を使うことによって適切に制御できる
  8. • FragmentTransaction を⾃動的にハンドリングする • Deep Link を⾃動的にハンドリングする • 画⾯遷移時に型安全に情報を渡すことができる Safe

    Args • アプリ内遷移を可視化・編集可能な Navigation Editor 主な特徴 etc
  9. 設計上の概念

  10. Navigation Graph アプリ内における画⾯と 画⾯間の遷移のグラフ

  11. Destination Fragment/Activity などの Actionによって遷移可能な画⾯

  12. Start Destination アプリのエントリポイントとなる Destination (ホーム画⾯)

  13. Action 任意の Destination から 他の Destination への遷移

  14. Principles of navigation https://developer.android.com/topic/libraries/architecture/navigation/#principles デザイン原則

  15. アプリは固定の "Start Destination" をもつ デザイン原則❶ A B C Start Destination

  16. Login Splash A B C アプリは固定の "Start Destination" をもつ デザイン原則❶

    Start Destination?
  17. Login Splash 条件付/⼀時的な画⾯は Start Destination にならない A B C Start

    Destination アプリは固定の "Start Destination" をもつ デザイン原則❶
  18. A B C A B C Navigation Stack == 遷移の状態はスタックで表現される

    デザイン原則❷
  19. A B C Current Destination Start Destination 遷移の状態はスタックで表現される デザイン原則❷

  20. A B C Current Destination Start Destination 遷移の操作は常に Current Destination

    で またはそれに対して⾏われるべき 遷移の状態はスタックで表現される デザイン原則❷
  21. • Start destination(ホーム画⾯) にいるときは
 Toolbar に Upボタンを表⽰すべきではない • Toolbar と連携するための拡張機能があるので


    それを使うことで簡単に実現できる 参考: Back ボタンと Up ボタンを使⽤したナビゲーション - https://developer.android.com/design/patterns/navigation Up ボタンはアプリを終了しない デザイン原則❸
  22. • "Start Destination" ではなく, ⾃⾝のアプリのタスクにいるとき
 Up と Back は同じ動作をするべき •

    Start Destination のときは, Up は使えない (❸で⽰した原則) • 他のアプリのタスクとして起動したとき Back は他のアプリに
 戻り, Up は⾃⾝のアプリの前の画⾯に戻る 参考: Reverse Navigation - https://material.io/design/navigation/understanding-navigation.html#reverse-navigation Up と Back は同じ動作をする デザイン原則❹
  23. • Deep Link で遷移したときと普通に遷移したときで
 同じ画⾯にいるなら, 同じスタックが形成されているべき • Navigation がもつ Deep

    Link の機能を使えば, Navigation Graph 
 から⾃動的にスタックを構築してくれるようになっている • ただし, 起動⽅法(既存のタスク or 新しいタスク)によって
 制御できない部分があるので注意が必要 Deep Link でも同じスタックを形成する デザイン原則❺
  24. 実装する上での知識

  25. Navigation Safe Args Navigation Editor

  26. Navigation XML を⽤いて Navigation Graph を記述していく <navigation> <fragment> <action />

    </fragment> <fragment> <argument /> <argument /> <deepLink /> </fragment> </navigation>
  27. Navigation Navigation Graph XML を⽤いて Navigation Graph を記述していく <navigation> <fragment>

    <action /> </fragment> <fragment> <argument /> <argument /> <deepLink /> </fragment> </navigation>
  28. <navigation> <fragment> <action /> </fragment> <fragment> <argument /> <argument />

    <deepLink /> </fragment> </navigation> Navigation Destination Destination XML を⽤いて Navigation Graph を記述していく
  29. Navigation Action Argument(遷移時の引数) XML を⽤いて Navigation Graph を記述していく Deep Link

    <navigation> <fragment> <action /> </fragment> <fragment> <argument /> <argument /> <deepLink /> </fragment> </navigation>
  30. <navigation> <fragment> <action /> </fragment> <fragment> <argument /> <argument />

    <deepLink /> </fragment> </navigation> Directions class Args class 型安全なデータ渡しを実現するための Gradle Plugin SafeArgs 遷移元からデータを渡すときに使う 遷移先でデータを受け取るときに使う
  31. Navigation Editor XMLで定義したグラフを可視化および編集できるGUIツール

  32. 実装してみる

  33. MainActivity UserDetailFragment User ID UserListFragment ❶ UserListFragment を表⽰ ❷ UserDetailFragment

    に遷移する ❸ 遷移時にデータ(User ID) を渡す
  34. app/build.gradle Navigation を導⼊する dependencies { implementation "android.arch.navigation:navigation-fragment-ktx:1.0.0-beta01" implementation "android.arch.navigation:navigation-ui-ktx:1.0.0-beta01" }

    Toolbar, BottomNavigation との連携を⾏うためのツール Navigation で Fragment を扱うための拡張 AndroidXを使⽤している場合は Jetifier が必要 ⚠
  35. <navigation xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/graph_main"> </navigation> res/navigation/graph_main.xml 最初のFragmentを表⽰する

  36. <navigation xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/graph_main"> <fragment android:id="@+id/dest_user_list" android:name="com.example.UserListFragment"/> </navigation> res/navigation/graph_main.xml Destination

    を追加 最初のFragmentを表⽰する
  37. <navigation xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/graph_main" app:startDestination="@id/dest_user_list"> <fragment android:id="@+id/dest_user_list" android:name="com.example.UserListFragment"/> </navigation> res/navigation/graph_main.xml

    Start Destination の指定 最初のFragmentを表⽰する
  38. <navigation xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/graph_main" app:startDestination="@id/dest_user_list"> <fragment android:id="@+id/dest_user_list" android:name="com.example.UserListFragment"/> </navigation> res/navigation/graph_main.xml

    最初のFragmentを表⽰する
  39. res/layout/activity_main.xml <FrameLayout …> <fragment android:id="@+id/navHostFragment" android:name="androidx.navigation.fragment.NavHostFragment" android:layout_width="match_parent" android:layout_height="match_parent" app:defaultNavHost="true" app:navGraph="@navigation/graph_main"/>

    </FrameLayout> 最初のFragmentを表⽰する
  40. res/layout/activity_main.xml <FrameLayout …> <fragment android:id="@+id/navHostFragment" android:name="androidx.navigation.fragment.NavHostFragment" android:layout_width="match_parent" android:layout_height="match_parent" app:defaultNavHost="true" app:navGraph="@navigation/graph_main"/>

    </FrameLayout> Destination をホストするための Fragment 最初のFragmentを表⽰する
  41. res/layout/activity_main.xml <FrameLayout …> <fragment android:id="@+id/navHostFragment" android:name="androidx.navigation.fragment.NavHostFragment" android:layout_width="match_parent" android:layout_height="match_parent" app:defaultNavHost="true" app:navGraph="@navigation/graph_main"/>

    </FrameLayout> 作成した Navigation XML を指定する 最初のFragmentを表⽰する
  42. res/layout/activity_main.xml <FrameLayout …> <fragment android:id="@+id/navHostFragment" android:name="androidx.navigation.fragment.NavHostFragment" android:layout_width="match_parent" android:layout_height="match_parent" app:defaultNavHost="true" app:navGraph="@navigation/graph_main"/>

    </FrameLayout> true のとき, システムのBackで前のFragmentに戻れる 最初のFragmentを表⽰する
  43. res/navigation/graph_main.xml 最初のFragmentを表⽰する MainActivity NavHostFragment UserListFragment Layout xml を読み込んで表⽰ 指定した Graph

    の startDestination を表⽰
  44. <navigation xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/graph_main" app:startDestination="@id/dest_user_list"> <fragment android:id="@+id/dest_user_list" android:name="com.example.UserListFragment"/> </navigation> res/navigation/graph_main.xml

    別のFragmentに遷移する
  45. <navigation xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/graph_main" app:startDestination="@id/dest_user_list"> <fragment android:id="@+id/dest_user_list" android:name="com.example.UserListFragment"/> <fragment android:id="@+id/dest_user_detail"

    android:name="com.example.UserDetailFragment"/> </navigation> res/navigation/graph_main.xml 別のFragmentに遷移する
  46. <navigation xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/graph_main" app:startDestination="@id/dest_user_list"> <fragment android:id="@+id/dest_user_list" android:name="com.example.UserListFragment"/> <fragment android:id="@+id/dest_user_detail"

    android:name="com.example.UserDetailFragment"/> </navigation> res/navigation/graph_main.xml 別のFragmentに遷移する
  47. src/ /UserListFragment.kt findNavController().navigate(R.id.dest_user_detail, Bundle().apply { putString("userId", userId) }) 別のFragmentに遷移する

  48. src/ /UserListFragment.kt findNavController().navigate(R.id.dest_user_detail, Bundle().apply { putString("userId", userId) }) 遷移するときは Safe

    Args を使う 別のFragmentに遷移する
  49. buildscript { dependencies { classpath "android.arch.navigation:navigation-safe-args-gradle-plugin:1.0.0-beta01" } } /build.gradle Safe

    Args を導⼊する
  50. app/build.gradle Safe Args を導⼊する apply plugin: 'com.android.application' apply plugin: 'kotlin-android'

    apply plugin: 'androidx.navigation.safeargs.kotlin' android { … } 'androidx.navigation.safeargs' 'androidx.navigation.safeargs.kotlin' Kotlin のコードが⽣成される Java のコードが⽣成される Javaで出来ることがKotlinで出来ないことがあるので注意 ⚠ alpha10 から
  51. <navigation xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/graph_main" app:startDestination="@id/dest_user_list"> <fragment android:id="@+id/dest_user_list" android:name="com.example.UserListFragment"/> <fragment android:id="@+id/dest_user_detail"

    android:name="com.example.UserDetailFragment"/> </navigation> res/navigation/graph_main.xml Safe Args で遷移する
  52. <navigation xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/graph_main" app:startDestination="@id/dest_user_list"> <fragment android:id="@+id/dest_user_list" android:name="com.example.UserListFragment"> <action android:id="@+id/toUserDetail"

    app:destination="@id/dest_user_detail"/> </fragment> <fragment android:id="@+id/dest_user_detail" android:name="com.example.UserDetailFragment"/> </navigation> res/navigation/graph_main.xml Safe Args で遷移する
  53. <navigation xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/graph_main" app:startDestination="@id/dest_user_list"> <fragment android:id="@+id/dest_user_list" android:name="com.example.UserListFragment"> <action android:id="@+id/toUserDetail"

    app:destination="@id/dest_user_detail"/> </fragment> <fragment android:id="@+id/dest_user_detail" android:name="com.example.UserDetailFragment"/> </navigation> res/navigation/graph_main.xml Safe Args で遷移する
  54. <navigation xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/graph_main" app:startDestination="@id/dest_user_list"> <fragment android:id="@+id/dest_user_list" android:name="com.example.UserListFragment"> <action android:id="@+id/toUserDetail"

    app:destination="@id/dest_user_detail"/> </fragment> <fragment android:id="@+id/dest_user_detail" android:name="com.example.UserDetailFragment"> <argument android:name="userId" app:argType="string" /> </fragment> </navigation> res/navigation/graph_main.xml Safe Args で遷移する
  55. <navigation xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/graph_main" app:startDestination="@id/dest_user_list"> <fragment android:id="@+id/dest_user_list" android:name="com.example.UserListFragment"> <action android:id="@+id/toUserDetail"

    app:destination="@id/dest_user_detail"/> </fragment> <fragment android:id="@+id/dest_user_detail" android:name="com.example.UserDetailFragment"> <argument android:name="userId" app:argType="string" /> </fragment> </navigation> res/navigation/graph_main.xml Safe Args で遷移する
  56. <navigation xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/graph_main" app:startDestination="@id/dest_user_list"> <fragment android:id="@+id/dest_user_list" android:name="com.example.UserListFragment"> <action android:id="@+id/toUserDetail"

    app:destination="@id/dest_user_detail"/> </fragment> <fragment android:id="@+id/dest_user_detail" android:name="com.example.UserDetailFragment"> <argument android:name="userId" app:argType="string" /> </fragment> </navigation> res/navigation/graph_main.xml Safe Args で遷移する UserListFragmentDirections
  57. <navigation xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/graph_main" app:startDestination="@id/dest_user_list"> <fragment android:id="@+id/dest_user_list" android:name="com.example.UserListFragment"> <action android:id="@+id/toUserDetail"

    app:destination="@id/dest_user_detail"/> </fragment> <fragment android:id="@+id/dest_user_detail" android:name="com.example.UserDetailFragment"> <argument android:name="userId" app:argType="string" /> </fragment> </navigation> res/navigation/graph_main.xml Safe Args で遷移する UserDetailFragmentArgs
  58. class UserListFragmentDirections private constructor() { private data class ToUserDetail(val userId:

    String) : NavDirections { override fun getActionId(): Int = com.example.R.id.toUserDetail override fun getArguments(): Bundle { val result = Bundle() result.putString("userId", this.userId) return result } } companion object { fun toUserDetail(userId: String): NavDirections = ToUserDetail(userId) } } app/build/ /UserListFragmentDirections.kt Safe Args で遷移する
  59. app/build/ /UserDetailFragmentArgs.kt Safe Args で遷移する data class UserDetailFragmentArgs(val userId: String)

    : NavArgs { fun toBundle(): Bundle { val result = Bundle() result.putString("userId", this.userId) return result } companion object { @JvmStatic fun fromBundle(bundle: Bundle): UserDetailFragmentArgs { bundle.setClassLoader(UserDetailFragmentArgs::class.java.classLoader) val __userId : String? if (bundle.containsKey("userId")) { __userId = bundle.getString("userId") if (__userId == null) { throw IllegalArgumentException("Argument \"userId\" is marked as non-null but } } else { throw IllegalArgumentException("Required argument \"userId\" is missing and does } return UserDetailFragmentArgs(__userId) } }
  60. findNavController().navigate(
 UserListFragmentDirections.toUserDetail(userId)) src/ /UserListFragment.kt Safe Args で遷移する

  61. src/ /UserDetailFragment.kt Safe Args でデータを受け取る private val args by navArgs<UserDetailFragmentArgs>()

    getUser(args.userId) Property Delegation alpha10 から
  62. <navigation xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/graph_main" app:startDestination="@id/dest_user_list"> <fragment android:id="@+id/dest_user_list" android:name="com.example.UserListFragment"> <action android:id="@+id/toUserDetail"

    app:destination="@id/dest_user_detail"/> </fragment> <fragment android:id="@+id/dest_user_detail" android:name="com.example.UserDetailFragment"> <argument android:name="userId" app:argType="string" /> </fragment> </navigation> Deep Link を使って遷移する res/navigation/graph_main.xml
  63. android:id="@+id/dest_user_list" android:name="com.example.UserListFragment"> <action android:id="@+id/toUserDetail" app:destination="@id/dest_user_detail"/> </fragment> <fragment android:id="@+id/dest_user_detail" android:name="com.example.UserDetailFragment"> <argument

    android:name="userId" app:argType="string" /> </fragment> </navigation> Deep Link を使って遷移する res/navigation/graph_main.xml
  64. android:id="@+id/dest_user_list" android:name="com.example.UserListFragment"> <action android:id="@+id/toUserDetail" app:destination="@id/dest_user_detail"/> </fragment> <fragment android:id="@+id/dest_user_detail" android:name="com.example.UserDetailFragment"> <argument

    android:name="userId" app:argType="string" /> <deepLink app:uri="example.com/user/{userId}"/> </fragment> </navigation> Deep Link を使って遷移する res/navigation/graph_main.xml argument にマッピング
  65. Deep Link を使って遷移する app/AndroidManifest.xml <manifest …> <application …> <activity android:name=".MainActivity">

    <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>
  66. Deep Link を使って遷移する app/AndroidManifest.xml <manifest …> <application …> <activity android:name=".MainActivity">

    <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> <nav-graph android:value="@navigation/graph_main" /> </activity> </application> </manifest>
  67. Deep Link を使って遷移する app/AndroidManifest.xml Manifest Merger

  68. Deep Link を使って遷移する https://example.com/user/U12345

  69. Deep Link を使って遷移する https://example.com/user/U12345 MainActivity UserDetailFragment User ID

  70. Deep Link を使って遷移する https://example.com/user/U12345 MainActivity UserDetailFragment User ID UserListFragment

  71. res/navigation/graph_main.xml Navigation Graphを⾒てみる

  72. Start Destination Deep Link Action Navigation Graphを⾒てみる res/navigation/graph_main.xml

  73. 実装のまとめ • Naviation XML によってアプリ内遷移を実装する • <fragment> で Destination を定義する

    • <action> で 遷移(Action) を定義する • <argument> で受け取る引数を定義する • Safe Args で型安全なデータの受け渡しを⾏うことができる • <deepLink> と <nav-args> で Deep Link を実装できる
  74. その他の機能

  75. Toolbar と連携する res/layout/activity_main.xml <androidx.constraintlayout.widget.ConstraintLayout...> <androidx.appcompat.widget.Toolbar android:id="@+id/toolbar"
 … /> <fragment android:id="@+id/navHostFragment"

    android:name="androidx.navigation.fragment.NavHostFragment" app:navGraph="@navigation/graph_main"
 … /> </androidx.constraintlayout.widget.ConstraintLayout>
  76. Toolbar と連携する res/layout/MainActivity.kt toolbar.setupWithNavController( findNavController(R.id.navHostFragment)) • Start Destination のとき以外 Up

    ボタンを⾃動的に表⽰する • <fragment> に指定した android:label を表⽰する
  77. src/ /UserListFragment.kt Fragmentをもっと使う android:label Toolbarと連携したときに title を⾃動でセットする
 argument で置き換えることもできる <fragment

    android:id="@+id/dest_user_detail" android:name="com.example.UserDetailFragment" android:label="User: {userId}"> <argument android:name="userId" app:argType="string" /> </fragment>
  78. Actionをもっと使う app:launchSingleTop app:popUpTo app:popUpToInclusive app:enterAnim app:exitAnim app:popEnterAnim app:popExitAnim Fragment切り替え時のアニメーション 遷移時にスタックをどこまでPopするか

    SingleTop として起動するかどうか
  79. Argumentをもっと使う app:nullable android:defaultValue Null値を許容するかどうか 引数のデフォルト値 app:argType に指定可能な型 integer integer[] long

    long[] float float[]
 boolean boolean[] reference reference[] string string[] Parcelable / Serializable を実装したクラスとͦͷArray型
 (独⾃のクラスを指定することも可能)
  80. SafeArgs — Java と Kotlin の違い findNavController().navigate( FirstFragmentDirections .toSecond(123) //

    argA .setArgB("arg_b") .setArgC("arb_c") ) findNavController().navigate( UserListFragmentDirections.toUserDetail( argA = 123, argB = "test", argC = "test")) Java
 Builder pattern Kotlin Named Argument
  81. マルチモジュールにおける遷移

  82. マルチモジュール • ここ最近の Android におけるトレンド • Instant Apps や Dynamic

    Feature Module を使うには
 マルチモジュール構成が必須となる • ここで扱うのは, 機能ごとにモジュールが分かれており
 Fragment が各モジュールに分散しているような状態の構成
  83. First Second Third Base (Dynamic) Feature Modules

  84. First Second Third

  85. Feature modules 間の依存関係が複雑になる
 循環参照が発⽣する First Second Third

  86. nickbutcher/plaid • デザイン関連ニュースのフィードアプリ • Navigation を使⽤していないマルチモジュールプロジェクト • 遷移は主に Activity 間で⾏われる

    https://github.com/nickbutcher/plaid どうやって遷移している?
  87. • モジュール "core" に各モジュールを依存させている • core内の ActivityHelper.kt で各Activityのクラス名をハードコーディング object Activities

    { …… object Search : AddressableActivity { override val className = "$PACKAGE_NAME.search.ui.SearchActivity" const val EXTRA_QUERY = "EXTRA_QUERY" const val EXTRA_SAVE_DRIBBBLE = "EXTRA_SAVE_DRIBBBLE" const val EXTRA_SAVE_DESIGNER_NEWS = "EXTRA_SAVE_DESIGNER_NEWS" const val RESULT_CODE_SAVE = 7 } }
  88. fun intentTo(addressableActivity: AddressableActivity): Intent { return Intent(Intent.ACTION_VIEW).setClassName( PACKAGE_NAME, addressableActivity.className )

    } val intent = intentTo(Activities.Search) ActivityHelper.kt HomeActivity.kt
  89. DroidKaigi/droidkaigi-2019-app https://github.com/DroidKaigi/droidkaigi-2019-app • DroidKaigi 2019 公式アプリ • Navigation を使⽤しているマルチモジュールプロジェクト •

    Safe Args も使⽤している • 遷移は主に Fragment 間で⾏われる
  90. Navigation をどう使っているか • 各 feature module が共通で依存するモジュールに
 Navigation XML を配置している

    • コードは共通モジュールに⽣成され, それを各モジュールが
 参照するようになっている
  91. Navigation をどう使っているか <navigation xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" app:startDestination="@id/main"> <fragment android:id="@+id/main" android:name="io.github.droidkaigi.confsched2019.session.ui.SessionPagesFragment" android:label="@string/session_label"

    > <action android:id="@+id/action_session_to_session_detail" app:destination="@id/session_detail" /> ……
  92. Navigation をどう使っているか <navigation xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" app:startDestination="@id/main"> <fragment android:id="@+id/main" android:name="io.github.droidkaigi.confsched2019.session.ui.SessionPagesFragment" android:label="@string/session_label"

    > <action android:id="@+id/action_session_to_session_detail" app:destination="@id/session_detail" /> …… 別モジュールにあるため参照できない
  93. Safe Args がクラスを⽣成するとき • クラス⽣成時は android:name が指定されているかどうかチェック • 参照が正しいかどうかはチェックしていないため正しく⽣成できる https://android.googlesource.com/platform/frameworks/support/+/androidx-master-dev/navigation/safe-args-generator/

    src/main/kotlin/androidx/navigation/safe/args/generator/NavParser.kt NavParser.kt Safe Args に含まれる, XMLをパースするためのクラス
  94. ⽣成されたクラスを使って遷移するとき https://android.googlesource.com/platform/frameworks/support/+/androidx-master-dev/navigation/fragment/ src/main/java/androidx/navigation/fragment/FragmentNavigator.java FragmentNavigator.kt Fragmentの遷移処理を受け持つクラス @NonNull public Fragment instantiateFragment( @NonNull

    Context context, @SuppressWarnings("unused") @NonNull FragmentManager fragmentManager, @NonNull String className, @Nullable Bundle args) { return Fragment.instantiate(context, className, args); }
  95. public static Fragment instantiate(Context context, String fname, @Nullable Bundle args)

    { try { Class<?> clazz = sClassMap.get(fname); if (clazz == null) { // Class not found in the cache, see if it's real, and try to add it clazz = context.getClassLoader().loadClass(fname); if (!Fragment.class.isAssignableFrom(clazz)) { throw new InstantiationException("Trying to instantiate a class " + fname + " that is not a Fragment", new ClassCastException()); } sClassMap.put(fname, clazz); } Fragment f = (Fragment) clazz.getConstructor().newInstance(); if (args != null) { args.setClassLoader(f.getClass().getClassLoader()); f.setArguments(args); } return f; } catch (ClassNotFoundException e) { リフレクションによってFragmentのインスタンスを⽣成
 実⾏時には各モジュールがマージされているため参照可能 ⽣成されたクラスを使って遷移するとき
  96. Navigation — 別のアプローチ ❶ Dagger で interface のみを Feature Modules

    に提供して
 実際の遷移コードを application module で記述する (記述量が増える)
 ❷ 実⾏時だけ android:name が使われるなら実⾏時に
 クラスの参照を渡すこともできそう (ワークアラウンド的) Feature module が Application module の
 依存関係にあるときに限られる ⚠
  97. マルチモジュールまとめ • マルチモジュールで遷移するときはリフレクションを使う
 のが最も現実的な⽅法だと考えられる • Navigation を使うときは Navigation Graph を


    共通モジュールに配置するのが現状の解決策
 (droidkaigi-app-2019 の⽅法)
  98. まとめ • Navigation の登場によって, 画⾯遷移に関わる様々な機能を
 容易に実装できるようになった
 • Safe Args によってNavigationをもっと便利に使える

    • マルチモジュールにおける利⽤はワークアラウンド的である
 ことを理解した上で使ったほうがよい
  99. ありがとうございました @yt_hizi @yt-tkhs