• HOME
  • INTEGRATIONS
  • SDKS
  • GUIDES
  • API DOCS
No results for ""
EXPAND ALL

EDIT ON GITHUB

Subscribing to flag changes

Read time: 8 minutes
Last edited: Jul 28, 2021

Overview

This topic explains how to configure each SDK to receive changes in flag state from LaunchDarkly. This feature is available for both client-side and server-side SDKs.

Receiving flag changes

You can configure client-side and server-side SDKs to receive updated flag values when they change. Client-side SDKs use a streaming and/or a polling connection to receive updates with low latency, minimal data usage, and minimal battery drain. Server-side SDKs use a listener-based mechanism to notify you when flag values change.

  • Client-side SDKs
  • Server-side SDKs

Client-side SDKs

This feature is available in the following client-side SDKs:

Android

The SDK maintains what variations a user should receive locally in its cache and evaluates flags based on that cache. The SDK uses both a streaming strategy and a polling strategy to keep the local cache in sync with your flag configurations in LaunchDarkly.

The SDK is highly configurable, but the default configuration has low latency updates, minimal data usage, and minimal battery drain.

Here is the default behavior:

  1. When the app is foregrounded, the SDK opens a Server-Sent Events streaming connection to LaunchDarkly. The initial payload from the streaming connection contains the variations your user receives. This streaming connection stays open and idles unless there are updates. This requires minimal data and battery power to maintain. The streaming connection stays open as long as your app is in the foreground and is connected to the internet.
  2. When the app is backgrounded, the stream connection terminates. The SDK polls for flag updates every hour to stay in sync. This strategy has higher latency, but optimizes battery and data usage.
  3. When the app is foregrounded again, the SDK reconnects to the stream which sends the latest flag values.
  4. When streaming, the SDK actively monitors network availability. It avoids requests when the network is unavailable, and reconnects when the network becomes available again. When polling, the SDK checks for network connectivity at the current polling interval, only making the request if the check succeeds. When it reconnects, it automatically syncs its local cache with LaunchDarkly.

This configuration means that you get near real-time updates for your feature flag values when the app is in the foreground, and maximum device and SDK efficiency when backgrounded.

You can configure these settings if needed. To learn more, read Configuration

To perform real-time updates in your app, it must register listeners to be notified when a flag's value changes.

Here's how:

1String flagKey = "yourFlagKey";
2
3FeatureFlagChangeListener listener = new FeatureFlagChangeListener() {
4 @Override
5 public void onFeatureFlagChange(String flagKey) {
6 boolean newValue = LDClient.get().boolVariation(flagKey, false);
7 }
8};
9
10LDClient.get().registerFeatureFlagListener(flagKey, listener);

The flag key passed to onFeatureFlagChange is the key of the updated flag, which lets a single listener be registered for multiple flags.

You can also disable listeners by unregistering them:

1LDClient.get().unregisterFeatureFlagListener(flagKey, listener);

Availability

These calls have been available since v2.8.0:

  • LDAllFlagsListener
  • LDClient.registerAllFlagsListener
  • LDClient.unregisterAllFlagsListener

Additionally, we provide an update listener interface for when you want to be notified when the flag cache is updated. The application provides a class implementing LDAllFlagsListener which provides the SDK with the method onChange. Whenever the SDK's flag cache is updated, it calls the onChange method with a list of flag keys for flags that were updated during the update to the flag cache. If no flag values changed, this list is empty.

Here is an example:

1LDAllFlagsListener listener = new LDAllFlagsListener() {
2 @Override
3 public void onChange(List<String> flagKeys) {
4 // Get new values for flagKeys or other operations
5 }
6};
7
8// register all flags listener
9LDClient.get().registerAllFlagsListener(listener);
10// when done with all flags listener it should be unregistered
11LDClient.get().unregisterAllFlagsListener(listener);

C/C++ (client-side)

To accomplish real-time updates, LaunchDarkly broadcasts an event stream that is listened to by the C SDK. Whenever an event is performed on the dashboard, the C SDK is notified of the updated flag settings in real-time.

Electron

By default, the client requests feature flag values only once per user, once at startup time, and then each time you call identify(). You can also use a persistent connection to receive flag updates whenever they occur.

Setting streaming to true in the client options, or calling client.setStreaming(true), turns on this behavior. LaunchDarkly will push new values to the SDK, which will update the current feature flag state in the background, ensuring that variation() will always return the latest values.

If you want to be notified when a flag has changed, you can use an event listener for a specific flag:

1client.on('change:YOUR_FEATURE_KEY', function(newValue, oldValue) {
2 console.log('The flag was ' + oldValue + ' and now it is ' + newValue);
3});

Or, you can listen for all feature flag changes:

