kmp-feature-flags
>-
Works with
---
name: kmp-feature-flags
description: >-
license: Apache-2.0
---
## When to Use This Skill
Use when:
- Enabling or disabling a feature without a new app release
- Running an A/B experiment where users see variant A or B
- Adding a kill switch to roll back a buggy feature from the server
- Controlling feature rollout percentage (0–100%)
- Deferring a feature to a date-based or user-segment condition
**Trigger keywords:** feature flags, feature toggle, remote config, Firebase Remote Config,
A/B test, experiment, kill switch, flag evaluation, FeatureFlagProvider, variant,
rollout, LaunchDarkly KMP, feature flag KMP, flag provider, remote feature flag.
**Freshness rule:** Firebase Remote Config fetch interval is 12 hours in production;
call `activate()` after `fetchAndActivate()` to apply fetched values in the current session.
For LaunchDarkly or other providers, check the Kotlin SDK's KMP support status — as of 2026
LaunchDarkly's Kotlin SDK is JVM/Android only, not KMP; use a thin `expect/actual` wrapper
if targeting iOS natively.
---
## Recommendation First
Define flags as a `FeatureFlag` enum in `commonMain` with typed defaults. Evaluation happens
in the `:domain` layer via `FeatureFlagProvider` — never read flags directly from a composable
or ViewModel. This keeps flag-gated logic testable with a fake provider and decouples feature
code from the backend (Firebase, LaunchDarkly, local overrides).
---
## Feature flag definitions — commonMain
```kotlin
// :core:flags:model — FeatureFlag.kt
enum class FeatureFlag(
val key: String,
val defaultValue: FlagValue,
) {
NewCheckoutFlow("new_checkout_flow", FlagValue.Bool(false)),
ChatSupport("chat_support_enabled", FlagValue.Bool(false)),
MaxItemsInCart("max_cart_items", FlagValue.Int(10)),
WelcomeBannerVariant("welcome_banner_variant", FlagValue.String("control")),
PricingTierEnabled("pricing_tier", FlagValue.Bool(false)),
}
sealed class FlagValue {
data class Bool(val default: Boolean) : FlagValue()
data class Int(val default: kotlin.Int) : FlagValue()
data class String(val default: kotlin.String) : FlagValue()
data class Double(val default: kotlin.Double) : FlagValue()
}
```
---
## FeatureFlagProvider interface — commonMain
```kotlin
// :core:flags:api — FeatureFlagProvider.kt
interface FeatureFlagProvider {
suspend fun fetchAndActivate()
fun getBoolean(flag: FeatureFlag): Boolean
fun getInt(flag: FeatureFlag): Int
fun getString(flag: FeatureFlag): String
fun getDouble(flag: FeatureFlag): Double
}
// Typed extension helpers
fun FeatureFlagProvider.isEnabled(flag: FeatureFlag): Boolean = getBoolean(flag)
fun FeatureFlagProvider.variant(flag: FeatureFlag): String = getString(flag)
```
---
## Firebase Remote Config implementation — Android
```kotlin
// :core:flags:data (androidMain) — FirebaseFeatureFlagProvider.kt
class FirebaseFeatureFlagProvider : FeatureFlagProvider {
private val remoteConfig = Firebase.remoteConfig.apply {
val defaults = FeatureFlag.values().associate { flag ->
flag.key to when (val d = flag.defaultValue) {
is FlagValue.Bool -> d.default
is FlagValue.Int -> d.default.toLong()
is FlagValue.String -> d.default
is FlagValue.Double -> d.default
}
}
setDefaultsAsync(defaults)
setConfigSettingsAsync(
remoteConfigSettings {
minimumFetchIntervalInSeconds = if (BuildConfig.DEBUG) 0L else 3600L
}
)
}
override suspend fun fetchAndActivate() {
remoteConfig.fetchAndActivate().await()
}
override fun getBoolean(flag: FeatureFlag): Boolean =
remoteConfig.getBoolean(flag.key)
override fun getInt(flag: FeatureFlag): Int =
remoteConfig.getLong(flag.key).toInt()
override fun getString(flag: FeatureFlag): String =
remoteConfig.getString(flag.key)
override fun getDouble(flag: FeatureFlag): Double =
remoteConfig.getDouble(flag.key)
}
```
---
## No-op / hardcoded provider for iOS and Desktop
```kotlin
// commonMain or iosMain — DefaultsFeatureFlagProvider.kt
class DefaultsFeatureFlagProvider : FeatureFlagProvider {
override suspend fun fetchAndActivate() = Unit // no remote source
override fun getBoolean(flag: FeatureFlag): Boolean =
(flag.defaultValue as? FlagValue.Bool)?.default ?: false
override fun getInt(flag: FeatureFlag): Int =
(flag.defaultValue as? FlagValue.Int)?.default ?: 0
override fun getString(flag: FeatureFlag): String =
(flag.defaultValue as? FlagValue.String)?.default ?: ""
override fun getDouble(flag: FeatureFlag): Double =
(flag.defaultValue as? FlagValue.Double)?.default ?: 0.0
}
```
---
## Flag evaluation in use cases — never in composables
```kotlin
// :domain — CheckoutUseCase.kt
class CheckoutUseCase(
private val flags: FeatureFlagProvider,
private val cartRepository: CartRepository,
private val checkoutRepository: CheckoutRepository,
) {
suspend operator fun invoke(cartId: String): Result<Order> {
return if (flags.isEnabled(FeatureFlag.NewCheckoutFlow)) {
checkoutRepository.checkoutV2(cartId)
} else {
checkoutRepository.checkoutV1(cartId)
}
}
}
```
---
## A/B variant example
```kotlin
// :presenter — HomeViewModel.kt
class HomeViewModel(
private val flags: FeatureFlagProvider,
) : ViewModel() {
private val _state = MutableStateFlow(HomeContract.State())
val state: StateFlow<HomeContract.State> = _state.asStateFlow()
init {
viewModelScope.launch {
flags.fetchAndActivate()
val variant = flags.variant(FeatureFlag.WelcomeBannerVariant)
_state.update { it.copy(welcomeVariant = variant) }
}
}
}
// In UiState and composable:
// variant == "control" → show original banner
// variant == "treatment_a" → show new banner design
```
---
## Kill switch pattern
```kotlin
// :domain — ChatFeature.kt
class OpenChatUseCase(private val flags: FeatureFlagProvider) {
operator fun invoke(): Result<Unit> {
if (!flags.isEnabled(FeatureFlag.ChatSupport)) {
return Result.failure(FeatureDisabledException("Chat is temporarily unavailable"))
}
// ... open chat
return Result.success(Unit)
}
}
```
---
## App startup — fetch on launch
```kotlin
// In Application.onCreate (Android) or KoinApplication initializer:
scope.launch {
get<FeatureFlagProvider>().fetchAndActivate()
}
```
---
## Koin wiring
```kotlin
// androidMain
val featureFlagsModule = module {
single<FeatureFlagProvider> { FirebaseFeatureFlagProvider() }
}
// iosMain / desktopMain
val featureFlagsModule = module {
single<FeatureFlagProvider> { DefaultsFeatureFlagProvider() }
}
```
---
## Testing with FakeFeatureFlagProvider
```kotlin
class FakeFeatureFlagProvider(
private val flags: Map<FeatureFlag, Any> = emptyMap(),
) : FeatureFlagProvider {
override suspend fun fetchAndActivate() = Unit
override fun getBoolean(flag: FeatureFlag) =
(flags[flag] as? Boolean) ?: (flag.defaultValue as? FlagValue.Bool)?.default ?: false
override fun getInt(flag: FeatureFlag) =
(flags[flag] as? Int) ?: (flag.defaultValue as? FlagValue.Int)?.default ?: 0
override fun getString(flag: FeatureFlag) =
(flags[flag] as? String) ?: (flag.defaultValue as? FlagValue.String)?.default ?: ""
override fun getDouble(flag: FeatureFlag) =
(flags[flag] as? Double) ?: (flag.defaultValue as? FlagValue.Double)?.default ?: 0.0
}
@Test fun `checkout uses v2 when flag is enabled`() = runTest {
val flags = FakeFeatureFlagProvider(mapOf(FeatureFlag.NewCheckoutFlow to true))
val useCase = CheckoutUseCase(flags, FakeCartRepository(), FakeCheckoutRepository())
useCase.invoke("cart-123")
assertTrue(FakeCheckoutRepository.v2WasCalled)
}
```
---
## Common Anti-Patterns
- **Reading flags in composables** — `if (flags.isEnabled(FeatureFlag.X))` in a `@Composable`
bypasses the ViewModel and cannot be tested; evaluate flags in use cases or ViewModels and
pass the result as part of `UiState`
- **No offline fallback** — if `fetchAndActivate()` fails silently, code using the flag
should fall back to the hardcoded default in `FeatureFlag.defaultValue`; never crash
- **Adding flag keys as raw strings** — `remoteConfig.getBoolean("new_checkout_flow")` in
feature code creates hidden coupling; always read via the `FeatureFlag` enum
- **Fetching on every ViewModel init** — `fetchAndActivate()` should be called once on
app startup and stored; fetching per ViewModel adds unnecessary latency and Firebase quota
- **Not cleaning up stale flags** — flags that are 100% enabled and will never roll back
should be removed from the enum, the remote config, and all call sites; stale flags
accumulate and make the code harder to reason about
---
## Related Skills
- `kmp-analytics` — log which variant a user sees immediately after
`fetchAndActivate()` so the analytics team can correlate behavior with experiment buckets
- `kmp-dependency-injection` — `FeatureFlagProvider` is a singleton in
the Koin module; it is injected into use cases and ViewModels via constructor
- `kmp-unit-testing` — `FakeFeatureFlagProvider` allows testing both
branches of any flag-gated use case without a Firebase connection
---
## Output Style
When implementing feature flags, respond in this order:
1. **FeatureFlag enum** — key + typed default for each flag in scope
2. **FeatureFlagProvider interface** — standard methods
3. **Firebase implementation** — Android; defaults-only stub for iOS/Desktop
4. **Flag evaluation** — use case or ViewModel that branches on the flag
5. **Startup fetch** — `fetchAndActivate()` on app launch
6. **Koin wiring** — platform modules
7. **FakeFeatureFlagProvider + test** — both branches covered
---
## Changelog
| Date | Change |
|---|---|
| 2026-06-21 | Initial release. |More Deployment & CI/CD skills
azure-enterprise-infra-planner
microsoft/azure-skills
Architect and provision enterprise Azure infrastructure from workload descriptions. For cloud architects and platform engineers planning networking, identity, security, compliance, and multi-resource topologies with WAF alignment. Generates Bicep or Terraform directly (no azd). WHEN: 'plan Azure infrastructure', 'architect Azure landing zone', 'design hub-spoke network', 'plan multi-region DR topology', 'set up VNets firewalls and private endpoints', 'subscription-scope Bicep deployment', 'Azure Backup for VM workloads'. PREFER azure-prepare FOR app-centric workflows.
azure-kubernetes-app-deploy
microsoft/azure-skills
Use when deploying an existing web application or API to an already-running Azure Kubernetes Service cluster. Detects the framework, generates a Dockerfile and Kubernetes manifests, validates against AKS Deployment Safeguards, and deploys with verification. WHEN: deploy app to AKS, deploy to existing AKS cluster, containerize app for Kubernetes, generate K8s manifests for Azure, set up CI/CD for AKS, my AKS deployment is failing safeguard checks, I have a Django/Express/Spring Boot app to run on AKS. DO NOT USE FOR: creating or provisioning an AKS cluster (use azure-kubernetes), assessing migration to AKS Automatic (use azure-kubernetes-automatic-readiness), or deploying to non-AKS targets like Web Apps, Container Apps, or Functions.
finetuning
microsoft/azure-skills
Fine-tune models on Microsoft Foundry using SFT (supervised), DPO (preference), or RFT (reinforcement with graders). Covers dataset preparation, training job submission, deployment, and evaluation. USE FOR: fine-tune, SFT, DPO, RFT, training data, grader, distillation, fine-tuned model, training job, large file upload, calibrate grader, deploy fine-tuned model, evaluate fine-tuned model. DO NOT USE FOR: general model deployment without fine-tuning (use deploy-model), agent creation (use agents), prompt optimization without training (use prompt-optimizer).

