Jetpack Composeを本番投入しました(画面遷移編)

リサーチ・アンド・イノベーションの高田(tfandkusu)です。Androidエンジニアをやっています。前回Android版CODEアプリにおけるJetpack Compose使用画面でのマルチモジュール構成について解説しましたが、今回は画面遷移における技術選定について解説いたします。

3部構成

この記事は3部構成の第3部です。

Jetpack Composeにおける画面遷移の方法一覧

DroidKaigi 2021でのセッション「プロダクトレベルで必要になる Jetpack Compose テクニック」では、3種類の画面遷移の方法が紹介されていました。

Activity 画面遷移
複数Activity Activity遷移、Fragment差し替え
1 Activity Fragment差し替え
1 Activity Compose

結論

CODEでは複数のActivityをstartActivityメソッドやActivity Result APIで遷移することで、画面遷移を実現しています。言い換えると1 Activityは1画面分のComposable関数しか持っていません。そのような技術選定になった経緯を次の節から解説します。

複数Activityになった経緯

Jetpack Composeを採用する前の話

Jetpack Composeを採用する前から複数Activityの構成になっていました。2020年にホーム画面をリニューアルしたときに、Jetpack Navigationを一時期採用していましたが、最終的に複数Activityの構成になりました。理由はホーム画面はアプリ内広告のAdMobを設置する画面であったことで、AdMobのバナー広告はアドネットワークからダウンロードしたコンテンツをViewの中に持つ作りに対して、Jetpack Navigationは前の画面に戻ったときもFragment内のViewが再生成される挙動のため、相性が良くなかったからです。Jetpack Navigationを使うと、AdMobバナー広告を持つ画面に戻ったときに再びアドネットワークからコンテンツをダウンロードすることを防ぐために、AdMobのViewを剥がしてまた付けるという分かりにくいコードを含むことになります。それがJetpack Navigationを使うことのメリットを上回ると思わなかったので、結局複数Activityを使い続けていました。

とりあえずNavigation Composeを採用

当初はNavigation Composeを使っていました。前々回の記事で繰り返し一括登録機能の2画面(一覧画面、編集画面)をJetpack Composeで開発したことを解説しましたが、その2画面をこのような構成でNavigation Composeで遷移していました。ViewModelの注入はkoin-androidx-composeで行っていました。

Navigation Compose

/**
 * 一覧画面
 */
private const val LIST_PATH = "list"
/**
 * 編集画面
 */
private const val EDIT_PATH = "edit/{id}"
/**
 * 繰り返し一括登録画面の一覧と編集の2画面を含むComposable関数
 *
 * @param subscriptionViewModel SubscriptionActivityを制御するためのViewModel
 */
@Composable
fun SubscriptionContent(
    subscriptionViewModel: SubscriptionViewModel
) {
    val navController = rememberNavController()
    NavHost(navController = navController, startDestination = LIST_PATH) {
        composable(LIST_PATH) {
            // 一覧画面
            val viewModel = getViewModel<SubscriptionListViewModel>()
            SubscriptionListScreen(viewModel = viewModel, navigateToEdit = { id ->
                // 編集画面に遷移する
                navController.navigate("edit/$id")
            }) {
                // 前のActivity(家計簿設定画面)に戻る
                subscriptionViewModel.backToHouseholdSetting()
            }
        }
        composable(
            EDIT_PATH,
            arguments = listOf(
                navArgument("id") {
                    type = NavType.LongType
                }
            )
        ) { backStackEntry ->
            // 編集画面
            val id = backStackEntry.arguments?.getLong("id") ?: 0L
            val viewModel = getViewModel<SubscriptionEditViewModel>()
            SubscriptionEditScreen(viewModel = viewModel, id = id) {
                // 一覧画面に戻る
                navController.popBackStack()
            }
        }
    }
}

Firebase Analyticsによる計測を優先して、複数Activityになった

しかし最終的にはこのように1Activityに1画面分のComposable関数を持つ構成となりました。

1Activityに1画面分のComposable関数

そうなった理由はFirebase Analyticsです。Firebase Analyticsには画面遷移の情報を自動で送信する機能があります。イベント名がscreen_viewでActivityクラス名がfirebase_screen_classパラメータで送信されます。このような情報はデータ分析エンジニアやAndroidエンジニアが利用実態を把握して、改修方針を検討するために利用されます。

参考 スクリーン ビューを測定する

BigQueryにエクスポートしたFirebase Analyticsの情報

しかしNavigation Composeでは画面遷移の情報を自動で送信しません。よって当初はNavController.OnDestinationChangedListenerを使用して独自に送信しようと考えていました。

@Composable
fun SubscriptionContent(
    subscriptionViewModel: SubscriptionViewModel
) {
    val navController = rememberNavController()
    navController.addOnDestinationChangedListener { _, destination, _ ->
        when (destination.route) {
            LIST_PATH -> {
                // 一覧画面への遷移イベントの送信
                // screen_view(firebase_screen_class=SubscriptionListScreen)
                subscriptionViewModel.onList()
            }
            EDIT_PATH -> {
                // 編集画面への遷移イベントの送信
                // screen_view(firebase_screen_class=SubscriptionEditScreen)
                subscriptionViewModel.onEdit()
            }
            else -> {
            }
        }
    }
    // 略
}

しかし、編集画面(SubscriptionEditScreen)から、Jetpack Compose導入以前から存在したActivityであるカテゴリ選択画面(CategorySelectActvity)を呼ぶ実装があり、そこで新たな問題が発覚しました。

こちらの動画のような画面遷移を想定します。

  • 一覧画面
  • 編集画面
  • カテゴリ選択画面
  • 編集画面

繰り返し一括登録画面の画面遷移

理想としては、このように画面遷移を送信したかったです。

  • SubscriptionListScreen (一覧画面)
  • SubscriptionEditScreen (編集画面)
  • CategorySelectActivity (カテゴリ選択画面)
  • SubscriptionEditScreen (編集画面)

しかし実際はこのような送信になってしまいました。

  • SubscriptionListScreen (一覧画面)
  • SubscriptionEditScreen (編集画面)
  • CategorySelectActivity (カテゴリ選択画面)
  • SubscriptionActivity (一覧画面と編集画面をNavigation Composeで持つActivity)

SubscriptionEditScreenでCategorySelectActivityから戻ってきた時もSubscriptionEditScreenに遷移したイベントを送ることで解決しようと思いましたが、実装漏れを起こしてデータ分析エンジニアに迷惑をかけてしまう懸念がメンバーから上がりました。よって最終的にはチームとして、計測を重視してNavigation Composeは使わないという結論になりました。

まとめ

今回はCODEにおけるJetpack Compose使用画面の画面遷移について、技術選定の様子を解説しました。これが正解ということでは無く、選択当時のメンバーによるその時点での状況を踏まえたディスカッションの結果、複数Activityによる画面遷移になりました。

これまで3回にわたり、Jetpack Compose本番投入の様子を紹介してきました。本番投入はうまくいったので、新画面ではJetpack Composeを積極的に使用していく方針です。そして、まだまだベストプラクティスを模索しながら実装している状態です。メンバーの増員や新たな課題、Kotlin Multiplatform Mobile導入機運の高まりによっては、また別の技術やアーキテクチャを採用する可能性があります。

Androidエンジニア募集中

弊社リサーチ・アンド・イノベーションでは、例えば技術選定をディスカッションしたいAndroidエンジニアを募集しています。

採用情報 - Androidエンジニア