Feature Gates Usage Guide
Feature gates (also known as feature toggles or feature tweaks) allow you to turn pieces of functionality on or off at runtime without shipping a new build. This guide explains what they are, when to use them, and how to implement a new tweak inside the Baselines architecture.
What Is a Feature Gate?
A feature gate is a small abstraction that tells the rest of the app whether a feature is currently
available. Under the hood it can talk to remote config, preferences, or any other store, but the
public API always looks the same: check if the feature is enabled, optionally flip its value, and
react in the UI based on the answer. In Baselines this contract lives in toolkit/feature-tweak module
and is modeled by:
AppFeatureenumFeatureTweakinterfaceTweaksfacade
When Are Feature Tweaks Needed?
Use a tweak whenever you want control over exposing a feature without republishing the app. Typical cases include:
- Gradually rolling out a capability to internal testers before going GA
- Keeping experimental UI behind a guard so it can be disabled quickly
- Building tooling (like the in-app Playground) where product or QA can toggle behaviors while exercising the app
If something must be safe to disable instantly—especially during early development—wrap it in a feature tweak.
Step-by-Step: Implement a New Tweak
Follow these three steps whenever you introduce a new gated feature. The snippets below assume we are
adding a Profile feature flag.
1️⃣ Extend FeatureTweak
Create a class that implements FeatureTweak for your feature. The implementation can inject any
dependencies it needs (config, storage, etc.) to make enabled() and tweak(Boolean) functions
work.
kotlin1@Inject2// You can add `@SingleIn(AppScope::class)` to make it behave as a singleton,3// so it can store your runtime variables in memory, like `cachedStated` below.4// In other cases `@SingleIn(AppScope::class)` is redundant.5@SingleIn(AppScope::class)6class ProfileFeatureTweak(7 private val appConfigManager: AppConfigManager,8) : FeatureTweak {910 private var cachedState = true1112 override suspend fun enabled(): Boolean {13 val appInfo = appConfigManager.appConfig.first().info14 return appInfo.debug && cachedState15 }1617 override suspend fun tweak(enabled: Boolean) {18 cachedState = enabled19 }20}
2️⃣ Provide the tweak instance into the map
Tweak implementations are exposed through DI as key/value pairs where the key is an AppFeature and
the value is the FeatureTweak. Contribute your pair so the Tweaks class can pick it up.
kotlin1@ContributesTo(AppScope::class)2interface ProfileTweaksProvider {34 @Provides5 @IntoMap6 fun provideProfileTweak(7 impl: ProfileFeatureTweak,8 ): Pair<AppFeature, FeatureTweak> = AppFeature.PROFILE to impl9}
Add the new constant to AppFeature if it does not yet exist.
3️⃣ Use Tweaks to read or flip the state
Inject Tweaks wherever the UI needs to react to the feature gate. View models typically read the
value inside a mutableState block and invoke tweak() when the user flips a toggle. HomeViewModel
and the Playground FeatureTweakViewModel provide concrete examples.
kotlin1@Inject2@ContributesIntoMap(3 scope = AppScope::class,4 binding = binding<ViewModel>()5)6@ViewModelKey(ProfileViewModel::class)7class ProfileEntryViewModel(8 private val tweaks: Tweaks,9) : BaselineViewModel<ProfileUiEvent, ProfileUiState>() {1011 private val profileState = mutableState(false) {12 tweaks.enabled(AppFeature.PROFILE)13 }1415 @Composable16 override fun state(): ProfileUiState {17 val enabled by profileState.collectAsStateWithLifecycle()18 return ProfileUiState(enabled) { event ->19 when (event) {20 ProfileUiEvent.ToggleProfile -> launch {21 tweaks.tweak(AppFeature.PROFILE, !enabled)22 // If your current UI needs to react to the tweak change,23 // simply recreate the state to pass the new state in.24 profileState.recreate()25 }26 }27 }28 }29}
With these pieces in place, the new feature gate becomes available throughout the app and can be controlled through the Playground tweaks UI or any other custom surface.