Feature flags are an essential tool for controlling feature rollouts, enabling A/B testing, and managing experimental features without requiring a new app release. In this guide, we’ll implement feature flags in an Android app using Jetpack Compose, DataStore Preferences, and app flavors.
Why Use Feature Flags?
- Feature flags allow you to:
- Enable or disable features dynamically
- Deploy incomplete features safely
- Roll out features to specific user groups
- Experiment with new ideas without affecting all users
- Now, let's walk through the implementation!
Step 1: Setting Up App Flavors
We need to define app flavors in the build to manage different feature flag configurations based on the app version (e.g., free
, paid
, beta
).gradle file:
android {
flavorDimensions "version"
productFlavors {
free {
dimension "version"
}
paid {
dimension "version"
}
}
}
This setup allows us to maintain separate feature flag configurations for each flavor.
Step 2: Adding DataStore for Persistent Storage
Jetpack DataStore (Preferences) is ideal for storing feature flags because it is lightweight and supports asynchronous data reading and writing.
First, add the dependency to your build.gradle
:
dependencies {
implementation "androidx.datastore:datastore-preferences:1.0.0"
}
Next, create a DataStore helper class to manage feature flags:
private val Context.dataStore by preferencesDataStore(name = "feature_flags")
class FeatureFlagManager(
private val context: Context
) {
companion object {
val FEATURE_X = booleanPreferencesKey("feature_x")
}
val isFeatureXEnabled: Flow<Boolean> = context.dataStore.data
.map { preferences -> preferences[FEATURE_X] ?: false }
suspend fun setFeatureX(enabled: Boolean) {
context.dataStore.edit { it[FEATURE_X] = enabled }
}
}
This class provides a Flow to observe feature flag changes and a function to update flags.
Step 3: Providing Different Feature Flags for Each Flavor
We’ll create separate JSON files for each flavor to predefine feature flags.
free/src/main/assets/feature_flags.json
{
"feature_x": false
}
paid/src/main/assets/feature_flags.json
{
"feature_x": true
}
Then, we can load these values at app startup.
fun Context.loadFeatureFlagsFromAssets(): Map<String, Boolean> {
val assetName = "feature_flags.json"
return try {
val inputStream: InputStream = assets.open(assetName)
val json = inputStream.bufferedReader().use { it.readText() }
val jsonObject = JSONObject(json)
jsonObject.keys().asSequence().associateWith { jsonObject.getBoolean(it) }
} catch (e: Exception) {
emptyMap()
}
}
Step 4: Using Feature Flags in Jetpack Compose
We can now use the FeatureFlagManager to control UI elements dynamically in
@Composable
fun FeatureXScreen(featureFlagManager: FeatureFlagManager) {
val isFeatureXEnabled by featureFlagManager.isFeatureXEnabled.collectAsState(initial = false)
Column {
Text("Feature X is ${if (isFeatureXEnabled) "Enabled" else "Disabled"}")
if (isFeatureXEnabled) {
Button(onClick = { /* Do something */ }) {
Text("Try Feature X")
}
}
}
}
Wrapping Up
By implementing feature flags with DataStore and app flavors, we gain greater flexibility in managing features dynamically. This approach enables safer releases and better control over feature rollouts. 🚀
Top comments (1)
Thank you for sharing this !