Feature Flags setup in Flutter with Firebase Remote Config
This article aims to teach how to setup some feature flags logic in your flutter app using Firebase Remote Config free service, leveraging of its amazing real time updates feature 🚀. For those who like to check the whole code, an example app was implemented in this GitHub repository: https://github.com/DavidGrunheidt/flutter-feature-flags-firebase-example.
Before starting coding, let's discuss a little bit about feature flags.
Why should I use Feature Flags on my Flutter app?
- For apps not in production yet, feature flags are a good way to easily enable or disable an entire feature. Say you just created this app and are posting it in production the first time. You can easily disable a feature in case there’s an error that was discovered only in production. The optimal case of course is not having any errors in production, but we all know that may happen from time to time, and with feature flags you have one more tool in your hand to prevent the users from encountering an error. After you turn off that feature you can work on the fix.
- For apps already in production, using feature flags and staged rollouts can help you out releasing a feature progressively for a percentage of users. You can start with a small amount like 1%, 5% or 10%, depending on how many users your app already has. After that, you can monitor your crash report service (Firebase Crashlytics for example) for unusual crashes, exceptions and errors. After some time, if everything seems normal, you can increase the feature flag rollout percentage and repeat the loop until the rollout is on 100%
- With feature flags you can also have specific enabling conditions using a user property like
city
. This depends on your “user” model of course. For example, you can activate a feature only for the user withcity = São Paulo
. Firebase has audiences, which groups users under some conditions including user properties. This allows you to have a feature flag condition by a desired audience.
Feature flags with Firebase Remote Config setup steps:
1 — Go to https://console.firebase.google.com/ and check if you already have a project for your current Flutter app. If not, click on Add Project
and go through the steps. It will ask you to activate Analytics
. If you want the ability to activate and deactivate feature flags by some user property, like email, city, age, or something like that, you should activate analytics. If you already have a project, check if analytics is enabled on the left menu under the Analytics Dashboard.
2 — On your pubspec.yaml
add the firebase dependencies. You can check this file under the example GitHub repository. After that, open a terminal, navigate to the root folder of your project and run a flutter pub get.
firebase_core: ^2.15.1
firebase_analytics: ^10.4.5
firebase_remote_config: ^4.2.5
3— Configure Firebase. Nowadays this is done automatically thanks to the Firebase CLI tool. You can check their official docs on how to do it https://firebase.google.com/docs/flutter/setup#install-cli-tools. If the docs aren't enough, here's how to do it in your terminal
- 3.1 Run
firebase login
. This will open a new window on your default web browser. You should login with the google account that contains the Firebase project you want to add to your Flutter app. - 3.2 Run
dart pub global activate flutterfire_cli
. These commands enableflutterfire
command, which we'll use in the next step. - 3.3 Run
flutterfire configure
. If everything went right before it will ask you to select which project you want to add to your Flutter app. You can scroll up or down using arrow keys and enter for selecting.
- 3.4 After that it will ask which platforms your app supports. You can select or deselect each one with space and scroll with the arrows again. Once you are done, just hit enter again.
- 3.5 Android and iOS apps will be registered on Firebase, files for each platform will be fetched and a configuration file will be created under
lib/firebase_options.dart
. If you already have that file, it will ask if you want to override it.
4 — Create a file called feature_flag_keys.dart
that will contain all your feature flag keys. These keys are used to access a bool
value stored on Firebase Remote Config.
// Feature flags
const kEnableMyCarTabKey = 'enable_my_car_tab';
const kEnableMyCarMileageKey = 'enable_my_car_mileage';
const kEnableMyCityTabKey = 'enable_my_city_tab';
5— Create a class called RemoteConfigRepository
that will handle all your remote config logic. In the given example we are using MobX for defining observable variables that trigger state changes and widget rebuilds when their value changes. Variables defined on feature_flag_keys.dart
will be accessed here. The implementation of this class will totally vary depending on how you are implementing state management in your project.
import 'package:firebase_remote_config/firebase_remote_config.dart';
import 'package:flutter/foundation.dart';
import 'package:mobx/mobx.dart';
import '../helpers/feature_flag_keys.dart';
part 'remote_config_repository.g.dart';
class RemoteConfigRepository = _RemoteConfigRepository with _$RemoteConfigRepository;
abstract class _RemoteConfigRepository with Store {
final _remoteConfig = FirebaseRemoteConfig.instance;
@observable
bool enableMyCarTab = false;
@observable
bool enableMyCarMileage = false;
@observable
bool enableMyCityTab = false;
@action
Future<void> init() async {
try {
if (!kIsWeb) _remoteConfig.onConfigUpdated.listen(_updateConfigs, onError: (_) {});
await _setDefaultConfigs();
await _remoteConfig.setConfigSettings(
RemoteConfigSettings(
fetchTimeout: kDebugMode ? const Duration(minutes: 1) : const Duration(seconds: 10),
minimumFetchInterval: kDebugMode ? const Duration(hours: 4) : const Duration(minutes: 1),
),
);
await _remoteConfig.fetchAndActivate();
} finally {
await _updateConfigs(RemoteConfigUpdate({}));
}
}
@action
Future<void> _updateConfigs(RemoteConfigUpdate remoteConfigUpdate) async {
await _remoteConfig.activate();
enableMyCarTab = _remoteConfig.getBool(kEnableMyCarTabKey);
enableMyCityTab = _remoteConfig.getBool(kEnableMyCityTabKey);
enableMyCarMileage = _remoteConfig.getBool(kEnableMyCarMileageKey);
}
Future<void> _setDefaultConfigs() async {
return _remoteConfig.setDefaults({});
}
}
- 5.1 On
init
method,_remoteConfig.onConfigUpdate
streams updates from Firebase Remote Config. You can listen to these updates usinglisten
method of Stream. When a new update arrives,_updateConfigs
runs and your feature flags variables can be updated inside this function. If these variables are being observed in a Widget, any change on them will trigger a widget rebuild which may hide an existing feature or show a new feature. In our case the variables areenableMyCarTab
,enableMyCityTab
andenableCarMileage
but this will totally change according to your needs. - 5.2 We also fetch values one time on
init
to update previously cached ones and callupdateConfigs
to update our observable variables the first time.
6— Create a class AnalyticsRepository
that will handle your analytics logic, such as logging events and setting user properties.
import 'package:firebase_analytics/firebase_analytics.dart';
import 'package:flutter/foundation.dart';
import '../helpers/app_constants.dart';
import '../models/user_info.dart';
class AnalyticsRepository {
static final analytics = FirebaseAnalytics.instance;
Future<void> init() {
setUserProperties(userInfo);
return analytics.setAnalyticsCollectionEnabled(kReleaseMode);
}
Future<void> setUserProperties(UserInfo userInfo) async {
if (kDebugMode) print('[AnalyticsRepository] - setUserProperties ${userInfo.toJson()}');
for (final userProperty in userInfo.toJson().entries) {
await analytics.setUserProperty(name: userProperty.key, value: userProperty.value.toString());
}
}
}
- 6.1
setAnalyticsCollectionEnabled
should be called after asking forApp Tracking Transparency
permission on iOS and on Android can be set totrue
. - 6.2
setUserProperties
can be called after the user performs a login operation on your app, for example, with the user data retrieved from your API. Just be sure to not call this function every time the app starts.
7 — Implement access logic to the classes create at items 5 and 6. In our case these classes will be located using GetIt
. File dependency_locator.dart
initializes all dependencies and also calls they init
method, if they have one. setupRepositoryDependencies()
will be called on main
function.
import 'package:get_it/get_it.dart';
import '../../repositories/remote_config_repository.dart';
import '../repositories/analytics_repository.dart';
GetIt dependencyLocator = GetIt.instance;
Future<void> setupRepositoryDependencies() async {
dependencyLocator.registerLazySingleton<RemoteConfigRepository>(RemoteConfigRepository.new);
dependencyLocator.registerLazySingleton<AnalyticsRepository>(AnalyticsRepository.new);
await dependencyLocator<AnalyticsRepository>().init();
await dependencyLocator<RemoteConfigRepository>().init();
}
8 — Call WidgetsFlutterBinding.ensureInitialized()
, Firebase.initializeApp()
and setupRepositoryDependencies()
in your main function. This way you make sure that everything is initialized before being used.
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
await setupRepositoryDependencies();
return runApp(const MyApp());
}
Creating flags on Firebase Remote Config and testing:
Last steps are all it takes for setting up a feature flag approach to your Flutter app. These next steps will cover how to create feature flags on Firebase Remote Config as bool
values and test their change in the app. We will use examples to demonstrate how to set and use these flags. The example app uses MVVM architecture with GetIt for dependency locator and MobX for state management. It has a feature called Home with three tabs:
- Home: Initial view with a welcome message
- My Car: Displays the user car model and its mileage. The tab is only shown if
enable_my_car_tab
remote configbool
value is true. Also, the mileage is only displayed ifenable_my_car_mileage
remote configbool
value is true. Both these values act as a feature flag. - My City: Displays the user city name. It’s only shown if
enable_my_city
remote configbool
value is true.
You can check the example feature implementation here: https://github.com/DavidGrunheidt/flutter-feature-flags-firebase-example/tree/master/lib/features/home
9 — Go to https://console.firebase.google.com/ , select your project and on the left menu find Remote Config
. You can create a new key:value
pair clicking the button Add Parameter
. You can also add parameters to groups if you think your app is going to have a lot of remote configs. Be sure to add the same key name you created on feature_flag_keys.dart
. Here's a gif showing how to create a new parameter:
10 — Check if your UI element attached to some feature flag is changing when you change the flag on Firebase Remote Config. In the example case, My Car
and My City
tabs should be enabled/disabled when we change enable_my_car_tab
and enable_my_city_tab
. Inside My Car
tab, car mileage should be displayed only if enable_my_car_mileage
is true. Let's check if this is happening:
As you can see, the changes happened automatically without needing to rebuild the app, which means the user will get these types of changes automatically in their apps, while in use, without needing to reopen it. For this you need to implement feature flags variables in your code as observable variables that trigger widget rebuilds, and have the realtime listener configured to update these, as we did on item 5.
11 — Create Custom Definitions on Firebase according to your user properties. This example has a user model with carName
, carMileage
, city
. If your user has a lot of fields, try to use the most important ones, which you know that can be used to differentiate/group them, or that it's already used by your company as a grouping field.
12 — Create conditions and use these custom definitions when needed, or create a condition for staged rollouts for feature flags. You can create a condition where a random user is in a percentage, and attach that to a feature flag to enable it only for 5% of your users, for example.
That's basically it for starting to use feature flags in your Flutter app. These examples are just the tip of the iceberg on what you can do with Firebase services for implementing Feature Flags. Hope you enjoy and feel free to leave a comment asking something. Check out my profile for other tutorials https://medium.com/@davidordine