Launch Darkly
Tested with Launch Darkly version 9.12.0 (iOS)
Using Appfigurate remote properties instead of Launch Darkly flag evaluation APIs directly allows for the following:
locally change Launch Darkly flags without affecting your entire customer base.
compile time type safety - Appfigurate remote properties are typed.
avoids hardcoding duplicated flag names throughout your app.
avoids hardcoding duplicated default values throughout your app.
deleting a flag from Launch Darkly Console won't affect existing apps, they'll continue to use the default value provided in the
resetmethod.
We assume you already have Appfigurate Library and Launch Darkly integrated into your app with the following example remote properties created in the Launch Darkly console:

Add remote properties into your Configuration subclass
Update your configuration subclass to include your remote configuration properties. Provide default values for the properties in the overridden reset method. See also Supported property types.
Swift Configuration example
import Foundation
import AppfigurateLibrary
@objcMembers class MyConfiguration: APLConfiguration {
...
@RemoteBoolProperty(key: "alwaysDarkMode", description: "Force dark mode to be always set")
var alwaysDarkMode: Bool
@RemoteStringPropertyEdit(key: "appTitle", description: "Title of application")
var appTitle: String
@RemoteIntPropertyEdit(key: "bookingDuration", description: "Duration (days) for reservation bookings")
var bookingDuration: Int
@RemoteDoublePropertyEdit(key: "fontSize", description: "Size of font throughout app")
var fontSize: Double
...
override func reset() {
alwaysDarkMode = false
appTitle = "Holiday finder"
bookingDuration = 30
fontSize = 13.0
}
...
}Objective-C Configuration header example
@import Foundation;
@import AppfigurateLibrary;
@interface MyConfiguration : APLConfiguration
...
@property(nonatomic, assign) BOOL alwaysDarkMode;
@property(nonatomic, strong) NSString* appTitle;
@property(nonatomic, assign) NSInteger bookingDuration;
@property(nonatomic, assign) double fontSize;
...
@end
Objective-C Configuration implementation example
#import "MyConfiguration.h"
@implementation MyConfiguration
...
REMOTE_BOOL_PROPERTY(alwaysDarkMode, @"alwaysDarkMode", @"Force dark mode to be always set");
REMOTE_STRING_PROPERTY_EDIT(appTitle, @"appTitle", @"Title of application");
REMOTE_INT_PROPERTY_EDIT(bookingDuration, @"bookingDuration", @"Duration (days) for reservation bookings");
REMOTE_DOUBLE_PROPERTY_EDIT(fontSize, @"fontSize", @"Size of font throughout app");
...
- (void) reset {
self.alwaysDarkMode = NO;
self.appTitle = @"Holiday finder";
self.bookingDuration = 30;
self.fontSize = 13.0;
}
...
@endKotlin Configuration example
package com.yourcompany.yourapp
import nz.co.electricbolt.appfiguratelibrary.annotations.RemoteBooleanProperty
import nz.co.electricbolt.appfiguratelibrary.annotations.RemoteDoublePropertyEdit
import nz.co.electricbolt.appfiguratelibrary.annotations.RemoteIntPropertyEdit
import nz.co.electricbolt.appfiguratelibrary.annotations.RemoteStringPropertyEdit
class MyConfiguration : nz.co.electricbolt.appfiguratelibrary.Configuration() {
...
@RemoteBooleanProperty(propertyKey = "alwaysDarkMode", description = "Force dark mode to be always set")
var alwaysDarkMode: Boolean = false
@RemoteStringPropertyEdit(propertyKey = "appTitle", description = "Title of application")
var appTitle: String? = null
@RemoteDoublePropertyEdit(propertyKey = "fontSize", description = "Size of font throughout app")
var fontSize: Double = 0.0
@RemoteIntPropertyEdit(propertyKey = "bookingDuration", description = "Duration (days) for reservation bookings")
var bookingDuration: Int = 0
...
override fun reset() {
alwaysDarkMode = false
appTitle = "Holiday finder"
bookingDuration = 30
fontSize = 13.0
}
}Java Configuration example
package com.yourcompany.yourapp;
import nz.co.electricbolt.appfiguratelibrary.annotations.RemoteBooleanProperty;
import nz.co.electricbolt.appfiguratelibrary.annotations.RemoteDoublePropertyEdit;
import nz.co.electricbolt.appfiguratelibrary.annotations.RemoteIntPropertyEdit;
import nz.co.electricbolt.appfiguratelibrary.annotations.RemoteStringPropertyEdit;
public class MyConfiguration extends nz.co.electricbolt.appfiguratelibrary.Configuration {
...
@RemoteBooleanProperty(propertyKey = "alwaysDarkMode", description = "Force dark mode to be always set")
public boolean alwaysDarkMode;
@RemoteStringPropertyEdit(propertyKey = "appTitle", description = "Title of application")
public String appTitle;
@RemoteDoublePropertyEdit(propertyKey = "fontSize", description = "Size of font throughout app")
public double fontSize;
@RemoteIntPropertyEdit(propertyKey = "bookingDuration", description = "Duration (days) for reservation bookings")
public int bookingDuration;
...
@Override
public void reset() {
alwaysDarkMode = false;
appTitle = "Holiday finder";
bookingDuration = 30;
fontSize = 13.0;
}
...
}Provide remote configuration values to Appfigurate Library when requested
Appfigurate Library needs to be able to read the current remote flags from Launch Darkly. Copy and paste the following code into your app.
Swift example
APLFetchRemoteConfiguration { propertyKey, propertyType, defaultValue in
if propertyType == .bool {
return NSNumber(value: (LDClient.get()!.boolVariation(forKey: propertyKey, defaultValue: (defaultValue as! NSNumber).boolValue)))
} else if propertyType == .int {
return NSNumber(value: (LDClient.get()!.intVariation(forKey: propertyKey, defaultValue: (defaultValue as! NSNumber).intValue)))
} else if propertyType == .double {
return NSNumber(value: (LDClient.get()!.doubleVariation(forKey: propertyKey, defaultValue: (defaultValue as! NSNumber).doubleValue)))
} else { // .string
return NSString(string: LDClient.get()!.stringVariation(forKey: propertyKey, defaultValue: (defaultValue as! String)))
}
}Objective-C example
APLFetchRemoteConfiguration(^NSObject* (NSString* propertyKey, APLRemotePropertyType propertyType, NSObject* defaultValue) {
if (propertyType == APLRemotePropertyTypeBool) {
return [NSNumber numberWithBool: [[LDClient get] boolVariationForKey: propertyKey defaultValue: [((NSNumber*) defaultValue) boolValue]]];
} else if (propertyType == APLRemotePropertyTypeInt) {
return [NSNumber numberWithInteger: [[LDClient get] integerVariationForKey: propertyKey defaultValue: [((NSNumber*) defaultValue) integerValue]]];
} else if (propertyType == APLRemotePropertyTypeDouble) {
return [NSNumber numberWithDouble: [[LDClient get] doubleVariationForKey: propertyKey defaultValue: [((NSNumber*) defaultValue) doubleValue]]];
} else { // APLRemotePropertyTypeString
return [[LDClient get] stringVariationForKey: propertyKey defaultValue: (NSString*) defaultValue];
}
});Kotlin example
Appfigurate.fetchRemoteConfiguration { propertyKey: String, propertyType: RemotePropertyType, defaultValue: Any? ->
try {
if (propertyType == RemotePropertyTypeString)
LDClient.get().stringVariation(propertyKey, defaultValue as String)
else if (propertyType == RemotePropertyTypeBoolean)
LDClient.get().boolVariation(propertyKey, defaultValue as Boolean)
else if (propertyType == RemotePropertyTypeInt)
LDClient.get().intVariation(propertyKey, defaultValue as Int)
else // RemotePropertyTypeDouble
LDClient.get().doubleVariation(propertyKey, defaultValue as Double)
} catch (e: LaunchDarklyException) {
defaultValue
}
}Java example
Appfigurate.fetchRemoteConfiguration((propertyKey, propertyType, defaultValue) -> {
try {
return switch (propertyType) {
case RemotePropertyTypeString -> LDClient.get().stringVariation(propertyKey, (String) defaultValue);
case RemotePropertyTypeBoolean -> LDClient.get().boolVariation(propertyKey, (Boolean) defaultValue);
case RemotePropertyTypeInt -> LDClient.get().intVariation(propertyKey, (Integer) defaultValue);
case RemotePropertyTypeDouble -> LDClient.get().doubleVariation(propertyKey, (Double) defaultValue);
};
} catch(LaunchDarklyException e) {
return defaultValue;
}
});Notify Appfigurate Library when Launch Darkly has received flags
We need to tell Appfigurate Library that Launch Darkly has received flags, so that it can keep your Configuration subclasses remote properties in sync.
Add a call to APLFlushRemoteConfiguration in any existing Launch Darkly start(config, startWaitSeconds, completion) and observeAll(owner, handler) blocks.
Swift example
LDClient.start(config: config, startWaitSeconds: 5.0) { timedOut in
...
APLFlushRemoteConfiguration() // add this line
}
...
LDClient.get()?.observeAll(owner: self) { keys in
...
APLFlushRemoteConfiguration() // add this line
}Add a call to APLFlushRemoteConfiguration in any existing Launch Darkly startWithConfiguration:startWaitSeconds:completion: and observeAllKeysWithOwner:handler: blocks.
Objective-C example
[LDClient startWithConfiguration:config startWaitSeconds:5.0 completion:^(bool timedOut) {
...
APLFlushRemoteConfiguration(); // add this line
}];
...
[[LDClient get] observeAllKeysWithOwner: self handler:^(NSDictionary<NSString *,LDChangedFlag *> *handler) {
...
APLFlushRemoteConfiguration(); // add this line
}];The Android Launch Darkly SDK doesn't have functionality that notifies you of flags being received during the LDClient.init call. Instead we must programmatically check for a well known flag's evaluation detail, and then call Appfigurate.flushRemoteConfiguration as appropriate.
Add another call to Appfigurate.flushRemoteConfiguration in a Launch Darkly registerAllFlagsListener block.
Kotlin example
LDClient.init(...)
// add this block of code
if (LDClient.get().boolVariationDetail("alwaysDarkMode", false).variationIndex > EvaluationDetail.NO_VARIATION) {
// Flags loaded by LDClient.init()
Appfigurate.flushRemoteConfiguration()
}
LDClient.get().registerAllFlagsListener {
...
Appfigurate.flushRemoteConfiguration(); // add this line
});The Android Launch Darkly SDK doesn't have functionality that notifies you of flags being received during the LDClient.init call. Instead we must programmatically check for a well known flag's evaluation detail, and then call Appfigurate.flushRemoteConfiguration as appropriate.
Add another call to Appfigurate.flushRemoteConfiguration in a Launch Darkly registerAllFlagsListener block.
Java example
LDClient.init(...)
// add this block of code
if (LDClient.get().boolVariationDetail("alwaysDarkMode", false).variationIndex > EvaluationDetail.NO_VARIATION) {
// Flags loaded by LDClient.init()
Appfigurate.flushRemoteConfiguration(); // add this line
}
LDClient.get().registerAllFlagsListener(new LDAllFlagsListener() {
@Override
public void onChange(List<String> flagKey) {
...
Appfigurate.flushRemoteConfiguration(); // add this line
}
});Complete initialisation example
Swift complete initialisation example
let config = LDConfig(mobileKey: "<YOUR-KEY>", autoEnvAttributes: .enabled)
// add this block of code
APLFetchRemoteConfiguration { propertyKey, propertyType, defaultValue in
if propertyType == .bool {
return NSNumber(value: (LDClient.get()!.boolVariation(forKey: propertyKey, defaultValue: (defaultValue as! NSNumber).boolValue)))
} else if propertyType == .int {
return NSNumber(value: (LDClient.get()!.intVariation(forKey: propertyKey, defaultValue: (defaultValue as! NSNumber).intValue)))
} else if propertyType == .double {
return NSNumber(value: (LDClient.get()!.doubleVariation(forKey: propertyKey, defaultValue: (defaultValue as! NSNumber).doubleValue)))
} else {
return NSString(string: LDClient.get()!.stringVariation(forKey: propertyKey, defaultValue: (defaultValue as! String)))
}
}
LDClient.start(config: config, startWaitSeconds: 5.0) { timedOut in
if !timedOut {
APLFlushRemoteConfiguration() // add this line
}
}
LDClient.get()?.observeAll(owner: self) { keys in
APLFlushRemoteConfiguration() // add this line
}Objective-C complete initialisation example
LDConfig* config = [[LDConfig alloc] initWithMobileKey: @"<YOUR-KEY>" autoEnvAttributes:AutoEnvAttributesEnabled];
// add this block of code
APLFetchRemoteConfiguration(^NSObject* (NSString* propertyKey, APLRemotePropertyType propertyType, NSObject* defaultValue) {
if (propertyType == APLRemotePropertyTypeBool) {
return [NSNumber numberWithBool: [[LDClient get] boolVariationForKey: propertyKey defaultValue: [((NSNumber*) defaultValue) boolValue]]];
} else if (propertyType == APLRemotePropertyTypeInt) {
return [NSNumber numberWithInteger: [[LDClient get] integerVariationForKey: propertyKey defaultValue: [((NSNumber*) defaultValue) integerValue]]];
} else if (propertyType == APLRemotePropertyTypeDouble) {
return [NSNumber numberWithDouble: [[LDClient get] doubleVariationForKey: propertyKey defaultValue: [((NSNumber*) defaultValue) doubleValue]]];
} else {
return [[LDClient get] stringVariationForKey: propertyKey defaultValue: (NSString*) defaultValue];
}
});
LDContext* context = [[[[LDContextBuilder alloc] initWithKey: @"<YOUR-CONTEXT>"] build] success];
[LDClient startWithConfiguration:config context:context startWaitSeconds:5.0 completion:^(bool timedOut) {
if (!timedOut) {
APLFlushRemoteConfiguration(); // add this line
}
}];
[[LDClient get] observeAllKeysWithOwner: self handler: ^(NSDictionary<NSString *,LDChangedFlag *> *handler) {
APLFlushRemoteConfiguration(); // add this line
}];Kotlin complete initilisation example
val ldConfig = LDConfig.Builder(LDConfig.Builder.AutoEnvAttributes.Enabled)
.mobileKey("<YOUR-KEY>")
.build()
val ldContext = LDContext.create("<YOUR-CONTEXT>")
LDClient.init(this.application, ldConfig, ldContext, 0)
// add this block of code
Appfigurate.fetchRemoteConfiguration { propertyKey: String, propertyType: RemotePropertyType, defaultValue: Any? ->
try {
if (propertyType == RemotePropertyTypeString)
LDClient.get().stringVariation(propertyKey, defaultValue as String)
else if (propertyType == RemotePropertyTypeBoolean)
LDClient.get().boolVariation(propertyKey, defaultValue as Boolean)
else if (propertyType == RemotePropertyTypeInt)
LDClient.get().intVariation(propertyKey, defaultValue as Int)
else // RemotePropertyTypeDouble
LDClient.get().doubleVariation(propertyKey, defaultValue as Double)
} catch (e: LaunchDarklyException) {
defaultValue
}
}
// add this block of code
var detail = LDClient.get().boolVariationDetail("<YOUR-FLAG-NAME>", false)
if (detail.variationIndex > EvaluationDetail.NO_VARIATION) {
// Flags loaded by LDClient.init()
Appfigurate.flushRemoteConfiguration()
}
LDClient.get().registerAllFlagsListener {
Appfigurate.flushRemoteConfiguration() // add this line
}Java complete initialisation example
LDConfig ldConfig = new LDConfig.Builder(LDConfig.Builder.AutoEnvAttributes.Enabled)
.mobileKey("<YOUR-KEY>")
.build();
LDContext context = LDContext.create("<YOUR-CONTEXT>");
// add this block of code
Appfigurate.fetchRemoteConfiguration((propertyKey, propertyType, defaultValue) -> {
try {
return switch (propertyType) {
case RemotePropertyTypeString -> LDClient.get().stringVariation(propertyKey, (String) defaultValue);
case RemotePropertyTypeBoolean -> LDClient.get().boolVariation(propertyKey, (Boolean) defaultValue);
case RemotePropertyTypeInt -> LDClient.get().intVariation(propertyKey, (Integer) defaultValue);
case RemotePropertyTypeDouble -> LDClient.get().doubleVariation(propertyKey, (Double) defaultValue);
};
} catch(LaunchDarklyException e) {
return defaultValue;
}
});
LDClient client = LDClient.init(this.getApplication(), ldConfig, context, 0);
// add this block of code
if (client.boolVariationDetail("<YOUR-FLAG-NAME>", false).variationIndex > EvaluationDetail.NO_VARIATION) {
// Flags loaded by LDClient.init()
Appfigurate.flushRemoteConfiguration();
}
client.registerAllFlagsListener(flagKey -> {
Appfigurate.flushRemoteConfiguration(); // add this line
});Best practice and usage
Replace all calls to Launch Darkly boolVariation(forKey:defaultValue:), intVariation(forKey:defaultValue:), doubleVariation(forKey:defaultValue), stringVariation(forKey:defaultValue:)
if (LDClient.get()!.boolVariation(forKey: "alwaysDarkMode", defaultValue: false)) {
...with the following:
if (CONFIGURATION().alwaysDarkMode) {
...Replace all calls to Launch Darkly boolVariationForKey:defaultValue:, intVariationForKey:defaultValue:, doubleVariationForKey:defaultValue, stringVariationForKey:defaultValue:
if ([[LDClient get] boolVariationForKey: @"alwaysDarkMode" defaultValue: NO]) {
...with the following:
if (CONFIGURATION.alwaysDarkMode) {
...Replace all calls to Launch Darkly boolVariation(), intVariation(), doubleVariation(), stringVariation()
try {
if (LDClient.get().boolVariation("alwaysDarkMode", false)) {
...
}
} catch(e: LaunchDarklyException) {
...
}with the following:
if (CONFIGURATION().alwaysDarkMode) {
...Replace all calls to Launch Darkly boolVariation(), intVariation(), doubleVariation(), stringVariation()
try {
if (LDClient.get().boolVariation("alwaysDarkMode", false)) {
...
}
} catch(LaunchDarklyException e) {
...
}with the following:
if (CONFIGURATION().alwaysDarkMode) {
...Last updated