Appfigurate™️
HomeDocumentation
  • Introducing Appfigurate™️ 3
  • Getting Started
    • Getting Started
    • Examples
    • Upgrade guide
      • v1.4.0 to v2.2.0
      • v2.1.1 to v2.2.0
      • v2.2.1 to v3.0.0
      • v3.2.1 to v4.0.0
    • iOS native app integration
      • iOS app extension integration
    • watchOS app integration
      • watchOS app extension integration
    • Android native app integration
    • Mobile Flutter integration
      • Flutter iOS
      • Flutter Android
    • React Native integration
      • iOS native module integration
      • Android native module integration
      • JavaScript integration
    • Third party remote configuration providers
      • Firebase Remote Config
      • Launch Darkly
      • Other third party remote configuration providers
  • Configuration subclasses
    • Supported property types
      • Boolean
      • Integer
      • Float
      • Double
      • Plain String
      • Encrypted String
    • Custom executable actions
    • Slider icon types
  • Additional reading
    • Info.plist options
    • AndroidManifest.xml options
    • Displaying overridden configuration
    • Security
      • Best practice
      • Encryption
      • Export compliance
      • App Store compliance
      • PrivacyInfo.xcprivacy
      • Rotating your private key
  • Automation testing
    • iOS native app automation testing
    • Android native automation testing
  • API
    • iOS and watchOS API
    • Android API
    • Mobile Flutter API
    • React Native API
  • Appfigurate User Guide
    • Introduction
    • Main menu
    • Select app
    • Add app
    • Import app
    • Install example apps
    • Settings
      • Passcode Lock
      • Restore
      • Backup
      • Delete all apps and Settings
      • Analytics
    • Edit app
    • Configure app
    • Permissions
  • Appfigurate SE user guide
    • Introduction
    • Manual encryption
      • ENCRYPTED_STRING macro/function
      • ENCRYPTED_STRING_IOS_WATCHOS macro/function
    • Setup iOS Simulator app
    • Setup Android Emulator app
    • Xcode source editor extension
      • Troubleshooting
    • Real device cloud testing services
      • BrowserStack
  • LEGAL
    • License Agreement
    • Privacy Policy
    • Release History
    • Third party notices
Powered by GitBook
On this page
  • Add remote properties into your Configuration subclass
  • Provide remote configuration values to Appfigurate Library when requested
  • Notify Appfigurate Library when Launch Darkly has received flags
  • Best practice and usage
  1. Getting Started
  2. Third party remote configuration providers

Launch Darkly

Tested with Launch Darkly version 9.12.0 (iOS)

PreviousFirebase Remote ConfigNextOther third party remote configuration providers

Last updated 4 months ago

Third party remote configuration provider integration is currently in private beta and will be available in the next major release of Appfigurate.

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, Launch Darkly flags are not.

  • 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 method.

We assume you already have Appfigurate Library and integrated into your app with the following example remote properties created in the :

Add remote properties into your Configuration subclass

Swift Configuration example

import Foundation
import AppfigurateLibrary

@objcMembers class MyConfiguration: APLConfiguration {
...
    @RemoteBoolProperty(remoteKey: "alwaysDarkMode", description: "Force dark mode to be always set")
    var alwaysDarkMode: Bool

    @RemoteStringPropertyEdit(remoteKey: "appTitle", description: "Title of application")
    var appTitle: String

    @RemoteIntPropertyEdit(remoteKey: "bookingDuration", description: "Duration (days) for reservation bookings")
    var bookingDuration: Int

    @RemoteDoublePropertyEdit(remoteKey: "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;
}
...
@end

Kotlin 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(remoteKey = "alwaysDarkMode", description = "Force dark mode to be always set")
    var alwaysDarkMode: Boolean = false

    @RemoteStringPropertyEdit(remoteKey = "appTitle", description = "Title of application")
    var appTitle: String? = null

    @RemoteDoublePropertyEdit(remoteKey = "fontSize", description = "Size of font throughout app")
    var fontSize: Double = 0.0

    @RemoteIntPropertyEdit(remoteKey = "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(remoteKey = "alwaysDarkMode", description = "Force dark mode to be always set")
    public boolean alwaysDarkMode;

    @RemoteStringPropertyEdit(remoteKey = "appTitle", description = "Title of application")
    public String appTitle;

    @RemoteDoublePropertyEdit(remoteKey = "fontSize", description = "Size of font throughout app")
    public double fontSize;

    @RemoteIntPropertyEdit(remoteKey = "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.

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()
}
...
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();
}
...
LDClient.get().registerAllFlagsListener(new LDAllFlagsListener() {
    @Override
    public void onChange(List<String> 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 ((APLConfiguration.shared() as! MyConfiguration).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 (((MyConfiguration*) [APLConfiguration sharedConfiguration]).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 (((MyConfiguration) Configuration.sharedConfiguration()).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 (((MyConfiguration) Configuration.sharedConfiguration()).alwaysDarkMode) {
   ...

Update your configuration subclass to include your remote configuration properties. Provide default values for the properties in the overridden method. See also .

reset
Supported property types
reset
Launch Darkly
Launch Darkly console