Design and implement a complete feature flag system for a new "AI-powered article summarization" feature, including the rollout plan and kill switch.
The Feature
You're shipping an "AI Summary" button on article detail screens. When tapped, it generates a 3-sentence summary using an AI API. The API is expensive and may have reliability issues.
Rollout requirements:
- Internal testing: company employees only
- Beta: 5% of users
- Production: 25% → 50% → 100% over 3 weeks
Step 1: Firebase Remote Config Setup
In Firebase Console → Remote Config → Create parameters:
Parameter: ai_summary_enabled
Default value: false
Description: "Controls AI article summarization feature"
Conditions (in priority order):
1. "Internal testers" → user property: tester_group = "internal" → value: true
2. "5% beta" → random percentile 0–5% → value: true
3. Default → false
Step 2: Implement FeatureFlags Interface
interface FeatureFlags {
val isAiSummaryEnabled: Boolean
val aiSummaryMaxLength: Int
val aiSummaryModel: String // allows switching AI model remotely
}
class FirebaseFeatureFlags(
private val remoteConfig: FirebaseRemoteConfig
) : FeatureFlags {
override val isAiSummaryEnabled
get() = remoteConfig.getBoolean("ai_summary_enabled")
override val aiSummaryMaxLength
get() = remoteConfig.getLong("ai_summary_max_length").toInt().coerceIn(50, 500)
override val aiSummaryModel
get() = remoteConfig.getString("ai_summary_model").ifBlank { "gpt-4o-mini" }
}
Step 3: Use Flags in UI
@Composable
fun ArticleDetailScreen(
article: Article,
viewModel: ArticleViewModel = hiltViewModel(),
flags: FeatureFlags = get()
) {
var summaryState by remember { mutableStateOf<SummaryState>(SummaryState.Idle) }
Column {
ArticleContent(article)
if (flags.isAiSummaryEnabled) {
AiSummarySection(
state = summaryState,
onSummarize = {
summaryState = SummaryState.Loading
viewModel.summarize(
article,
maxLength = flags.aiSummaryMaxLength,
model = flags.aiSummaryModel
)
}
)
}
}
}
sealed class SummaryState {
object Idle : SummaryState()
object Loading : SummaryState()
data class Success(val summary: String) : SummaryState()
data class Error(val message: String) : SummaryState()
}
Step 4: ViewModel with Rate Limiting
The AI API is expensive — add client-side rate limiting:
class ArticleViewModel(
private val repository: ArticleRepository,
private val aiService: AiSummaryService,
private val flags: FeatureFlags
) : ViewModel() {
private var lastSummarizeTime = 0L
private val minSummarizeIntervalMs = 30_000L // 30 seconds between requests
fun summarize(article: Article, maxLength: Int, model: String) {
val now = System.currentTimeMillis()
if (now - lastSummarizeTime < minSummarizeIntervalMs) {
_summaryState.value = SummaryState.Error("Please wait before requesting another summary")
return
}
viewModelScope.launch {
_summaryState.value = SummaryState.Loading
try {
val summary = aiService.summarize(article.body, maxLength, model)
lastSummarizeTime = System.currentTimeMillis()
_summaryState.value = SummaryState.Success(summary)
} catch (e: Exception) {
_summaryState.value = SummaryState.Error("Summary unavailable")
}
}
}
}
Step 5: Kill Switch Plan
If the AI API goes down or generates harmful content:
1. Open Firebase Console → Remote Config
2. Set ai_summary_enabled = false for ALL conditions
3. Click "Publish changes"
4. Changes propagate within fetch interval (~1hr)
→ OR force-fetch from app: remoteConfig.fetch(0).await() in critical path
5. All users see the button disappear without an app update
Step 6: Monitoring During Rollout
Track:
ai_summary_requested— count per user per dayai_summary_success_rate—success / (success + error)ai_summary_p95_latency— should be < 3 seconds- Crash rate on
ArticleDetailActivity— must not increase
// Log analytics events
fun summarize(article: Article, ...) {
analytics.logEvent("ai_summary_requested", bundleOf(
"article_id" to article.id,
"model" to flags.aiSummaryModel
))
// ...
// On success:
analytics.logEvent("ai_summary_completed", bundleOf(
"latency_ms" to elapsedMs,
"model" to flags.aiSummaryModel
))
}
Rollout Milestones
| Week | Condition | Flag value | Action |
|---|---|---|---|
| 0 | Internal testers | true | Verify no crashes or OOMs |
| 1 | 5% of users | true | Monitor error rate < 5% |
| 2 | 25% of users | true | Check P95 latency < 3s |
| 3 | 50% of users | true | Verify cost within budget |
| 4 | 100% | true | Remove conditional, make permanent |