1client.on('change', function(allFlagChanges)) {
2 Object.keys(allFlagChanges).forEach(function(key) {
3 console.log('Flag ' + key + ' is now ' + allFlagChanges[key]);
4 });
5});

Subscribing to change events will automatically turn on streaming mode too, unless you have explicitly set streaming to false.

Flutter

The SDK maintains what variations a user should receive locally in cache. The SDK evaluates flags based on what it has cached locally. To keep the local cache in sync with your flag configurations in LaunchDarkly, the SDK maintains a streaming connection to the service when in the foreground. This allows near instantaneous flag update delivery to your application.

Your application can register listeners to be notified immediately when the SDK receives updated flag values.

Here's how:

1LDFlagUpdatedCallback listener = (String flagKey) {
2 LDClient.boolVariation(flagKey, false).then((bool val) {
3 print('${flagKey}: ${val}');
4 });
5};
6
7await LDClient.registerFeatureFlagListener('yourFlagKey', listener);

The flag key passed to your LDFlagUpdatedCallback is the key of the updated flag, which allows a single listener to be registered for multiple flags.

You can also disable listeners by unregistering them:

1await LDClient.unregisterFeatureFlagListener(flagKey, listener);

Additionally, we provide a listener interface if you want to be notified any time the flag cache is updated. The application provides a callback that is activated whenever the SDK receives new flag data from the service. It calls with a list of flag keys that were updated. If no flag values changed, this list is empty.

1LDFlagsReceivedCallback listener = (List<String> flagKeys) {
2 print(flagKeys.toString());
3};
4
5await LDClient.registerFlagsReceivedListener(listener);
6await LDClient.unregisterFlagsReceivedListener(listener);

iOS

LaunchDarkly manages all flags for a user context in real-time by updating flags based on a real-time event stream. When a flag is modified from the LaunchDarkly dashboard, the flag values for the current user updates almost immediately.

To accomplish real-time updates, LaunchDarkly broadcasts an event stream that the SDK listens to. The SDK is notified of updated flag settings in real-time whenever an event registers on the dashboard.

The SDK provides methods for listening to a single flag, all flags, or no change to any flag. observeFlagsUnchanged is called when the SDK successfully receives an update or comes back online but no flags have changed. If the value of the flag changes, the method executes the handler. It passes in the changedFlag containing the old and new flag values, and old and new flag value source.

The SDK retains only weak references to the owner, which lets the client app freely destroy owners without issues. Client apps should use a capture list specifying [weak self] inside handlers to avoid retain cycles causing a memory leak.

The SDK executes handlers on the main thread. LDChangedFlag does not know the type of oldValue or newValue. The client app should cast the value into the type needed.

LDObserverOwner Lifecycle

The lifetime of the LDObserverOwner must extend for at least as long as you want to receive flag change notifications.

To configure the client:

1let flagKey = "MY_OBSERVE_FLAG_KEY"
2let flagObserverOwner = flagKey as LDObserverOwner
3
4let ldClient = LDClient.get()!
5
6ldClient.observe(keys: [flagKey], owner: flagObserverOwner, handler: { changedFlags in
7 if changedFlags[flagKey] != nil {
8 // Your code here
9 }
10})
11
12ldClient.stopObserving(owner: flagObserverOwner)
13
14ldClient.observeFlagsUnchanged(owner: self) {
15 ldClient.stopObserving(owner: self as LDObserverOwner)
16}
17
18ldClient.observeAll(owner: self) {_ in
19 ldClient.stopObserving(owner: self as LDObserverOwner)
20}

JavaScript

The client uses an event emitter pattern which allows you to subscribe to feature flag changes in real time. To subscribe to all feature flag changes, listen for the change event.

Here's how:

1ldclient.on('change', function(settings) {
2 console.log('flags changed:', settings);
3});

The settings object contains a map of updated feature flag keys and values. The map only contains the keys to flags that have changed. You can also subscribe to specific flags.

Here's how:

1ldclient.on('change:YOUR_FLAG_KEY', function(value, previous) {
2 console.log('YOUR_FLAG_KEY changed:', value, '(' + previous + ')');
3});

Node.js (client-side)

The client uses an event emitter pattern which allows you to subscribe to feature flag changes in real time. To subscribe to all feature flag changes, listen for the change event.

Here's how:

1ldClient.on('change', allChanges => {
2 console.log('flags changed:', JSON.stringify(allChanges));
3});
The `allChanges` object contains a map of updated feature flag keys and values. The map only contains the keys to flags that have changed. You can also subscribe to specific flags.

Here's how:

1ldClient.on('change:YOUR_FLAG_KEY', (value, previous) => {
2 console.log('YOUR_FLAG_KEY changed:', value, '(was ' + previous + ')');
3});

