Subscribing to flag changes
Read time: 16 minutes
Last edited: May 30, 2024
Overview
This topic explains how to configure each SDK to allow applications to subscribe to flag change notifications. This feature is available for client-side and server-side SDKs.
A context is a generalized way of referring to the people, services, machines, or other resources that encounter feature flags in your product. Contexts replace another data object in LaunchDarkly: "users." To learn more, read Contexts.
Creating contexts and evaluating flags based on them is supported in the latest major versions of most of our SDKs. For these SDKs, the code samples on this page include the two most recent versions.
Subscribe to flag change notifications
Flag change notifications allow your application to react to and consider flag changes immediately instead of waiting for your code to re-evaluate a flag. Some common use cases include updating content as part of a promotion, enabling/disabling feature code paths efficiently, showing outage bulletins to end users, and granting entitlements to groups of end users.
Use caution if you use a flag change notification to trigger an identify call as this could lead to an infinite loop where the flag changes the context, which changes the flag, and on and on. For example, you may include additional application information if a flag is true, but you will need to make sure that the inclusion of that information does not cause the flag to toggle.
Details about each SDK's configuration are available in the SDK-specific sections below.
Client-side SDKs
This feature is available in the following client-side SDKs:
- .NET (client-side)
- Android
- C++ (client-side)
- Electron
- Flutter
- iOS
- JavaScript
- Node.js (client-side)
- React Native
- Roku
.NET (client-side)
Expand .NET (client-side) code sample
The client uses an event pattern which allows your app to subscribe to feature flag changes in real time.
To subscribe to feature flag changes, register listeners for change events from the SDK:
client.FlagTracker.FlagChanged += (sender, eventArgs) => {if (eventArgs.Key == "key-for-flag-i-am-watching") {DoSomethingWithNewFlagValue(eventArgs.NewBoolValue);}};
To learn more, read IFlagTracker
.
Android
Expand Android code sample
The client uses a listener pattern which allows your app to subscribe to feature flag changes in real time.
To subscribe to feature flag changes, register listeners for change events from the SDK:
String flagKey = "yourFlagKey";FeatureFlagChangeListener listener = new FeatureFlagChangeListener() {@Overridepublic void onFeatureFlagChange(String flagKey) {boolean newValue = LDClient.get().boolVariation(flagKey, false);}};LDClient.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:
LDClient.get().unregisterFeatureFlagListener(flagKey, listener);
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:
LDAllFlagsListener listener = new LDAllFlagsListener() {@Overridepublic void onChange(List<String> flagKeys) {// Get new values for flagKeys or other operations}};// register all flags listenerLDClient.get().registerAllFlagsListener(listener);// when done with all flags listener it should be unregisteredLDClient.get().unregisterAllFlagsListener(listener);
C++ (client-side)
Expand C++ (client-side) code sample
The client exposes an interface which allows your app to subscribe to feature flag changes in real time.
To subscribe to feature flag changes, register listeners for change events from the SDK:
auto listener = client.FlagNotifier().OnFlagChange("flag-key-123abc", [](auto event) {if (event->Deleted()) {std::cout << "The flag was deleted" << std::endl;} else {std::cout << "The flag was " << event->OldValue() << " and now it is " << event->NewValue() << std::endl;}});/* Then, you can disconnect the listener later */listener->Disconnect();
If you are working in C, there are a few more steps:
-
Define a callback to receive the flag change notification:
void OnFlagChange(char const* flag_key,LDValue new_value,LDValue old_value,bool deleted,void* user_data) {if (deleted) {printf("The flag %s was deleted\n", flag_key);} else {printf("The flag %s was updated\n", flag_key);}} -
Assign the callback by creating a listener connection:
struct LDFlagListener listener;LDFlagListener_Init(listener);listener.FlagChanged = OnFlagChange;/* You may optionally assign the UserData pointer, which will be passed into FlagChanged. *//* listener.UserData = &some_struct; */LDListenerConnection connection =LDClientSDK_FlagNotifier_OnFlagChange(sdk, "flag-key-123abc", listener);/* You can disconnect the listener later */LDListenerConnection_Disconnect(connection); -
Ensure the connection is freed when you are done with it:
LDListenerConnection_Free(connection);
To learn more, read FlagNotifier
.
Electron
Expand Electron code sample
The client uses an event emitter pattern which allows your app to subscribe to feature flag changes in real time.
To subscribe to feature flag changes, register listeners for change events from the SDK:
client.on('update:flag-key-123abc', (newValue, oldValue) => {console.log('The flag was ' + oldValue + ' and now it is ' + newValue);});
Or, you can listen for all feature flag changes:
client.on('update', (allFlagChanges) => {Object.keys(allFlagChanges).forEach((key) => {console.log('Flag ' + key + ' is now ' + allFlagChanges[key]);});});
Subscribing to change
events automatically turns on streaming mode, unless you have explicitly set streaming
to false
.
Flutter
Expand Flutter code sample
The client uses an observer pattern which allows your app to subscribe to feature flag changes in real time.
To subscribe to feature flag changes, register listeners for change events from the SDK:
final sub = client.flagChanges.listen((changeEvent) {for(var flagKey in changeEvent.keys) {print(client.jsonVariation(flagKey, LDValue.ofString('default')));}});
flagChanges
is a stream that emits all flag changes, so you can use a single listener for multiple flags.
You can disable listeners by unregistering them:
sub.cancel();
You can also use flagChanges
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.
To learn more, read flagChanges
.
iOS
Expand iOS code sample
The client uses an observer pattern which allows your app to subscribe to feature flag changes in real time. To subscribe to feature flag changes, register listeners for change events from the SDK.
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.
The lifetime of the LDObserverOwner
must extend for at least as long as you want to receive flag change notifications.
To configure the client:
let flagKey = "flag-key-123abc"let flagObserverOwner = flagKey as LDObserverOwnerlet client = LDClient.get()!client.observe(keys: [flagKey], owner: flagObserverOwner, handler: { changedFlags inif changedFlags[flagKey] != nil {// Your code here}})client.stopObserving(owner: flagObserverOwner)client.observeFlagsUnchanged(owner: self) {client.stopObserving(owner: self as LDObserverOwner)}client.observeAll(owner: self) {_ inclient.stopObserving(owner: self as LDObserverOwner)}
JavaScript
Expand JavaScript code sample
The client uses an event emitter pattern which allows your app to subscribe to feature flag changes in real time.
To subscribe to all feature flag changes, register listeners for change events from the SDK:
client.on('change', (settings) => {console.log('flags changed:', settings);});
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:
client.on('change:flag-key-123abc', (value, previous) => {console.log('flag-key-123abc changed:', value, '(' + previous + ')');});
Node.js (client-side)
Expand Node.js (client-side) code sample
The client uses an event emitter pattern which allows your app to subscribe to feature flag changes in real time.
To subscribe to all feature flag changes, register listeners for change events from the SDK:
client.on('change', allChanges => {console.log('flags changed:', JSON.stringify(allChanges));});
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:
client.on('change:flag-key-123abc', (value, previous) => {console.log('flag-key-123abc changed:', value, '(was ' + previous + ')');});
React Native
Expand React Native code sample
The client uses an event emitter pattern which allows your app to subscribe to feature flag changes in real time. The available event types include error
and change
.
To subscribe to all feature flag changes, register listeners for change events from the SDK. Here's how:
const changeHandler = (context: LDContext, changedKeys: string[]) => {console.log('listening to change');};client.on('change', changeHandler);
You can also disable listeners by unregistering them:
const changeHandler = (context: LDContext, changedKeys: string[]) => {console.log('listening to change');};client.off('change', changeHandler);
Server-side SDKs
This feature is available in the following server-side SDKs:
.NET (server-side)
Expand .NET (server-side) code sample
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 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 context.
The listener method is called synchronously from a background task.
Here's how:
void LogWheneverAnyFlagChanges(LdClient client) {client.FlagTracker.FlagChanged += (sender, event) =>{Console.WriteLine("Flag \"{0}\" has changed", event.Key);};}
To listen for changes in flag values for a specific flag key and context, use IFlagTracker.FlagValueChangeHandler()
. It calls your code with a FlagValueChangeEvent
. This is equivalent to re-evaluating the flag for that context 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
.
void LogWheneverOneFlagChangesForOneContext(LdClient client, string flagKey, Context context) {client.FlagTracker.FlagChanged += client.FlagTracker.FlagValueChangeHandler(flagKey,context,(sender, event) =>{Console.WriteLine("Flag \"{0}\" for context \"{1}\" has changed from {2} to {3}",flagKey,context.Key,event.OldValue,event.NewValue);});}
Go
Expand Go code sample
The 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 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 evaluation context.
import ("log"ld "github.com/launchdarkly/go-server-sdk/v6")func logWheneverAnyFlagChanges(client *ld.LDClient) {updateCh := client.GetFlagTracker().AddFlagChangeListener()go func() {for event := range updateCh {log.Printf("Flag %q has changed", event.Key)}}()}
To listen for changes in flag values for a specific flag key and context, use GetFlagTracker().AddFlagValueChangeListener()
, which provides FlagValueChangeEvent
s. This is equivalent to re-evaluating the flag for that context 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
.
import ("log"ld "github.com/launchdarkly/go-server-sdk/v6""github.com/launchdarkly/go-sdk-common/v3/ldcontext""github.com/launchdarkly/go-sdk-common/v3/ldvalue")func logWheneverOneFlagChangesForOneUser(client *ld.LDClient, flagKey string, context ldcontext.Context) {updateCh := client.GetFlagTracker().AddFlagValueChangeListener(flagKey, context, ldvalue.Null())go func() {for event := range updateCh {log.Printf("Flag %q for context %q has changed from %s to %s", event.Key,context.Key(), event.OldValue, event.NewValue)}}()}
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
Expand Java code sample
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:
void logWheneverAnyFlagChanges(LDClient client) {client.getFlagTracker().addFlagChangeListener(event -> {System.out.printf("Flag \"%s\" has changed\n", event.getKey());});}
To listen for changes in flag values for a specific flag key and context, use getFlagTracker().addFlagValueChangeListener
, which provides FlagValueChangeEvent
s. This is equivalent to re-evaluating the flag for that context 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
.
void logWheneverOneFlagChangesForOneContext(LDClient client, string flagKey, LDContext context) {client.getFlagTracker().addFlagValueChangeListener(flagKey, context, event -> {System.out.printf("Flag \"%s\" for context \"%s\" has changed from %s to %s\n", event.getKey(),context.getKey(), event.getOldValue(), event.getNewValue());});}
Node.js (server-side)
Expand Node.js (server-side) code sample
The SDK provides an event-based mechanism to notify you when flag configurations change.
For example, imagine you have a feature flag named flag-key-123abc
. If the SDK detects a change in flag-key-123abc
's configuration, or in anything else that could indirectly affect the flag value, such as a prerequisite flag or a user segment that flag-key-123abc
uses, it emits two events.
These events are:
"update"
and"update:flag-key-123abc"
You can listen for "update:flag-key-123abc"
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 kinds, 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 know 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:
client.on('update', (param) => {console.log('a flag was changed: ' + param.key);});client.on('update:flag-key-123abc', () => {console.log('the flag-key-123abc flag was changed');});
Python
Expand Python code sample
The Python SDK only supports subscribing to flag changes in versions 9.1.0 and higher.
The SDK provides a listener-based mechanism to notify you when flag configurations change. The LDClient#flag_tracker
method returns an interface for this mechanism, FlagTracker
.
Calling flag_tracker.add_listener
calls your listener with a FlagChange
event whenever there is a change to any of the following:
- the flag's configuration
- the prerequisite flag's configuration
- a segment used by the flag
The event data consists only of the flag key. It does not contain a flag value, because server-side SDKs only evaluate flags when you provide a specific context.
The listener method is called from the caller's thread.
Here's how:
def flag_change_listener(flag_change):print(f"{flag_change.key} has changed")listener = ldclient.get().flag_tracker.add_listener(flag_change_listener)
To listen for changes in flag values for a specific flag key and context, use flag_tracker.add_flag_value_change_listener
, which provides FlagValueChange
events. This is equivalent to re-evaluating the flag for that context whenever add_listener
reports a change in that flag.
def flag_value_change_listener(flag_change):print(f"{flag_change.key} has changed from {flag_change.old_value} to {flag_change.new_value}")listener = ldclient.get().flag_tracker.add_flag_value_change_listener('example-flag-key', context, flag_value_change_listener)
Ruby
Expand Ruby code sample
The Ruby SDK only supports subscribing to flag changes in versions 7.2.0 and higher.
The SDK provides a listener-based mechanism to notify you when flag configurations change. The LDClient#flag_tracker
method returns an interface for this mechanism, FlagTracker
.
Calling flag_tracker.add_listener
calls your listener with a FlagChange
event whenever there is a change to any of the following:
- the flag's configuration
- the prerequisite flag's configuration
- a segment used by the flag
The event data consists only of the flag key. It does not contain a flag value, because server-side SDKs only evaluate flags when you provide a specific context.
The listener method is called from a worker thread.
Here's how:
class Listenerdef update(status)puts "Flag #{status.key} has changed"endendclient.flag_tracker.add_listener(Listener.new)
To listen for changes in flag values for a specific flag key and context, use flag_tracker.add_flag_value_change_listener
, which provides FlagValueChange
events. This is equivalent to re-evaluating the flag for that context whenever add_listener
reports a change in that flag.
class Listenerdef update(changed)puts "Flag #{changed.key} has changed from #{changed.old_value} to #{changed.new_value}"endendclient.flag_tracker.add_flag_value_change_listener("example-flag-key", context, Listener.new)
Roku
Expand Roku code sample
You can use Roku's observeField
method on your node to respond to changes in flags.
Here's how:
' replace "onFeatureChange" with the name of your handler functionslaunchDarklyNode.observeField("flags", "onFeatureChange")