🔄 Migration Guide
Guide for migrating from other solutions to Flagship.
📋 Table of Contents
1. Migration from Firebase Remote Config
2. Migration from LaunchDarkly
4. Migration from Custom Solution
Migration from Firebase Remote Config
Before (Pure Firebase)
// Initialization
val remoteConfig = Firebase.remoteConfig
remoteConfig.setConfigSettingsAsync(
remoteConfigSettings {
minimumFetchIntervalInSeconds = 3600
}
)
// Get values
val newFeatureEnabled = remoteConfig.getBoolean("new_feature")
val apiTimeout = remoteConfig.getLong("api_timeout").toInt()
// Fetch
remoteConfig.fetchAndActivate().addOnCompleteListener { task ->
if (task.isSuccessful) {
// Updated
}
}
After (Flagship + Firebase Provider)
// Initialization
val config = FlagsConfig(
appKey = "my-app",
environment = "production",
providers = listOf(
FirebaseRemoteConfigProvider(
remoteConfig = Firebase.remoteConfig,
fetchIntervalSeconds = 3600
)
),
cache = PersistentCache(FlagsSerializer())
)
Flags.configure(config)
// Get values (suspend functions - use in coroutine scope)
val manager = Flags.manager()
lifecycleScope.launch {
val newFeatureEnabled = manager.isEnabled("new_feature")
val apiTimeout: Int = manager.value("api_timeout", default = 5000)
}
// Fetch (automatic with coroutines)
lifecycleScope.launch {
manager.refresh()
}
Benefits
✅ Type-safe API - no manual type casting
✅ Kotlin Coroutines - instead of callbacks
✅ Offline-first - automatic caching
✅ Multi-provider - add REST fallback
✅ A/B Testing - built-in experiment support
Migration from LaunchDarkly
Before (LaunchDarkly)
val client = LDClient.init(application, ldConfig, ldUser)
// Feature flag
val showNewUI = client.boolVariation("show-new-ui", false)
// Experiment
val buttonColor = client.stringVariation("button-color-test", "blue")
After (Flagship)
// Initialization
val config = FlagsConfig(
appKey = "my-app",
environment = "production",
providers = listOf(
RestFlagsProvider(httpClient, "https://your-backend.com/flags")
),
cache = PersistentCache(FlagsSerializer())
)
Flags.configure(config)
// Feature flag
val manager = Flags.manager()
// Note: All flag access methods are suspend functions
lifecycleScope.launch {
val showNewUI = manager.isEnabled("show-new-ui")
// Experiment
val assignment = manager.assign("button-color-test")
val buttonColor = assignment?.payload["color"]?.jsonPrimitive?.content ?: "blue"
}
Migration from Split.io
Before (Split.io)
val factory = SplitFactoryBuilder.build("YOUR_SDK_KEY", applicationContext)
val client = factory.client()
// Feature flag
val treatment = client.getTreatment("user123", "new_checkout")
if (treatment == "on") {
// Show new checkout
}
After (Flagship)
val config = FlagsConfig(
appKey = "my-app",
environment = "production",
providers = listOf(
RestFlagsProvider(httpClient, "https://your-backend.com/flags")
),
cache = PersistentCache(FlagsSerializer())
)
Flags.configure(config)
val manager = Flags.manager()
val ctx = EvalContext(userId = "user123")
// Feature flag
// Note: isEnabled is a suspend function
lifecycleScope.launch {
if (manager.isEnabled("new_checkout")) {
// Show new checkout
}
}
Migration from Custom Solution
Before (Custom SharedPreferences)
val prefs = context.getSharedPreferences("feature_flags", Context.MODE_PRIVATE)
fun isFeatureEnabled(key: String): Boolean {
return prefs.getBoolean(key, false)
}
fun updateFlags() {
// Manual API call
apiService.getFlags().enqueue(object : Callback<FlagsResponse> {
override fun onResponse(call: Call<FlagsResponse>, response: Response<FlagsResponse>) {
response.body()?.let { flags ->
prefs.edit {
flags.features.forEach { (key, value) ->
putBoolean(key, value)
}
}
}
}
override fun onFailure(call: Call<FlagsResponse>, t: Throwable) {
// Handle error
}
})
}
After (Flagship)
// One-time setup
val config = FlagsConfig(
appKey = "my-app",
environment = "production",
providers = listOf(
RestFlagsProvider(httpClient, "https://your-backend.com/flags")
),
cache = PersistentCache(FlagsSerializer()) // Automatic cache
)
Flags.configure(config)
// Use everywhere
val manager = Flags.manager()
// Note: isEnabled is a suspend function - make the function suspend too
suspend fun isFeatureEnabled(key: String): Boolean {
return manager.isEnabled(key)
}
// Update is automatic based on TTL, or manually:
suspend fun updateFlags() {
manager.refresh()
}
Benefits
✅ Less code - no manual SharedPreferences management
✅ Type-safe - automatic type checking
✅ Coroutines - modern async approach
✅ TTL - automatic scheduled updates
✅ Multi-provider - easy to add Firebase/other sources
✅ A/B Testing - built-in experiment support
Step-by-Step Migration
Step 1: Add Flagship in Parallel
Don't remove the old solution immediately! Use both in parallel:
// Old
val oldFlag = remoteConfig.getBoolean("new_feature")
// New (Flagship)
val newFlag = Flags.manager().isEnabled("new_feature")
// Use new flag, fallback to old if needed
val featureEnabled = if (flagshipInitialized) newFlag else oldFlag
Step 2: Switch Flags Gradually
// Week 1: 10% users
if (userId.hashCode() % 100 < 10) {
useFlagship = true
}
// Week 2: 50% users
if (userId.hashCode() % 100 < 50) {
useFlagship = true
}
// Week 3: 100% users
useFlagship = true
Step 3: Monitor Errors
val config = FlagsConfig(
// ...
logger = object : FlagsLogger {
override fun log(level: LogLevel, message: String, error: Throwable?) {
// Send to Crashlytics/Sentry
if (level == LogLevel.ERROR) {
Crashlytics.getInstance().recordException(error ?: Exception(message))
}
}
}
)
Step 4: Remove Old Code
After successful migration (2-4 weeks):
1. Remove old SDK dependencies
2. Remove old solution code
3. Update documentation
Need migration help? Create an Issue on GitHub!