npx claudepluginhub ahmed3elshaer/everything-claude-code-mobile --plugin everything-claude-code-mobileThis skill uses the workspace's default tool permissions.
```kotlin
Implements feature flags for gradual rollouts, A/B testing, canary deployments, and kill switches. Provides TypeScript services, React hooks, analytics integration, LaunchDarkly-style SDK, and admin UI.
Guides feature flag design, gradual rollouts, A/B testing, kill switches. Recommends LaunchDarkly, Unleash, Flagsmith based on flag count and needs for safer deployments.
Guides using FeatBit Flag Evaluation and Track Insights REST APIs to build custom SDKs for Android, iOS, Swift, Kotlin, Unity, or embedded platforms without official SDKs.
Share bugs, ideas, or general feedback.
interface FeatureFlagProvider {
fun getBooleanFlag(key: String, default: Boolean = false): Boolean
fun getStringFlag(key: String, default: String = ""): String
fun getIntFlag(key: String, default: Int = 0): Int
fun getDoubleFlag(key: String, default: Double = 0.0): Double
suspend fun refresh()
}
sealed class FeatureFlag<T>(
val key: String,
val defaultValue: T
) {
// Boolean flags
object NewOnboarding : FeatureFlag<Boolean>("new_onboarding_v2", false)
object DarkModeEnabled : FeatureFlag<Boolean>("dark_mode_enabled", true)
object ChatFeature : FeatureFlag<Boolean>("chat_feature", false)
// String flags
object CheckoutButtonText : FeatureFlag<String>("checkout_button_text", "Buy Now")
object HomeLayoutVariant : FeatureFlag<String>("home_layout_variant", "control")
// Numeric flags
object MaxCartItems : FeatureFlag<Int>("max_cart_items", 50)
object SearchDebounceMs : FeatureFlag<Long>("search_debounce_ms", 300L)
}
// Type-safe evaluation
class FeatureFlagManager(private val provider: FeatureFlagProvider) {
fun isEnabled(flag: FeatureFlag<Boolean>): Boolean {
return provider.getBooleanFlag(flag.key, flag.defaultValue)
}
fun getString(flag: FeatureFlag<String>): String {
return provider.getStringFlag(flag.key, flag.defaultValue)
}
fun getInt(flag: FeatureFlag<Int>): Int {
return provider.getIntFlag(flag.key, flag.defaultValue)
}
}
class FirebaseFeatureFlagProvider(context: Context) : FeatureFlagProvider {
private val remoteConfig = Firebase.remoteConfig.apply {
val configSettings = remoteConfigSettings {
minimumFetchIntervalInSeconds = if (BuildConfig.DEBUG) 0 else 3600
}
setConfigSettingsAsync(configSettings)
setDefaultsAsync(R.xml.remote_config_defaults)
}
override fun getBooleanFlag(key: String, default: Boolean): Boolean {
return remoteConfig.getBoolean(key)
}
override fun getStringFlag(key: String, default: String): String {
return remoteConfig.getString(key).ifEmpty { default }
}
override fun getIntFlag(key: String, default: Int): Int {
return remoteConfig.getLong(key).toInt()
}
override fun getDoubleFlag(key: String, default: Double): Double {
return remoteConfig.getDouble(key)
}
override suspend fun refresh() {
remoteConfig.fetchAndActivate().await()
}
}
<?xml version="1.0" encoding="utf-8"?>
<defaultsMap>
<entry>
<key>new_onboarding_v2</key>
<value>false</value>
</entry>
<entry>
<key>dark_mode_enabled</key>
<value>true</value>
</entry>
<entry>
<key>checkout_button_text</key>
<value>Buy Now</value>
</entry>
</defaultsMap>
final class FirebaseFeatureFlagProvider: FeatureFlagProvider {
private let remoteConfig = RemoteConfig.remoteConfig()
init() {
let settings = RemoteConfigSettings()
#if DEBUG
settings.minimumFetchInterval = 0
#else
settings.minimumFetchInterval = 3600
#endif
remoteConfig.configSettings = settings
remoteConfig.setDefaults(fromPlist: "RemoteConfigDefaults")
}
func getBooleanFlag(key: String, defaultValue: Bool) -> Bool {
remoteConfig.configValue(forKey: key).boolValue
}
func getStringFlag(key: String, defaultValue: String) -> String {
let value = remoteConfig.configValue(forKey: key).stringValue
return value?.isEmpty == false ? value! : defaultValue
}
func refresh() async throws {
let status = try await remoteConfig.fetchAndActivate()
print("Remote config fetch status: \(status)")
}
}
class LaunchDarklyProvider(context: Context) : FeatureFlagProvider {
private val client: LDClient
init {
val ldConfig = LDConfig.Builder(LDConfig.Builder.AutoEnvAttributes.Enabled)
.mobileKey("mob-your-mobile-key")
.build()
val ldContext = LDContext.builder(ContextKind.DEFAULT, "user-id-123")
.set("email", "user@example.com")
.set("plan", "premium")
.build()
client = LDClient.init(context.applicationContext, ldConfig, ldContext, 5)
}
override fun getBooleanFlag(key: String, default: Boolean): Boolean {
return client.boolVariation(key, default)
}
override fun getStringFlag(key: String, default: String): String {
return client.stringVariation(key, default)
}
override fun getIntFlag(key: String, default: Int): Int {
return client.intVariation(key, default)
}
override fun getDoubleFlag(key: String, default: Double): Double {
return client.doubleVariation(key, default)
}
override suspend fun refresh() {
// LaunchDarkly uses streaming by default, manual refresh not needed
}
fun registerFlagChangeListener(key: String, listener: (Boolean) -> Unit) {
client.registerFeatureFlagListener(key) { flagKey ->
listener(client.boolVariation(flagKey, false))
}
}
}
// build.gradle.kts
android {
buildTypes {
debug {
buildConfigField("boolean", "ENABLE_DEV_TOOLS", "true")
buildConfigField("boolean", "MOCK_API", "true")
}
release {
buildConfigField("boolean", "ENABLE_DEV_TOOLS", "false")
buildConfigField("boolean", "MOCK_API", "false")
}
}
}
// Usage
if (BuildConfig.ENABLE_DEV_TOOLS) {
showDevMenu()
}
class LocalFeatureFlagProvider(
private val prefs: SharedPreferences
) : FeatureFlagProvider {
override fun getBooleanFlag(key: String, default: Boolean): Boolean {
return prefs.getBoolean("flag_$key", default)
}
fun overrideFlag(key: String, value: Boolean) {
prefs.edit().putBoolean("flag_$key", value).apply()
}
fun clearOverrides() {
prefs.edit().clear().apply()
}
}
// commonMain
interface SharedFeatureFlags {
fun isEnabled(key: String, default: Boolean = false): Boolean
fun getString(key: String, default: String = ""): String
}
// androidMain
class AndroidFeatureFlags(context: Context) : SharedFeatureFlags {
private val remoteConfig = Firebase.remoteConfig
override fun isEnabled(key: String, default: Boolean) = remoteConfig.getBoolean(key)
override fun getString(key: String, default: String) = remoteConfig.getString(key)
}
// iosMain
class IosFeatureFlags : SharedFeatureFlags {
private val remoteConfig = RemoteConfig.remoteConfig()
override fun isEnabled(key: String, default: Boolean) =
remoteConfig.configValue(forKey: key).boolValue
override fun getString(key: String, default: String) =
remoteConfig.configValue(forKey: key).stringValue ?: default
}
class GradualRollout(private val userId: String) {
fun isInRollout(flagKey: String, percentage: Int): Boolean {
val hash = "$flagKey-$userId".hashCode().absoluteValue
return (hash % 100) < percentage
}
}
// Server-side targeting in Firebase Remote Config:
// Condition: "10% of users" -> random percentile <= 10
// Condition: "Premium users" -> user property "tier" == "premium"
data class Experiment(
val name: String,
val variant: String // "control", "variant_a", "variant_b"
)
fun getExperimentVariant(flagManager: FeatureFlagManager): Experiment {
val variant = flagManager.getString(FeatureFlag.HomeLayoutVariant)
analytics.logEvent("experiment_assigned", mapOf(
"experiment_name" to "home_layout",
"variant" to variant
))
return Experiment("home_layout", variant)
}