Diary

Diary

日々学んだことをアウトプットする場として初めてみました

マルチモジュールでの compose navigation におけるベストプラクティス(Android DevSummit)

Compose の nagitaion における multi module 対応についての内容を youtube で見ました("Type safe, multi-module best practices with Navigation Compose")。

この中で 5 つのベストプラクティスが紹介されていたため、簡単に紹介します。

Compose のスクリーンでは State を入力とし events を出力とせよ

@Composable
fun ColumnWithLine(
    uiState: MemberListUiState,
    onNavigateToParticipantList: (conversationId: String) -> Unit,
) {
    ...
}

Screen はデータがどこから来たかを気にするべきではなく、例えば viewmodel であったり、適当な値が入ってようが動くようにするべきである。
また、誰がイベントを受け取るかも気にするべきではない。

独立性を高めて、テストを容易にするメリットもある。

画面ごとに feature module を分解した際に、app module 等で Navigation の設定をするかと思います。
『その Navigation のグラフ設定も、feature module 側に含めよ』ということです。

ルートの設定をするのは、使われる側(モジュール)の役目ということですね。

feature/settings/navigation/settings.kt

const val settingsRoute = "settings_route"

// NavGraphBuilder にメソッドを生やす
fun NavGraphBuilder.settingsScreen(
    onThemeChanged: (String) -> Unit,
) {
    composable(route = settingsRoute) {
        SettingsScreen {
            onThemeChanged(it)
        }
    }
}

例えば app 側で呼び出すには以下のようにします。

@Composable
fun BottomNavHost(
    navHostController: NavHostController,
    onThemeChanged: (String) -> Unit
) {
    NavHost(
        navController = navHostController,
        startDestination = BottomNavItem.Home.route
    ) {
        homeScreen()

        // こんな感じで使える!
        settingsScreen(onThemeChanged)

必要な Public API のみを公開せよ

Argument の設定等、公開しなくてもいいものは internal をつけるなどして、他モジュールに公開しないようにします。

また、VisibleForTesting annotation を使ってテストように公開することも可能です。

@VisibleForTesting
internal const val authorIdArg = "authorId"

internal class AuthorArgs(val authorId: String) {
    constructor(savedStateHandle: SavedStateHandle, stringDecoder: StringDecoder) :
        this(stringDecoder.decodeString(checkNotNull(savedStateHandle[authorIdArg])))
}

Module 構造と Graph 構造はセットに考えるべき

module を分割しその公開 API を決めることで、Graph 構造も定まるようにするべきです。

特に、module 間で遷移することはやめるべきであり、遷移メソッドを公開し上位メソッドから呼び出すように修正します。

公開する遷移のためのメソッドを NavController に生やす。

fun NavController.navigateToMemberDetail(member: Member) {
    this.navigateUp()
    this.navigate(
        memberDetailRoute
                + "/$memberJson=${getJsonFromMember(member)}"
    )
}

上位モジュールから呼び出す。

NavHost(
    navController = navHostController,
    startDestination = BottomNavItem.Home.route
) {
    memberListScreen {
        navHostController.navigateToMemberDetail(it)
    }

リソースを随時確認せよ

最後のベストプラクティスは、以下リソースを随時確認しよう、ということです。

おわりに

NowInAndroid app: sample app のアプリは非常に完成度高そうなので、積極的に参考にしていきたいです。