Feature Flag in React Native using Amplify Authentication
This guide explains how to implement feature flags in a React Native application using Amplify Authentication.
Dependencies
To begin, install the following dependencies in your project:
"@aws-amplify/auth": "6.10.0",
"@aws-amplify/react-native": "1.1.6",
"@aws-sdk/client-appconfigdata": "3.731.1",
"aws-amplify": "6.12.1",
"react-native-get-random-values": "1.11.0",
"readable-stream": "4.7.0",
"text-encoding": "0.7.0",
"web-streams-polyfill": "4.1.0",
"@babel/plugin-transform-class-static-block": "7.26.0",
Babel Configuration
In babel.config.js, include @babel/plugin-transform-class-static-block in the plugins array. The configuration should look like this:
module.exports = function (api) {
api.cache(true);
return {
presets: [
["babel-preset-expo", { jsxImportSource: "nativewind" }],
"nativewind/babel",
],
plugins: [
"react-native-reanimated/plugin",
"@babel/plugin-transform-class-static-block",
],
};
};
Key Setup
Set up the keys for both authentication and AppConfig.
For Authentication:
Amplify.configure({
Auth: {
Cognito: {
userPoolClientId: "YOUR_USER_POOL_CLIENT_ID",
userPoolId: "YOUR_USER_POOL_ID",
identityPoolId: "YOUR_IDENTITY_POOL_ID",
},
},
});
For AppConfig:
ApplicationIdentifier: "YOUR_APPLICATION_IDENTIFIER",
EnvironmentIdentifier: "YOUR_ENVIRONMENT_IDENTIFIER",
ConfigurationProfileIdentifier: "YOUR_CONFIG_PROFILE_IDENTIFIER",
User Authentication
Sign in with the existing user credentials:
const { isSignedIn } = await signIn({
username: "your@email.com",
password: "password",
});
Fetching User Credentials
After successful sign-in, fetch the user credentials:
import { fetchAuthSession } from "aws-amplify/auth";
// Get credentials after successful sign-in
const credentials = (await fetchAuthSession()).credentials;
AppConfig Setup
Using the fetched credentials, configure the AppConfig client:
const appConfig = new AppConfigDataClient({
credentials: {
accessKeyId: credentials.accessKeyId,
secretAccessKey: credentials.secretAccessKey,
sessionToken: credentials.sessionToken,
},
region: "YOUR_AWS_REGION",
});
Start a configuration session:
const startSessionCommand = new StartConfigurationSessionCommand({
ApplicationIdentifier: "YOUR_APPLICATION_IDENTIFIER",
EnvironmentIdentifier: "YOUR_ENVIRONMENT_IDENTIFIER",
ConfigurationProfileIdentifier: "YOUR_CONFIG_PROFILE_IDENTIFIER",
});
const session = await appConfig.send(startSessionCommand);
Retrieve the configuration:
const getConfigCommand = new GetLatestConfigurationCommand({
ConfigurationToken: session.InitialConfigurationToken,
});
const configData = await appConfig.send(getConfigCommand);
const configString = new TextDecoder("utf-8").decode(configData.Configuration);
const parsedFlags = JSON.parse(configString);
Example feature flag log:
{"zp-1": {"enabled": true}}
Redux Implementation
Call the feature flag only once when the app loads and pass it across screens using Redux.
Redux Slice for Feature Flags:
import {
AppConfigDataClient,
GetLatestConfigurationCommand,
StartConfigurationSessionCommand,
} from "@aws-sdk/client-appconfigdata";
import { createAsyncThunk, createSlice } from "@reduxjs/toolkit";
import { fetchAuthSession } from "aws-amplify/auth";
export const fetchFeatureFlags = createAsyncThunk(
"featureFlags/fetch",
async (_, { rejectWithValue }) => {
try {
const credentials = (await fetchAuthSession()).credentials;
const appConfig = new AppConfigDataClient({
credentials: {
accessKeyId: credentials.accessKeyId,
secretAccessKey: credentials.secretAccessKey,
sessionToken: credentials.sessionToken,
},
region: "YOUR_AWS_REGION",
});
const startSessionCommand = new StartConfigurationSessionCommand({
ApplicationIdentifier: "YOUR_APPLICATION_IDENTIFIER",
EnvironmentIdentifier: "YOUR_ENVIRONMENT_IDENTIFIER",
ConfigurationProfileIdentifier: "YOUR_CONFIG_PROFILE_IDENTIFIER",
});
const session = await appConfig.send(startSessionCommand);
const getConfigCommand = new GetLatestConfigurationCommand({
ConfigurationToken: session.InitialConfigurationToken,
});
const configData = await appConfig.send(getConfigCommand);
const configString = new TextDecoder("utf-8").decode(
configData.Configuration,
);
return JSON.parse(configString);
} catch (error) {
return rejectWithValue(String(error));
}
},
);
const featureFlagsSlice = createSlice({
name: "featureFlags",
initialState: {
flags: null,
loading: false,
error: null,
},
reducers: {},
extraReducers: (builder) => {
builder
.addCase(fetchFeatureFlags.pending, (state) => {
state.loading = true;
state.error = null;
})
.addCase(fetchFeatureFlags.fulfilled, (state, action) => {
state.loading = false;
state.flags = action.payload;
state.error = null;
})
.addCase(fetchFeatureFlags.rejected, (state, action) => {
state.loading = false;
state.error = action.payload;
state.flags = null;
});
},
});
export default featureFlagsSlice.reducer;
Using the Feature Flag
Here’s a simple example of how to use the feature flag in a React Native component:
const App = () => {
const { flags, loading, error } = useSelector((state) => state.featureFlags);
const [enabled, setEnabled] = useState(true);
useEffect(() => {
setEnabled(flags["zp-1"]?.enabled);
}, [flags]);
if (loading) return <ActivityIndicator size="large" color="black" />;
if (error) return <ErrorMessage />;
return enabled ? (
<View>
<Text>Feature Enabled</Text>
</View>
) : (
<View>
<Text>Feature Disabled</Text>
</View>
);
};