Navigation Compose 中的动态功能模块

问题描述 投票:0回答:1

几天以来,我搜索了一个将 Jetpack Compose 和 Dagger 与动态功能模块结合使用的解决方案(例如 onDemand 模块:https://developer.android.com/guide/playcore/feature-delivery/on-demand)。

我发现了这个问题:https://issuetracker.google.com/issues/183677219并且我阅读了很多文档,所以我没有太多希望,但幸运的是......

你有什么想法吗?

android android-jetpack-compose dagger-2 dagger-hilt dynamic-feature-module
1个回答
0
投票

未测试,但会是这样的:

@Composable
private fun AppNavHost(
    modifier: Modifier = Modifier,
    navController: NavHostController,
    startDestination: String = "/home"
) {
    NavHost(
        modifier = modifier,
        navController = navController,
        startDestination = startDestination
    ) {

        composable("/home") {
            // Add your non-dynamic composable here
        }
        
        dynamicComposable(
            route = "/settings",
            featureName = "feature_settings",
            initializerClassName = "com.example.coolapp.feature.settings.DiKt",
            initializerMethodName = "loadModules"
        ) {
            val dynamic = koinInject<DynamicFeatureSettings>()
            dynamic.DynamicFeatureSettingsNavHost(
                modifier = Modifier.fillMaxSize(),
                onBack = {
                    navController.navigateUp()
                }
            )
        }
    }
}

// Interface for Dynamic feature composables. In this example, it's a Nav Host which is the
// entry point to the dynamic feature and handles internal navigation of the dynamic feature.
// I recommend doing like this, but it's possible to have multiples composables.
// Define this in the app module or another module that is dependency in both dynamic and app
// modules.
interface DynamicFeatureSettings {

    @Composable
    fun DynamicFeatureSettingsNavHost(
        modifier: Modifier,
        onBack: () -> Unit
    )
}

fun NavGraphBuilder.dynamicComposable(
    route: String,
    featureName: String,
    initializerClassName: String,
    initializerMethodName: String,
    arguments: List<NamedNavArgument> = emptyList(),
    deepLinks: List<NavDeepLink> = emptyList(),
    enterTransition: (@JvmSuppressWildcards
    AnimatedContentTransitionScope<NavBackStackEntry>.() -> EnterTransition?)? = null,
    exitTransition: (@JvmSuppressWildcards
    AnimatedContentTransitionScope<NavBackStackEntry>.() -> ExitTransition?)? = null,
    popEnterTransition: (@JvmSuppressWildcards
    AnimatedContentTransitionScope<NavBackStackEntry>.() -> EnterTransition?)? =
        enterTransition,
    popExitTransition: (@JvmSuppressWildcards
    AnimatedContentTransitionScope<NavBackStackEntry>.() -> ExitTransition?)? =
        exitTransition,
    content: @Composable AnimatedContentScope.(NavBackStackEntry) -> Unit
) {
    composable(
        route,
        arguments,
        deepLinks,
        enterTransition,
        exitTransition,
        popEnterTransition,
        popExitTransition
    ) {
        // You can simplify all this logic with a extension like NavGraphBuilder.composable
        // like dynamicComposable(route: String, featureName: String, clazz: String, method: String)

        val isDynamicFeatureDownloaded = remember { mutableStateOf(false) }
        val isDynamicFeatureReady = remember { mutableStateOf(false) }
        val context = LocalContext.current

        // Downloads the dynamic feature eagerly. You may prefer put a button to start donwloading the feature on request
        LaunchedEffect(key1 = isDynamicFeatureDownloaded) {
            SplitInstall(context).loadFeature(featureName) {
                onFeatureReady {
                    isDynamicFeatureDownloaded.value = true
                }
            }
        }

        if (isDynamicFeatureDownloaded.value) {
            // Loads the DI defined in :feature:settings that will produce DynamicFeatureSettingsNavHost
            // It needs reflection just like Fragments. It's just once though
            LaunchedEffect(isDynamicFeatureDownloaded.value) {
                // Maps to a file called di.kt in the root of your dynamic module
                val clazz = Class.forName(initializerClassName)
                val loadFunction = clazz.getDeclaredMethod(initializerMethodName)
                loadFunction.invoke(null)
                isDynamicFeatureReady.value = true
            }
        }

        if (isDynamicFeatureReady.value) {
            content(this, it)
        }

        // TODO loading, error, etc.
       
    }
}

然后在动态功能的文件中初始化 DI。这是 Koin 的示例。

在 Dagger 中,我相信您不需要初始化动态功能,只需在应用程序模块上定义它们即可,在这种情况下,您可以修改dynamicComposable并删除初始化函数。我没有测试所以不确定编译器是否会出现问题。

// in the dynamic module file com.example.coolapp.feature.settings.di.kt
fun settingsFeatureModule() = module {

    // Add other dependencies too, like use cases and repositories

    // ...
    single<DynamicFeatureSettings> {
        object : DynamicFeatureSettings {
            @Composable
            override fun DynamicFeatureSettingsNavHost(modifier: Modifier, onBack: () -> Unit) {
                FeatureSettingsNavHost(modifier = modifier, onBack = onBack)
            }
        }
    }
}

private val lazyLoadModules = lazy { GlobalContext.loadKoinModules(settingsFeatureModule()) }
fun loadModules() = lazyLoadModules.value

我希望如果不符合您的需求至少可以帮助您找到解决方案。

© www.soinside.com 2019 - 2024. All rights reserved.