Studyplus Engineering Blog

スタディプラスの開発者が発信するブログ

JetpackのNavigationで開始画面を変更する方法

こんにちは、モバイルクライアントグループの隅山です。 去年からNavigationを導入してきましたが、導入する際に画面遷移周りで課題があったのでその課題について紹介します。

画面遷移の課題

スタディプラスのAndroidアプリはマルチモジュール構成となっているため、Navigationは機能モジュールごとに導入する方針としています。 Navigationを導入する上で、遷移元によって異なる画面を表示する機能の場合、どう実装するのかが課題として浮上しました。

一例としてユーザーが交流するコミュニティ機能をみてみます。この機能に遷移してくるパターンが以下の4つあり、それぞれコミュニティの最初の表示画面が異なります。

  • トップ画面→コミュニティ検索画面
  • ユーザ情報画面→コミュニティ一覧画面
  • 通知画面→コミュニティ詳細画面
  • 通知画面→コミュニティトピック詳細画面

それぞれ開始画面が異なる場合にNavigationでどう実装するか3つの方法を紹介します。

解決策1:StartDestinationを用いる

まず、一つ目にStartDestinationを用いる場合です。

StartDestinationとはNavGraphクラスの必須パラメータの一つです。このプロパティは開始画面を示しています。 NavGraphにsetStartDestination(@IdRes startDestId: Int)で開始画面を変更可能となっているため、下記のように変更することができます。

val navController = findNavController(R.id.nav_host_fragment)
val navGraph = navController.navInflater.inflate(R.navigation.community_nav_graph)

navController.graph = navGraph.apply {
    // StartDestinationで開始画面指定
    startDestination = R.id.communitySearchResultFragment
}

StartDestinationを用いる場合のメリットは、コード上で簡単に設定でき、nav_graph.xmlを変更する必要がない点です。 このため、設計にかかわらずすぐに導入したい場合に有効です。

使い所 メリット デメリット
開始画面が多い場合 nav_graph.xmlを変更する必要がない 特になし

解決策2:GlobalActionを用いる

続いて、二つ目はGlobalActionを用いる場合です。

GlobalActionとは同一NavGraph内であればどこからでも利用できる遷移のことです。 GlobalActionで最初に表示したい画面へ遷移することによって開始画面を変更することが可能です。詳しいコードは以下のようになります。

<!-- GlobalAction作成 -->
<action
    android:id="@+id/actionToSearchResult"
    app:destination="@id/communitySearchResultFragment" />
val navController = findNavController(R.id.nav_host_fragment)

// GlobalActionで開始画面に遷移
navController.navigate(
    ActionOnlyNavDirections(R.id.actionToSearchResult)
)

GlobalActionは汎用的な画面へ遷移する場合に効果的な機能です。そのため、GlobalActionを用いる方法はそのような画面が開始画面の場合に有効です。 ただ、GlobalActionを多用しすぎると、GUI上で遷移関係が追いにくくなってしまうため注意が必要です。

使い所 メリット デメリット
汎用的な画面への遷移の場合 遷移が複雑でも遷移図がシンプル GUI上で遷移関係が追いにくい

GlobalActionを用いる場合の注意

※NavGraphのstartDestinationへ遷移した直後にGlobalActionで指定位置へ遷移しているため、UI的には開始画面が変更できているが、コード上は2回の遷移が起きています。 そのため、GlobalActionの遷移先から戻った場合、NavGraphのstartDestinationに戻ります。

解決策3:NavGraphの分割

最後に、NavGraphを分ける場合です。

NavGraphの分割では開始画面ごとにNavGraphを切り分けて、最初に表示したい画面のNavGraphをNavControllerに設定することで開始画面を変更できます。 分割したNavGraphはNestedGraphを用いることでNavGraphを跨いだ遷移が実現できます。

NestedGraphの説明は今回の内容と少し外れてしまうため、説明を割愛させていただきます。

<!-- community_nav_graph_1.xml -->
<fragment
    android:id="@+id/communitySearchFragment"
    android:name="xxx.xxx.CommunitySearchFragment">

    <!-- NestedGraphで遷移するアクション -->
    <action
        android:id="@+id/actionToSearchResult"
        app:destination="@+id/community_nav_graph_2" />

</fragment>

<include app:graph="@navigation/community_nav_graph_2" />
<!-- community_nav_graph_2.xml -->
<fragment
    android:id="@+id/communitySearchResultFragment"
    android:name="xxx.xxx.CommunitySearchResultFragment" />
val navController = findNavController(R.id.nav_host_fragment)

// 開始したい画面のNavGraphを設定
if (isStart1) {
    navController.setGraph(R.navigation.community_nav_graph_1)
} else {
    navController.setGraph(R.navigation.community_nav_graph_2)
}

NavGraphの分割のメリットは、NavGraphスコープのViewModelでデータ共有できることです。 デメリットはNavGraphが分割されるため、GUI上で機能全体の遷移が追いにくい点です。

使い所 メリット デメリット
NavGraphViewModelでデータ共有したい場合 データの受け渡しが不要 GUI上で機能全体の遷移が追いにくい

採用した解決策

- 開始地点を変更 適した設計
StartDestination 実装可能 どんな設計でも有効
GlobalAction 実装可能(BackStackに注意) 汎用的な画面から開始したい場合
NavGraphの分割 実装可能 ViewModelでデータ共有したい場合

スタディプラスのコミュニティ機能では、解決策3のNavGraphの分割を採用しました。

開始地点を変更するだけならどの解決策でも実装可能なのですが、NavGraphの分割のメリットであるNavGraphViewModelを利用するためです。 コミュニティ画面ではサーバからコミュニティデータ取得して各画面でそのデータを表示することが多く、できるだけデータ取得回数を減らすためにViewModelで共有しました。

今回3つの方法を紹介しましたが、どれを採用すべきかは設計次第であるためどれがいいとは一概に言えません。 現に、スタディプラスのAndroidアプリ内では機能ごとに採用している解決策が異なっています。 どの解決策にすべきかは設計と実際に実装してみてご検討ください。

終わりに

Navigationで画面遷移を導入する際の課題を紹介しましたが、上記の事例以外では画面遷移で詰まることなく、むしろ画面遷移がGUIで簡単に実装することができました。

これからアプリを作る方はもちろん、大規模なアプリの開発に携わっている方も機能ごとや部分部分に分けて導入可能なので、是非導入してみてください。