React Native

LaunchDarkly manages all flags for a user context by updating flags based on a real-time event stream. When a flag is modified from the LaunchDarkly dashboard, the flag values for the current user update almost immediately.

To perform real-time updates, LaunchDarkly broadcasts an event stream that the React Native SDK listens to.

To perform real-time updates in your app, your app must register listeners for each flag you want to watch.

Here's how:

1if (this.state.listeners.hasOwnProperty(key)) {
2 return;
3}
4let listener = value => Alert.alert('Listener Callback', value);
5client.registerFeatureFlagListener('MY_LISTEN_FLAG_KEY', listener);
6this.setState({listeners: {...this.state.listeners, ...{['MY_LISTEN_FLAG_KEY']: listener}}});

You can also disable listeners by unregistering them:

1this.state.ldClient.unregisterFeatureFlagListener('MY_LISTEN_FLAG_KEY', this.state.listeners['MY_LISTEN_FLAG_KEY']);
2let {['MY_LISTEN_FLAG_KEY']: omit, ...newListeners} = this.state.listeners;
3this.setState({listeners: newListeners});

Xamarin

LaunchDarkly manages all flags for a user context by updating the flag cached based on a real-time event stream. When a flag is modified from the LaunchDarkly dashboard, the flag values for the current user update almost immediately.

The default SDK configuration has been found to be the best combination of low latency updates and minimal battery drain:

  1. When the app is foregrounded, a Server-sent events streaming connection is made to LaunchDarkly. This streaming connection stays open as long as your app is in the foreground and is connected to the internet.
  2. When the app is backgrounded, the stream connection is terminated and the SDK will poll with caching for flag updates every three minutes.
  3. When the app is foregrounded, the SDK reconnects to the stream and fetches the latest flags.
  4. In either the foreground or background, the SDK doesn't try to update unless the device has internet connectivity.

This configuration means that the device gets near real-time updates for feature flag values when the app is in the foreground. You can configure these settings if needed. To learn more, read Configuration

To perform real-time updates in the app, it must register listeners for updates from the streaming/polling connection for each flag you want to watch.

Here's how:

1ldClient.FlagChanged += (sender, eventArgs) => {
2 if (eventArgs.Key == "key-for-flag-i-am-watching") {
3 DoSomethingWithNewFlagValue(eventArgs.NewBoolValue);
4 }
5};

Server-side SDKs

This feature is available in the following server-side SDKs:

.NET

This feature is not available in all SDK versions

The .NET SDK only supports subscribing to flag changes in versions 6.0.0 and higher.

The SDK provides an event-based mechanism to notify you when flag configurations change. LDClient.FlagTracker returns an interface for this mechanism, IFlagTracker.

Any event handler that you add to the IFlagTracker.FlagChanged event will be called with a FlagChangeEvent whenever there is a change in any feature flag's configuration, or in anything else that could indirectly affect the flag value, such as a prerequisite flag or a user segment that the flag uses.

The event data consists only of the flag key. It does not contain a flag value, because in server-side SDKs, there is no such thing as a flag value except when it is evaluated for a specific set of user properties.

The listener method is called synchronously from a background task.

Here's how:

1void LogWheneverAnyFlagChanges(LdClient client) {
2 client.FlagTracker.FlagChanged += (sender, event) =>
3 {
4 Console.WriteLine("Flag \"{0}\" has changed", event.Key);
5 };
6}

To listen for changes in flag values for a specific flag key and user, use IFlagTracker.FlagValueChangeHandler(). It calls your code with a FlagValueChangeEvent. This is equivalent to re-evaluating the flag for that user whenever there is a change in that flag. Because flag values can have different data types, the value is reported using the general type LdValue.

1void LogWheneverOneFlagChangesForOneUser(LdClient client, string flagKey, User user) {
2 client.FlagTracker.FlagChanged += client.FlagTracker.FlagValueChangeHandler(
3 flagKey,
4 user,
5 (sender, event) =>
6 {
7 Console.WriteLine(
8 "Flag \"{0}\" for user \"{1}\" has changed from {2} to {3}",
9 flagKey,
10 user.Key,
11 event.OldValue,
12 event.NewValue
13 );
14 });
15}

Go

This feature is not available in all SDKs or versionsThe Go SDK only supports subscribing to flag changes in versions 5.0.0 and higher.

The Go SDK provides a channel-based mechanism to notify you when flag configurations change. The LDClient.GetFlagTracker() method returns an interface for this mechanism called FlagTracker.

Calling GetFlagTracker().AddFlagChangeListener() provides a channel that receives a FlagChangeEvent whenever there is a change in any feature flag's configuration. These changes include anything that could indirectly affect the flag value, such as a prerequisite flag or a user segment that the flag uses.

