几天以来,我搜索了一个将 Jetpack Compose 和 Dagger 与动态功能模块结合使用的解决方案(例如 onDemand 模块:https://developer.android.com/guide/playcore/feature-delivery/on-demand)。
我发现了这个问题:https://issuetracker.google.com/issues/183677219并且我阅读了很多文档,所以我没有太多希望,但幸运的是......
你有什么想法吗?
未测试,但会是这样的:
@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
我希望如果不符合您的需求至少可以帮助您找到解决方案。