Launch Darkly — More than just Feature Flagging in a managed Expo app

Jeanette Popken
Adventures in Expo
Published in
4 min readJun 18, 2024

We finally got the go ahead to migrate our feature flags from Feature Manager in App Configuration in Azure Portal to Launch Darkly.

We went from messy screens like below (that had duplicate keys for different platforms, split across 2 azure subscriptions, with no fine grain control):

To this beauty:

And it gave us the fine grain control we had been missing, the ability to have one flag and via targeting rules control what is should do for mobile VS web, A/B Testing, Beta Pilot Programs, custom rules by mobile OS, user, etc.

Basically this:

Here is how the migration experience was for us:

I found some docs that say, their solution supports Expo and I get super duper excited. I follow the steps and dah dah dahhhhhhh, it doesn’t work.

So here is my quest journey of getting Launch Darkly in our app. To remind folks, we have a managed expo app and we do use a custom dev-client (and not expo go).

But let’s back up, and check out their documentation, for instance:

Managed Expo app’s do not run pod-install commands. Don’t panic. Just skip that step.

Kick off a new dev-client build and when it’s done, install it on your device / simulator.

Then you will want to set up Launch Darkly with a context. When our app mounts and before we know any user information, we initialize a device context via the identify call.

*Note — I found using the default timeout was not enough time and there would be no errors but also the context was empty. Older devices especially will need more time. This was why it was not immediately working for me

// Mobile key is found for mobile enabled flags in Launch Darkly
// Click on the 3 dots for an environment and you'll see the option to copy the environment's mobile key

const lcClient = new ReactNativeLDClient(Constants.expoConfig.extra.launchDarklyMobileKey, AutoEnvAttributes.Enabled);

// Identify device context for LD

ldClient
identify({ kind: 'device', key: Device.deviceName + Device.modelName }, { timeout: 25 })
.catch((error) => console.log(error));

This will return all flags and if any rules/segments are targeting devices then they would be applied.

Then once we get user information from a valid auth token, we call identify again.

// retrieve the existing client
const ldClient = useLDClient();

// Identify the user context in LaunchDarkly
ldClient
.identify(
{
kind: 'user',

key: user.contactId,
firstName: user.firstName,
lastName: user.lastName,
email: user.email,
},
{ timeout: 25},
)
.catch((error) =>
console.log(error);
);
}

This will return all the flags again and if any rules / segments are targeting users then they would be applied. We are just targeting user contexts but if you need rules for both, then you would call identify with kind: ‘multi’ and have device and user both in your payload.

Then I wrote a hook to get allFlags and transform and serve them up in an easy to read way.

const useLDFeatureFlags = () => {
//Get instantiated Client with Context
const LDClient = useLDClient();
//Open connection and get all flags
const allFlags = LDClient.allFlags();

// Utility types and functions
type KebabToCamelCase<S> = S extends `${infer T}-${infer U}`
? `${Lowercase<T>}${Capitalize<KebabToCamelCase<U>>}`
: S;

type KeysToObject<T> = {
[K in keyof T as KebabToCamelCase<K & string>]: T[K];
};

const convertKeysToCamelCase = <T>(obj: T): KeysToObject<LDFeatureFlags> => {
const result = {} as KeysToObject<LDFeatureFlags>;
for (const key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
const camelCaseKey = key.replace(/-([a-z])/g, (g) => g[1].toUpperCase());
(result as any)[camelCaseKey] = obj[key];
}
}
return result;
};

//Transform: remove hyphens and camelCase EX enable-hello-world -> enableHelloWorld
const transformedFlags = useMemo(() => convertKeysToCamelCase(allFlags), [allFlags]);

return useMemo(
() => ({
...transformedFlags,
}),
[transformedFlags],
);
};

export default useLDFeatureFlags;

//how to use this hook
//const { enableHelloWorld } = useLDFeatureFlags();

Then you can set up rules and segments to control at a very fine grain level what users get what feature. A/B testing, Beta testing, Pilot programs. The possibility is endless.

Here is a rule that targets users:

Here is a rule that enables the flag for ios and android but would serve up disabled for our web app.

Happy coding!

--

--