The event data consists only of the flag key. It does not contain a flag value, because in server-side SDKs, flags only have values when they are evaluated for a specific set of user properties.

1import (
2 "log"
3 ld "gopkg.in/launchdarkly/go-server-sdk.v5"
4)
5
6func logWheneverAnyFlagChanges(client *ld.LDClient) {
7 updateCh := client.GetFlagTracker().AddFlagChangeListener()
8 go func() {
9 for event := range updateCh {
10 log.Printf("Flag %q has changed", event.Key)
11 }
12 }()
13}

To listen for changes in flag values for a specific flag key and user, use GetFlagTracker().AddFlagValueChangeListener(), which provides FlagValueChangeEvents. This is equivalent to re-evaluating the flag for that user whenever AddFlagChangeListener() reports a change in that flag. Because flag values can have different data types, the value is reported using the general type ldvalue.Value.

1import (
2 "log"
3 ld "gopkg.in/launchdarkly/go-server-sdk.v5"
4 "gopkg.in/launchdarkly/go-sdk-common.v2/lduser"
5 "gopkg.in/launchdarkly/go-sdk-common.v2/ldvalue"
6)
7
8func logWheneverOneFlagChangesForOneUser(client *ld.LDClient, flagKey string, user lduser.User) {
9 updateCh := client.GetFlagTracker().AddFlagValueChangeListener(flagKey, user, ldvalue.Null())
10 go func() {
11 for event := range updateCh {
12 log.Printf("Flag %q for user %q has changed from %s to %s", event.Key,
13 user.GetKey(), event.OldValue, event.NewValue)
14 }
15 }()
16}

With both of these methods, it is the caller's responsibility to consume values from the channel. Letting values accumulate in the channel can cause an SDK goroutine to be blocked.

Java

This feature is not available in all SDK versions

The Java SDK only supports subscribing to flag changes in versions 5.0.0 and higher.

The SDK provides a listener-based mechanism to notify you when flag configurations change. The LDClient.getFlagTracker() method returns an interface for this mechanism, FlagTracker.

Calling getFlagTracker().addFlagChangeListener calls your listener with a FlagChangeEvent whenever there is a change in any feature flag's configuration, or in anything else that could indirectly affect the flag value, such as a prerequisite flag or a user segment that the flag uses.

The event data consists only of the flag key. It does not contain a flag value, because in server-side SDKs, there is no such thing as a flag value except when it is evaluated for a specific set of user properties.

The listener method is called from a worker thread.

Here's how:

1void logWheneverAnyFlagChanges(LDClient client) {
2 client.getFlagTracker().addFlagChangeListener(event -> {
3 System.out.printf("Flag \"%s\" has changed\n", event.getKey());
4 });
5}

To listen for changes in flag values for a specific flag key and user, use getFlagTracker().addFlagValueChangeListener, which provides FlagValueChangeEvents. This is equivalent to re-evaluating the flag for that user whenever addFlagChangeListener() reports a change in that flag. Because flag values can have different data types, the value is reported using the general type LDValue.

1void logWheneverOneFlagChangesForOneUser(LDClient client, string flagKey, LDUser user) {
2 client.getFlagTracker().addFlagValueChangeListener(flagKey, user, event -> {
3 System.out.printf("Flag \"%s\" for user \"%s\" has changed from %s to %s\n", event.getKey(),
4 user.getKey(), event.getOldValue(), event.getNewValue());
5 });
6}

Node.js (server-side)

The SDK provides an event-based mechanism to notify you when flag configurations change.

For example, imagine you have a feature flag named EXAMPLE_FLAG_KEY. If the SDK detects a change in EXAMPLE_FLAG_KEY's configuration, or in anything else that could indirectly affect the flag value, such as a prerequisite flag or a user segment that EXAMPLE_FLAG_KEY uses, it emits two events.

These events are:

  • "update" and
  • "update:EXAMPLE_FLAG_KEY"

You can listen for "update:EXAMPLE_FLAG_KEY" if you only want to know about updates affecting that flag specifically, or "update" if you want to be notified about all updates.

For both of these event types, an extra parameter is sent to event listeners. This object has the single property key, with its value set to the flag key. If you listened for the general "update" event, this lets you see which flag changed.

The event parameter does not contain the flag value. In server-side SDKs, there is no such thing as a flag value except when it is evaluated for a specific set of user properties.

To find out what the effect, if any, of the configuration change was, call variation() after receiving an update event.

Here is an example:

1ldClient.on('update', (param) => {
2 console.log('a flag was changed: ' + param.key);
3});
4
5ldClient.on('update:EXAMPLE_FLAG_KEY', () => {
6 console.log('the EXAMPLE_FLAG_KEY flag was changed');
7});