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

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

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

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

Management of In-App Transition using Navigation Architecture Component

Yuta Takahashi

February 08, 2019
Tweet

More Decks by Yuta Takahashi

Other Decks in Programming

Transcript

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

    View Slide

  2. @yt_hizi
    @yt-tkhs
    髙橋 佑太
    2018年4⽉, 株式会社サイバーエージェントに新卒⼊社
    CATS(Client Advanced Technology Studio) に所属
    MotionLayout と Navigation に注⽬
    技術書典5 で MotionLayout のことを書きました✏
    Yuta Takahashi

    View Slide

  3. ΞδΣϯμ
    • Navigation Architecture Component の概要
    • 設計上の概念とデザイン原則
    • 画⾯遷移を実装してみる
    • マルチモジュールにおける遷移を考える
    • まとめ

    View Slide

  4. Navigation Architecture Component

    View Slide


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

    View Slide

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

    ためのライブラリとそのツール群
    • 現在の最新版は 1.0.0-beta01
    2019/2/4 に beta になりました

    View Slide

  7. 画⾯遷移における問題
    • FragmentTransaction
    • Deep Link
    • 画⾯間の引数渡し
    • Up と Back etc
    Navigation を使うことによって適切に制御できる

    View Slide

  8. • FragmentTransaction を⾃動的にハンドリングする
    • Deep Link を⾃動的にハンドリングする
    • 画⾯遷移時に型安全に情報を渡すことができる Safe Args
    • アプリ内遷移を可視化・編集可能な Navigation Editor
    主な特徴
    etc

    View Slide

  9. 設計上の概念

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  17. Login
    Splash
    条件付/⼀時的な画⾯は Start Destination にならない
    A B C
    Start Destination
    アプリは固定の "Start Destination" をもつ
    デザイン原則❶

    View Slide

  18. A B C
    A
    B
    C
    Navigation Stack
    ==
    遷移の状態はスタックで表現される
    デザイン原則❷

    View Slide

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

    View Slide

  20. A
    B
    C
    Current Destination
    Start Destination
    遷移の操作は常に Current Destination で
    またはそれに対して⾏われるべき
    遷移の状態はスタックで表現される
    デザイン原則❷

    View Slide

  21. • Start destination(ホーム画⾯) にいるときは

    Toolbar に Upボタンを表⽰すべきではない
    • Toolbar と連携するための拡張機能があるので

    それを使うことで簡単に実現できる
    参考: Back ボタンと Up ボタンを使⽤したナビゲーション - https://developer.android.com/design/patterns/navigation
    Up ボタンはアプリを終了しない
    デザイン原則❸

    View Slide

  22. • "Start Destination" ではなく, ⾃⾝のアプリのタスクにいるとき

    Up と Back は同じ動作をするべき
    • Start Destination のときは, Up は使えない (❸で⽰した原則)
    • 他のアプリのタスクとして起動したとき Back は他のアプリに

    戻り, Up は⾃⾝のアプリの前の画⾯に戻る
    参考: Reverse Navigation - https://material.io/design/navigation/understanding-navigation.html#reverse-navigation
    Up と Back は同じ動作をする
    デザイン原則❹

    View Slide

  23. • Deep Link で遷移したときと普通に遷移したときで

    同じ画⾯にいるなら, 同じスタックが形成されているべき
    • Navigation がもつ Deep Link の機能を使えば, Navigation Graph 

    から⾃動的にスタックを構築してくれるようになっている
    • ただし, 起動⽅法(既存のタスク or 新しいタスク)によって

    制御できない部分があるので注意が必要
    Deep Link でも同じスタックを形成する
    デザイン原則❺

    View Slide

  24. 実装する上での知識

    View Slide

  25. Navigation
    Safe Args
    Navigation Editor

    View Slide

  26. Navigation
    XML を⽤いて Navigation Graph を記述していく










    View Slide

  27. Navigation
    Navigation Graph
    XML を⽤いて Navigation Graph を記述していく










    View Slide











  28. Navigation
    Destination
    Destination
    XML を⽤いて Navigation Graph を記述していく

    View Slide

  29. Navigation
    Action
    Argument(遷移時の引数)
    XML を⽤いて Navigation Graph を記述していく
    Deep Link










    View Slide











  30. Directions class
    Args class
    型安全なデータ渡しを実現するための Gradle Plugin
    SafeArgs
    遷移元からデータを渡すときに使う
    遷移先でデータを受け取るときに使う

    View Slide

  31. Navigation Editor
    XMLで定義したグラフを可視化および編集できるGUIツール

    View Slide

  32. 実装してみる

    View Slide

  33. MainActivity
    UserDetailFragment
    User ID
    UserListFragment
    ❶ UserListFragment を表⽰
    ❷ UserDetailFragment に遷移する
    ❸ 遷移時にデータ(User ID) を渡す

    View Slide

  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 が必要

    View Slide

  35. xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/graph_main">

    res/navigation/graph_main.xml
    最初のFragmentを表⽰する

    View Slide

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

    res/navigation/graph_main.xml
    Destination を追加
    最初のFragmentを表⽰する

    View Slide

  37. xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/graph_main"
    app:startDestination="@id/dest_user_list">
    android:id="@+id/dest_user_list"
    android:name="com.example.UserListFragment"/>

    res/navigation/graph_main.xml
    Start Destination の指定
    最初のFragmentを表⽰する

    View Slide

  38. xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/graph_main"
    app:startDestination="@id/dest_user_list">
    android:id="@+id/dest_user_list"
    android:name="com.example.UserListFragment"/>

    res/navigation/graph_main.xml
    最初のFragmentを表⽰する

    View Slide

  39. res/layout/activity_main.xml

    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"/>

    最初のFragmentを表⽰する

    View Slide

  40. res/layout/activity_main.xml

    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"/>

    Destination をホストするための Fragment
    最初のFragmentを表⽰する

    View Slide

  41. res/layout/activity_main.xml

    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"/>

    作成した Navigation XML を指定する
    最初のFragmentを表⽰する

    View Slide

  42. res/layout/activity_main.xml

    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"/>

    true のとき, システムのBackで前のFragmentに戻れる
    最初のFragmentを表⽰する

    View Slide

  43. res/navigation/graph_main.xml
    最初のFragmentを表⽰する
    MainActivity
    NavHostFragment
    UserListFragment
    Layout xml を読み込んで表⽰
    指定した Graph の startDestination を表⽰

    View Slide

  44. xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/graph_main"
    app:startDestination="@id/dest_user_list">
    android:id="@+id/dest_user_list"
    android:name="com.example.UserListFragment"/>

    res/navigation/graph_main.xml
    別のFragmentに遷移する

    View Slide

  45. xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/graph_main"
    app:startDestination="@id/dest_user_list">
    android:id="@+id/dest_user_list"
    android:name="com.example.UserListFragment"/>
    android:id="@+id/dest_user_detail"
    android:name="com.example.UserDetailFragment"/>

    res/navigation/graph_main.xml
    別のFragmentに遷移する

    View Slide

  46. xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/graph_main"
    app:startDestination="@id/dest_user_list">
    android:id="@+id/dest_user_list"
    android:name="com.example.UserListFragment"/>
    android:id="@+id/dest_user_detail"
    android:name="com.example.UserDetailFragment"/>

    res/navigation/graph_main.xml
    別のFragmentに遷移する

    View Slide

  47. src/ /UserListFragment.kt
    findNavController().navigate(R.id.dest_user_detail,
    Bundle().apply { putString("userId", userId) })
    別のFragmentに遷移する

    View Slide

  48. src/ /UserListFragment.kt
    findNavController().navigate(R.id.dest_user_detail,
    Bundle().apply { putString("userId", userId) })
    遷移するときは Safe Args を使う
    別のFragmentに遷移する

    View Slide

  49. buildscript {
    dependencies {
    classpath "android.arch.navigation:navigation-safe-args-gradle-plugin:1.0.0-beta01"
    }
    }
    /build.gradle
    Safe Args を導⼊する

    View Slide

  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 から

    View Slide

  51. xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/graph_main"
    app:startDestination="@id/dest_user_list">
    android:id="@+id/dest_user_list"
    android:name="com.example.UserListFragment"/>
    android:id="@+id/dest_user_detail"
    android:name="com.example.UserDetailFragment"/>

    res/navigation/graph_main.xml
    Safe Args で遷移する

    View Slide

  52. xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/graph_main"
    app:startDestination="@id/dest_user_list">
    android:id="@+id/dest_user_list"
    android:name="com.example.UserListFragment">
    android:id="@+id/toUserDetail"
    app:destination="@id/dest_user_detail"/>

    android:id="@+id/dest_user_detail"
    android:name="com.example.UserDetailFragment"/>

    res/navigation/graph_main.xml
    Safe Args で遷移する

    View Slide

  53. xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/graph_main"
    app:startDestination="@id/dest_user_list">
    android:id="@+id/dest_user_list"
    android:name="com.example.UserListFragment">
    android:id="@+id/toUserDetail"
    app:destination="@id/dest_user_detail"/>

    android:id="@+id/dest_user_detail"
    android:name="com.example.UserDetailFragment"/>

    res/navigation/graph_main.xml
    Safe Args で遷移する

    View Slide

  54. xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/graph_main"
    app:startDestination="@id/dest_user_list">
    android:id="@+id/dest_user_list"
    android:name="com.example.UserListFragment">
    android:id="@+id/toUserDetail"
    app:destination="@id/dest_user_detail"/>

    android:id="@+id/dest_user_detail"
    android:name="com.example.UserDetailFragment">
    android:name="userId"
    app:argType="string" />


    res/navigation/graph_main.xml
    Safe Args で遷移する

    View Slide

  55. xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/graph_main"
    app:startDestination="@id/dest_user_list">
    android:id="@+id/dest_user_list"
    android:name="com.example.UserListFragment">
    android:id="@+id/toUserDetail"
    app:destination="@id/dest_user_detail"/>

    android:id="@+id/dest_user_detail"
    android:name="com.example.UserDetailFragment">
    android:name="userId"
    app:argType="string" />


    res/navigation/graph_main.xml
    Safe Args で遷移する

    View Slide

  56. xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/graph_main"
    app:startDestination="@id/dest_user_list">
    android:id="@+id/dest_user_list"
    android:name="com.example.UserListFragment">
    android:id="@+id/toUserDetail"
    app:destination="@id/dest_user_detail"/>

    android:id="@+id/dest_user_detail"
    android:name="com.example.UserDetailFragment">
    android:name="userId"
    app:argType="string" />


    res/navigation/graph_main.xml
    Safe Args で遷移する
    UserListFragmentDirections

    View Slide

  57. xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/graph_main"
    app:startDestination="@id/dest_user_list">
    android:id="@+id/dest_user_list"
    android:name="com.example.UserListFragment">
    android:id="@+id/toUserDetail"
    app:destination="@id/dest_user_detail"/>

    android:id="@+id/dest_user_detail"
    android:name="com.example.UserDetailFragment">
    android:name="userId"
    app:argType="string" />


    res/navigation/graph_main.xml
    Safe Args で遷移する
    UserDetailFragmentArgs

    View Slide

  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 で遷移する

    View Slide

  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)
    }
    }

    View Slide

  60. findNavController().navigate(

    UserListFragmentDirections.toUserDetail(userId))
    src/ /UserListFragment.kt
    Safe Args で遷移する

    View Slide

  61. src/ /UserDetailFragment.kt
    Safe Args でデータを受け取る
    private val args by navArgs()
    getUser(args.userId) Property Delegation
    alpha10 から

    View Slide

  62. xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/graph_main"
    app:startDestination="@id/dest_user_list">
    android:id="@+id/dest_user_list"
    android:name="com.example.UserListFragment">
    android:id="@+id/toUserDetail"
    app:destination="@id/dest_user_detail"/>

    android:id="@+id/dest_user_detail"
    android:name="com.example.UserDetailFragment">
    android:name="userId"
    app:argType="string" />


    Deep Link を使って遷移する res/navigation/graph_main.xml

    View Slide

  63. android:id="@+id/dest_user_list"
    android:name="com.example.UserListFragment">
    android:id="@+id/toUserDetail"
    app:destination="@id/dest_user_detail"/>

    android:id="@+id/dest_user_detail"
    android:name="com.example.UserDetailFragment">
    android:name="userId"
    app:argType="string" />


    Deep Link を使って遷移する res/navigation/graph_main.xml

    View Slide

  64. android:id="@+id/dest_user_list"
    android:name="com.example.UserListFragment">
    android:id="@+id/toUserDetail"
    app:destination="@id/dest_user_detail"/>

    android:id="@+id/dest_user_detail"
    android:name="com.example.UserDetailFragment">
    android:name="userId"
    app:argType="string" />



    Deep Link を使って遷移する res/navigation/graph_main.xml
    argument にマッピング

    View Slide

  65. Deep Link を使って遷移する app/AndroidManifest.xml










    View Slide

  66. Deep Link を使って遷移する app/AndroidManifest.xml











    View Slide

  67. Deep Link を使って遷移する app/AndroidManifest.xml
    Manifest Merger

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  73. 実装のまとめ
    • Naviation XML によってアプリ内遷移を実装する
    • で Destination を定義する
    • で 遷移(Action) を定義する
    • で受け取る引数を定義する
    • Safe Args で型安全なデータの受け渡しを⾏うことができる
    • と で Deep Link を実装できる

    View Slide

  74. その他の機能

    View Slide

  75. Toolbar と連携する res/layout/activity_main.xml

    android:id="@+id/toolbar"

    … />
    android:id="@+id/navHostFragment"
    android:name="androidx.navigation.fragment.NavHostFragment"
    app:navGraph="@navigation/graph_main"

    … />

    View Slide

  76. Toolbar と連携する res/layout/MainActivity.kt
    toolbar.setupWithNavController(
    findNavController(R.id.navHostFragment))
    • Start Destination のとき以外 Up ボタンを⾃動的に表⽰する
    • に指定した android:label を表⽰する

    View Slide

  77. src/ /UserListFragment.kt
    Fragmentをもっと使う
    android:label Toolbarと連携したときに title を⾃動でセットする

    argument で置き換えることもできる
    android:id="@+id/dest_user_detail"
    android:name="com.example.UserDetailFragment"
    android:label="User: {userId}">
    android:name="userId"
    app:argType="string" />

    View Slide

  78. Actionをもっと使う
    app:launchSingleTop
    app:popUpTo
    app:popUpToInclusive
    app:enterAnim
    app:exitAnim
    app:popEnterAnim
    app:popExitAnim
    Fragment切り替え時のアニメーション
    遷移時にスタックをどこまでPopするか
    SingleTop として起動するかどうか

    View Slide

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

    boolean
    boolean[]
    reference
    reference[]
    string
    string[]
    Parcelable / Serializable を実装したクラスとͦͷArray型

    (独⾃のクラスを指定することも可能)

    View Slide

  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

    View Slide

  81. マルチモジュールにおける遷移

    View Slide

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

    マルチモジュール構成が必須となる
    • ここで扱うのは, 機能ごとにモジュールが分かれており

    Fragment が各モジュールに分散しているような状態の構成

    View Slide

  83. First Second Third
    Base
    (Dynamic) Feature Modules

    View Slide

  84. First Second Third

    View Slide

  85. Feature modules 間の依存関係が複雑になる

    循環参照が発⽣する
    First Second Third

    View Slide

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

    View Slide

  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
    }
    }

    View Slide

  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

    View Slide

  89. DroidKaigi/droidkaigi-2019-app
    https://github.com/DroidKaigi/droidkaigi-2019-app
    • DroidKaigi 2019 公式アプリ
    • Navigation を使⽤しているマルチモジュールプロジェクト
    • Safe Args も使⽤している
    • 遷移は主に Fragment 間で⾏われる

    View Slide

  90. Navigation をどう使っているか
    • 各 feature module が共通で依存するモジュールに

    Navigation XML を配置している
    • コードは共通モジュールに⽣成され, それを各モジュールが

    参照するようになっている

    View Slide

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

    View Slide

  92. Navigation をどう使っているか
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    app:startDestination="@id/main">
    android:id="@+id/main"
    android:name="io.github.droidkaigi.confsched2019.session.ui.SessionPagesFragment"
    android:label="@string/session_label"
    >
    android:id="@+id/action_session_to_session_detail"
    app:destination="@id/session_detail"
    />
    ……
    別モジュールにあるため参照できない

    View Slide

  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をパースするためのクラス

    View Slide

  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);
    }

    View Slide

  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のインスタンスを⽣成

    実⾏時には各モジュールがマージされているため参照可能
    ⽣成されたクラスを使って遷移するとき

    View Slide

  96. Navigation — 別のアプローチ
    ❶ Dagger で interface のみを Feature Modules に提供して

    実際の遷移コードを application module で記述する (記述量が増える)

    ❷ 実⾏時だけ android:name が使われるなら実⾏時に

    クラスの参照を渡すこともできそう (ワークアラウンド的)
    Feature module が Application module の

    依存関係にあるときに限られる

    View Slide

  97. マルチモジュールまとめ
    • マルチモジュールで遷移するときはリフレクションを使う

    のが最も現実的な⽅法だと考えられる
    • Navigation を使うときは Navigation Graph を

    共通モジュールに配置するのが現状の解決策

    (droidkaigi-app-2019 の⽅法)

    View Slide

  98. まとめ
    • Navigation の登場によって, 画⾯遷移に関わる様々な機能を

    容易に実装できるようになった

    • Safe Args によってNavigationをもっと便利に使える
    • マルチモジュールにおける利⽤はワークアラウンド的である

    ことを理解した上で使ったほうがよい

    View Slide

  99. ありがとうございました
    @yt_hizi @yt-tkhs

    View Slide