Skip to content

Multi Module Setup

kirich1409 edited this page May 19, 2026 · 2 revisions

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.

Apply the plugin per module

// :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"
        }
    }
}

Root aggregator task

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 generateXcconfig

Single ConfigValues in the app module

Feature 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)
}

Aggregating flags for the debug UI

Apply the dev.androidbroadcast.featured.application plugin in the app module to aggregate flags from all feature modules into a single GeneratedFeaturedRegistry.all list:

// :app/build.gradle.kts
plugins {
    id("dev.androidbroadcast.featured")
    id("dev.androidbroadcast.featured.application")
}

dependencies {
    featuredAggregation(project(":feature-checkout"))
    featuredAggregation(project(":feature-profile"))
    // For modules with enum flags, also a regular implementation dep
    // so the enum class is on the compile classpath:
    implementation(project(":feature-checkout"))
}

// Wire the generated source set:
kotlin.sourceSets.commonMain.kotlin.srcDir(
    layout.buildDirectory.dir("generated/featured/commonMain"),
)

The plugin reads the featured-manifest.json artifact published by each featuredAggregation dependency and generates:

object GeneratedFeaturedRegistry {
    val all: List<ConfigParam<*>> = listOf(/* all aggregated flags */)
}

Pass this list to the debug UI:

FeatureFlagsDebugScreen(
    configValues = configValues,
    registry = GeneratedFeaturedRegistry.all,
)

Primitive-only modules need only a featuredAggregation(...) dependency. Modules that declare enum flags also require a regular implementation(project(...)) so the enum class is available on the compile classpath at the app level.

Known limitation: Isolated projects

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.

Clone this wiki locally