Android開発におけるモジュール設計とナビゲーション管理のベストプラクティス

モジュール設計の重要性

ラッパー(Wrapper)は必須ではありませんが、関連する関数を整理整頓しておくことが重要です。ラッパーを使用しても同量のコードになり、理解が難しくなる場合もあります。

以下にモジュール化の例を示します。

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun SettingsScreen(onBack: () -> Unit) {
    val context = LocalContext.current
    val fragmentManager = (context as? FragmentActivity)?.supportFragmentManager

    var hasNavigatedBack by remember { mutableStateOf(false) }
    // 二重クリック防止
    Scaffold(
        topBar = {
            TopAppBar(
                title = { Text("設定") },
                navigationIcon = {
                    IconButton(
                        onClick = {
                            if (!hasNavigatedBack) {
                                hasNavigatedBack = true
                                onBack()
                            }
                        }
                    ) {
                        Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = null)
                    }
                }
            )
        }
    ) { padding ->
        val containerId = remember { android.view.View.generateViewId() }
        val fragmentTag = remember(containerId) { "prefs_fragment_$containerId" }

        AndroidView<FragmentContainerView>(
            factory = { ctx ->
                FragmentContainerView(ctx).apply {
                    id = containerId
                }
            },
            modifier = Modifier
                .padding(padding)
                .fillMaxSize()
        )

        DisposableEffect(containerId) {
            val fragment = org.primftpd.prefs.FtpPrefsFragment()
            fragmentManager?.beginTransaction()
                ?.replace(containerId, fragment, fragmentTag)
                ?.commit()

            onDispose {
                fragmentManager?.findFragmentByTag(fragmentTag)?.let { existingFragment ->
                    fragmentManager.beginTransaction()
                        .remove(existingFragment)
                        .commitAllowingStateLoss()
                }
            }
        }
    }
}

これを以下のようにモジュール化します。

FragmentContainerScreen(
    "test1",
    {org.primftpd.ui.PftpdFragment()},
    {navController.popBackStack()}
)
//onNavigate("test1")を呼び出す
//。。。。。。

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun FragmentContainerScreen(
    title: String,
    fragmentFactory: () -> androidx.fragment.app.Fragment, // Fragmentのコンストラクタを渡す
    onBack: () -> Unit
) {
    val context = LocalContext.current
    val fragmentManager = (context as? FragmentActivity)?.supportFragmentManager
    var hasNavigatedBack by remember { mutableStateOf(false) }

    Scaffold(
        topBar = {
            TopAppBar(
                title = { Text(title) },
                navigationIcon = {
                    IconButton(
                        onClick = {
                            if (!hasNavigatedBack) {
                                hasNavigatedBack = true
                                onBack()
                            }
                        }
                    ) {
                        Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = null)
                    }
                }
            )
        }
    ) { padding ->
        val containerId = remember { android.view.View.generateViewId() }
        val fragmentTag = remember(containerId) { "fragment_$containerId" }

        AndroidView<FragmentContainerView>(
            factory = { ctx ->
                FragmentContainerView(ctx).apply {
                    id = containerId
                }
            },
            modifier = Modifier
                .padding(padding)
                .fillMaxSize()
        )

        DisposableEffect(containerId) {
            val fragment = fragmentFactory() // ここで渡されたコンストラクタを呼び出す
            fragmentManager?.beginTransaction()
                ?.replace(containerId, fragment, fragmentTag)
                ?.commit()

            onDispose {
                fragmentManager?.findFragmentByTag(fragmentTag)?.let { existingFragment ->
                    fragmentManager.beginTransaction()
                        .remove(existingFragment)
                        .commitAllowingStateLoss()
                }
            }
        }
    }
}

ただし、調査の結果、この機能はAndroidFragment()で代替可能であることが判明しました。変更する気力がなくなりました。

GitHubの高度な検索機能

何時間もかけて何も見つからない経験は誰でもしたことがあるでしょう 文字列の除外指定が可能です。例:repo:rD227/shizuku-ftp "extends Fragment" -で特定の単語を除外できます。

正規表現も使用できます (正規表現とは思えない見た目ですが 例:repo:rD227/shizuku-ftp /extends\s+Fragment\b/ \s+:1つ以上の空白文字に一致 \b:単語境界に一致し、Fragmentで始まる単語を検索(FragmentXXXXには一致しない)

最後にいくつかの技術的考察

EventBus(デバッグが困難)はViewModelを使用すべきです。FragmentManagerは@composableのようなプレビュー機能を提供できず、バックスタックの管理なども手動で行う必要があります。このようなリポジトリを継承する場合(または選択肢がなく、すべて自分で書き直す必要がある場合)は致し方ありません そのため、私はコピーリライトと完全な再実装の両方を試してみました

AG Fra Nav Activi
**管理方式** **手動** add/replace/commit **宣言的**。グラフィカルルーティングテーブルで管理 **システムレベル**。OSがActivityスタックを管理
**バックスタック** 手動 **自動管理**。論理的で予測可能 **自動管理**。LaunchModeの影響を受ける
**パラメータ渡し** Bundle (キー間違いのリスク) **Safe Args** (プラグインが自動生成、型安全) Intent (キー間違いのリスク)
**遷移アニメーション** トランザクション内で手動指定 XMLでグローバルに統一設定 overridePendingTransitionの使用が必要
**オーバーヘッド** 非常に軽量 軽量 (Fragmentベース) **比較的重い**。プロセス/システムのウィンドウ切り替えを伴う (AG)

タグ: Android JetpackCompose Fragment NavigationComponent GitHubAPI

5月22日 09:29 投稿