-
Notifications
You must be signed in to change notification settings - Fork 0
Multi Module Setup
In a multi-module project, apply the Gradle plugin to every module that declares flags in featured { }. Each module manages its own flag declarations; the app module owns the single shared ConfigValues instance.
// :feature:checkout/build.gradle.kts
plugins {
id("dev.androidbroadcast.featured")
// … other plugins
}
featured {
localFlags {
boolean("checkout_new_flow", default = false) {
description = "Enable the redesigned checkout flow"
category = "checkout"
expiresAt = "2026-09-01"
}
}
}// :feature:profile/build.gradle.kts
plugins {
id("dev.androidbroadcast.featured")
}
featured {
localFlags {
boolean("profile_v2", default = false) {
description = "Enable the new profile screen"
category = "profile"
}
}
}The plugin registers a resolveFeatureFlags task per module and an aggregator task scanAllLocalFlags at the root project that collects flags across all modules.
# Resolve and aggregate flags across all modules
./gradlew scanAllLocalFlags
# Generate R8 rules for all Android modules
./gradlew generateFeaturedProguardRules
# Generate xcconfig across all modules
./gradlew generateXcconfigFeature modules declare their own ConfigParam objects but do not create ConfigValues. A single ConfigValues instance lives in the app module and is injected into feature modules through dependency injection:
// :app/src/main/kotlin/AppModule.kt (example with manual DI)
val configValues = ConfigValues(
localProvider = DataStoreConfigValueProvider(dataStore),
)
// Inject into :feature:checkout
val checkoutViewModel = CheckoutViewModel(configValues)Feature modules read flags through the generated extension functions on ConfigValues:
// In :feature:checkout
class CheckoutViewModel(private val configValues: ConfigValues) : ViewModel() {
val isNewFlowEnabled: StateFlow<Boolean> =
configValues.observe(GeneratedLocalFlags.checkoutNewFlow)
.map { it.value }
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), false)
}Each feature module registers its own flags with FlagRegistry in a debug-only block. The app module calls all of them before showing the debug UI:
// :feature:checkout/src/debug/kotlin/CheckoutDebugInitializer.kt
fun registerCheckoutFlags() {
FlagRegistry.register(GeneratedLocalFlags.checkoutNewFlow)
}// :app — Application.onCreate
if (BuildConfig.DEBUG) {
registerCheckoutFlags()
registerProfileFlags()
// … other modules
}The plugin is Configuration Cache safe but not isolated-projects safe. The wireToRootAggregator() call in FeaturedPlugin.kt accesses target.rootProject to register the scanAllLocalFlags aggregator, which violates the isolated-projects contract. This is intentional for 1.0.0-Beta and will be addressed in v1.1.0 by converting the aggregator wiring to a settings plugin. See Known Limitations for the tracking issue.