はじめに
こんにちは。クラシルのAndroidアプリチームのテックリードのうめもりです。
この記事はdelyアドベントカレンダー17日目の記事となっています。
16日目である昨日はshita-shunさんのswift-algorithmsを初めて触ってみた。という記事でした。ご興味ある方は是非読んでみてください。
Jetpack Composeでは画面という概念が大きく変わる
Jetpack Composeの正式版がリリースされて半年ほど経過し、すでに本番プロダクトに投入している方や、現行のプロダクトへの導入を検討しようとしている方などいらっしゃると思います。弊社でも向こう1年以内くらいの導入を検討しており、現在は実際に導入するとしてどのような設計に移行するかを検討している段階です。
実際にJetpack Composeを採用するにあたり、Jetpack Composeのような宣言的UIツールキットを使ったUIプログラミングに移行するためには、既存のUIプログラミングの考え方はある程度捨て去る必要があります。そして、それは画面や、画面遷移という概念においても同じで、今までのAndroidのUIプログラミングの考え方とは違う考え方を採用する必要があります。
Androidにおける画面遷移とは何か?
さて、Jetpack Composeにおける画面遷移とは何なのかという話をする前に、そもそもAndroidにおける画面遷移とは何なのかを振り返っておきましょう。
Androidアプリケーションにおいて、画面を表現する手段は様々なものが用意されています。
Activity
Androidアプリケーションにおける最も基本的な画面を表現する手段はActivityでしょう。アプリケーションを立ち上げる時の初期画面としてAndroidManifestに指定することで、特定のActivityを起動して画面表示を行ったり、
this@Activity.startActivity(intent)
といったコードで画面遷移を行うことが出来ます。
そして、AndroidOSがタスクという単位でアクティビティのバックスタックを管理し、バックボタンなどを使ったナビゲーションを行うことが出来るようになっています。
Fragment
Android 3.0から導入されたのがFragmentです。Fragmentは画面の一部を表現するコンポーネントや、画面全体を表現するコンポーネントとして使うことができます。
Activityの中で、
val tx = this@Activity.getFragmentManager().beginTransaction() tx.replace(R.id.fragment_container, SampleFragment()) tx.commit()
といったコードでFragmentを表示したり画面遷移することが出来、FragmentManagerがバックスタックを管理し、バックボタンなどを使ったナビゲーションを行うことが出来ます。
今ではJetpack Navigationを使うことで、Activity内でのFragmentを使った画面遷移がやりやすくなりましたね。
今表示されている画面が何かというViewの状態をコントロールするのが画面遷移だった
ここまでActivityやFragmentといったAndroidにおける画面の管理を行うための仕組みを振り返ってきましたが、ActivityにしろFragmentにしろ、Activityであれば今表示されているActivityが何か、Fragmentであれば今表示されているFragmentが何か、というViewの状態があり、それを変化させることを画面遷移と呼んできました。
では、Jetpack Composeにおける画面遷移とはなんでしょうか?
Jetpack Composeにおける画面遷移とは何か?
Composable functionは (State) -> Composition という変換を行う関数
Jetpack Composeが今までのUIプログラミングと大きく違うのは、Composable functionの内部では副作用を発生させずに、引数とrememberされたStateのみでCompositionを構築することを要求する点にあります。この仕組みによって常に(State) -> Compositionという参照透過な関数を適用することで、宣言的にUIを構築することが出来るようになっています。
重要なのは、必ずStateを表現するオブジェクトが先にあり、それを使ってViewが構築されている、という点です。そして、それは画面遷移においても同様なのです。
Navigation ComposeのNavHostの実装をのぞき見してみる
Jetpack Composeでは画面遷移にNavigation Composeを使うことが推奨されています。Navigation Composeは、Navigation componentをJetpack Compose上で使うことが出来るようにするライブラリーですが、実装自体は非常に薄いものになっています。
Navigation ComposeではNavHostというfunctionを使うことでナビゲーショングラフを定義することが出来ます。Android公式のNavigation Composeのコードを引用すると、このようになっています。
NavHost(navController = navController, startDestination = "profile") { composable("profile") { Profile(/*...*/) } composable("friendslist") { FriendsList(/*...*/) } /*...*/ }
https://developer.android.com/jetpack/compose/navigation
NavHostの実装をのぞき見してみましょう。
細かい実装の話は割愛しますが、重要な部分はこの
val backStack by composeNavigator.backStack.collectAsState()
部分で、Navigatorから更新されたバックスタックが通知されるたびに、NavHostがrecomposeされるようになっています。
(lastEntry.destination as ComposeNavigator.Destination).content(lastEntry)
そして、NavHostがrecomposeされる度にこの処理が実行されます。ComposeNavigator.Destinationのコードは以下のようになっており、
public class Destination( navigator: ComposeNavigator, internal val content: @Composable (NavBackStackEntry) -> Unit ) : NavDestination(navigator)
該当の個所では末尾にあるBackStackEntryを引数にComposable functionを呼び出しています。つまり、NavHostはrecomposeされる度に
(NavBackStackEntry) -> Composition
となるComposable functionを呼び出すことで、画面を更新しているのです。
画面の実態はBackStack
Navigation Composeでは、NavigatorからBackStackを取り出し、最終的にはリストの末尾にあるBackStackEntryを使って画面を構築しています。 つまり、画面遷移を行う側として実際に行っていることは、バックスタックのリストを操作することだけなのです。
必ずバックスタックを表現するオブジェクトが先にあり、それを使って画面が構築されている(結果的に画面遷移が行われる)、というのがJetpack Composeにおける画面遷移、といえます。
画面遷移の構成要素
NavHostのコードはわずか100行弱のコードですが、
この中に画面遷移に必要な全ての要素が入っています。
- バックスタックを表現するオブジェクトのState
- バックボタンのコントロール
- トランジション
もちろんJetpack Navigation並みの機能(ディープリンク対応など)を実現するにはさらなる実装が必要になりますが、基本的にはこの3つがそろっていれば画面遷移を行うことが出来るようになるでしょう。もし興味がある方は、冬休みに自作の画面遷移ロジックを書いてみてはいかがでしょうか。
おわりに
クラシルのAndroidチームでは、生産性の高いアーキテクチャを維持・アップデートしつつも楽しくプロダクト開発できる人、したい人を求めています。 今回の記事を読んで興味が出てきた方、是非お話ししましょう! 下記の採用リンクから応募いただいてもOKですし、Twitterから気軽にDM頂けてもOKです!