

Firebase Remote Config Generator for Flutter
Transform your Firebase Remote Config implementation from fragile magic strings to type-safe, robust code generation.
Firebase Remote Config lets you change your app’s behavior without releasing updates. But there’s a problem: the standard way to use it relies on “magic strings” that can break your app with simple typos.
The Problem: Using strings like getString("welcome_message")
is risky because:
- Typos cause silent failures
- No compile-time safety
- Hard to refactor
- Poor developer experience
The Solution: Use remote_config_gen to generate type-safe code that catches errors at compile time instead of runtime.

Table of Contents
- Understanding Firebase Remote Config
- Problems with Magic Strings
- The Solution: remote_config_gen
- How to Set it Up
- Building Feature Flags
Understanding Firebase Remote Config
Before we look at the problems with the standard way of using Firebase Remote Config, let’s first understand what it is, what it can do, and how it works.
What is Firebase Remote Config?
At its core, Firebase Remote Config is a cloud service that allows developers to store a set of key-value parameters on Firebase’s servers. The client application can then fetch these parameters and use their values to alter its behavior and appearance dynamically. This decouples the application’s configuration from its compiled code, providing a powerful mechanism for post-launch updates without requiring a new release to the Apple App Store or Google Play Store.
Its capabilities extend far beyond simple configuration changes, enabling a suite of modern development practices:
Dynamic UI and Content Updates: Developers can change display text, promotional banners, color themes, or entire layouts to support marketing campaigns, seasonal events, or simply to refresh the user experience.
Feature Management and Gradual Rollouts: Parameters can serve as “feature flags,” which are used to enable or disable functionality within the app. This is fundamental for modern DevOps practices like gradual, percentage-based rollouts, where a new feature can be exposed to a small subset of users (e.g., 1%, then 10%, then 50%) to monitor for crashes or negative feedback before a full launch. It also enables the use of “kill switches” to instantly disable a faulty feature in production.
User Personalization: The service can deliver different parameter values to different user segments. These segments can be defined by a wide range of criteria, including Google Analytics audiences, app version, operating system, language, country, or custom user properties you define. This allows for tailoring the app experience to specific cohorts of users.
A/B Testing: Remote Config integrates seamlessly with Google Analytics to facilitate A/B testing. Developers can create experiments that present different parameter values to different groups of users and measure the impact on key metrics like engagement, retention, or conversion.
AI-Powered Personalization: A more advanced capability allows Firebase’s machine learning algorithms to automatically optimize a parameter’s value for each individual user to maximize a predefined goal, such as ad clicks or in-app purchases.
The FRC Lifecycle: A Technical Walkthrough
Understanding the sequence of operations in the Remote Config client SDK is crucial for implementing it correctly and avoiding common pitfalls like UI jank or stale data.
Setting In-App Defaults: The first and most critical step in any Remote Config implementation is
to provide a set of in-app default values using the setDefaults()
method. These are the values the
app will use if it cannot connect to the Firebase backend, if the connection times out, or before
the first successful fetch has completed. This ensures the application always starts in a known,
predictable state and never crashes due to a missing configuration value. These defaults act as the
application’s ultimate safety net.
Fetching Values: To retrieve updated parameters from the Firebase backend, the app calls a fetch
method, most commonly fetchAndActivate()
. This single call performs two distinct operations: it
fetches the latest values from the server and caches them locally on the device.
Understanding Caching and Throttling: To prevent apps from overloading the backend with
excessive requests, the Remote Config client SDK employs aggressive caching. By default, fetched
values are cached for 12 hours. This means that even if fetchAndActivate()
is called multiple
times, a new network request will only be made if the cache is older than this minimum fetch
interval. While essential for production stability, this behavior can be a major source of
frustration during development when rapid iteration is needed. To address this, developers can use
setConfigSettings()
to temporarily set a very low minimumFetchInterval
(e.g., one minute). It is
imperative that this low interval is only used in debug builds and is reverted to a longer duration
for production releases to avoid client-side throttling.
Activating Values: The fetch operation merely downloads and caches values; it does not make them
“live” in the app. A separate step, activation, is required. The activate()
method makes the most
recently fetched set of parameters available to the app’s getter methods (e.g., getString()
,
getBool()
). This separation gives developers precise control over when a UI change occurs. For
example, new values can be fetched in the background and only activated on the next app start to
prevent a jarring visual change while the user is in the middle of a task. The fetchAndActivate()
method is a convenient wrapper that performs both steps sequentially.
Real-Time Updates: The most modern and powerful approach to receiving updates is to use the
onConfigUpdated
stream. By adding a listener to this stream, the app opens a persistent connection
to the Remote Config backend. Whenever parameters are published in the Firebase console, the backend
sends a signal to the client, which then automatically fetches the new values in real-time,
completely bypassing the minimumFetchInterval
setting. This is the key to enabling true real-time
functionality like instant feature toggling.
Standard Flutter Implementation (The “Before” Picture)
To establish a baseline, here is a minimal but complete example of how Firebase Remote Config is
typically set up in a Flutter application using the standard firebase_remote_config
package. This
code will serve as our point of comparison when we refactor it to a type-safe implementation in a
later chapter.
// main.dart
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_remote_config/firebase_remote_config.dart';
import 'package:flutter/material.dart';
import 'firebase_options.dart'; // Generated by FlutterFire CLI
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
// Get the Remote Config instance
final remoteConfig = FirebaseRemoteConfig.instance;
// Set configuration settings for development
await remoteConfig.setConfigSettings(RemoteConfigSettings(
fetchTimeout: const Duration(minutes: 1),
minimumFetchInterval: const Duration(seconds: 10), // Low for development
));
// Set in-app default values
await remoteConfig.setDefaults(const {
"welcome_message": "Hello from a default value!",
"show_promo_banner": false,
});
// Fetch and activate remote values
await remoteConfig.fetchAndActivate();
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('Remote Config Demo'),
),
body: Center(
// Accessing the value using a "magic string"
child: Text(FirebaseRemoteConfig.instance.getString('welcome_message')),
),
),
);
}
}
This implementation is functional, but as we will now explore, it harbors significant underlying risks that can compromise application stability and developer velocity.
Problems with Magic Strings in Remote Config
The standard Remote Config API, while powerful, has a big problem: it uses “magic strings.” This section explains the real risks and problems this approach creates.
The “Magic String” Anti-Pattern
A “magic string” is a hardcoded string literal used as an identifier or key, whose meaning is not
self-evident from the code and is not validated by the compiler. The line
remoteConfig.getString("welcome_message")
is a perfect example. From the Dart compiler’s
perspective, "welcome_message"
is just an arbitrary sequence of characters. There is no static
link between this string in the application code and the parameter key defined in the Firebase
console. This connection exists only by convention and relies entirely on developer discipline—a
notoriously unreliable foundation for building robust, scalable software.
This approach creates a fragile, implicit contract between two disparate systems (the client code and the Firebase backend). When this contract is inevitably violated, the consequences manifest as runtime bugs, not compile-time errors.
Common Pitfalls and Their Real-World Consequences
The reliance on magic strings introduces several distinct categories of risk, each with significant real-world consequences for development teams.
1. Compile-Time Blindness to Typos
The Problem: A developer intends to access the welcome_message
parameter but makes a simple
typographical error, writing getString("welcom_message")
instead. The Dart compiler will not flag
this as an error because it is a syntactically valid string.
The Consequence: The application will compile and run without issue. However, at runtime, the
call to getString
will fail to find a matching key in the fetched configuration. It will silently
fall back to the in-app default value (or an empty string if no default is provided). This leads to
insidious bugs that are difficult to diagnose. A feature may appear to be “broken” or a new remote
value “not applying,” when the root cause is a single mistyped character. Teams can waste hours
debugging an issue that a type-safe system would have caught instantly.
2. The Risk of Runtime TypeErrors
The Problem: The application code expects an integer value for a parameter, calling
getInt("max_items")
. Meanwhile, a team member updating the configuration in the Firebase console
accidentally saves the value as a string (e.g., “100” instead of 100).
The Consequence: This creates a type mismatch that is invisible at compile time. When the app fetches the configuration and attempts to parse the string “100” as an integer, it will throw a TypeError at runtime. Depending on where this call is made, this could crash a screen or even the entire application. This is a critical stability risk, as it allows a non-technical team member to inadvertently introduce a production-crashing bug through a simple data entry error in a web console.
3. The High Cost of Refactoring
The Problem: As an application evolves, parameter names often need to be changed for clarity or
consistency. The product team decides that welcome_message
should be renamed to
home_screen_greeting
.
The Consequence: To implement this change, a developer must perform a global, text-based “find
and replace” for the string "welcome_message"
. This process is fraught with peril. It might miss
some occurrences if they are constructed dynamically. Worse, it could incorrectly change a similarly
named but unrelated string in a different context. There is no compiler assistance to validate that
all usages have been correctly updated. This high risk and manual effort makes the codebase rigid
and actively discourages the kind of routine refactoring that is essential for long-term code health
and maintainability.
4. Degraded Developer Experience
The Problem: When a developer needs to use a remote parameter, the IDE offers no assistance. There is no autocomplete to suggest available keys or validate their existence. The developer must break their workflow, switch context to the Firebase console or internal documentation, and manually type the string key.
The Consequence: This friction slows down the development process and dramatically increases the cognitive load on the developer, making errors more likely. It runs counter to the principles of a modern, integrated development environment, which is designed to assist and guide the developer.
The Solution: remote_config_gen
To fix the problems with string-based APIs, Flutter developers use code generation. The
remote_config_gen package applies this pattern to
Firebase Remote Config, changing a risky runtime setup into a safe, compile-time setup. It’s a
dev_dependency
, meaning it’s a tool used during development and doesn’t add any size to your final
app.
How Code Generation Works: One Source of Truth
The main idea of remote_config_gen
is to make the Firebase Remote Config console your single
source of truth and then use a build-time tool to keep your Dart code in sync with it.
The workflow is as follows:
-
Define the Contract: The developer defines all parameters, their data types (String, Number, Boolean, JSON), default values, and parameter groups directly in the Firebase Remote Config console. This is the canonical definition of the app’s remote configuration.
-
Capture the Contract: Using the Firebase CLI, the developer downloads this entire configuration into a file named
remoteconfig.template.json
. This file is a structured representation of the configuration and can be committed to version control, making changes to remote parameters as auditable and reviewable as any other code change. -
Generate the API: The
remote_config_gen
library is a simple dart script. It parses theremoteconfig.template.json
file and generates theRemoteConfigParams
Dart class. -
Enforce the Contract: The library generates a Dart class, typically named
RemoteConfigParams
, which contains static, type-safe accessors for every single parameter and parameter group defined in the JSON file.
This process changes who’s responsible for keeping things correct. Instead of relying on a developer’s memory to keep string keys the same between the console and the code, the system now relies on an automated build process. If a parameter is renamed in the console but not in the code, the build will fail. If a type is changed, the build will fail. This moves error detection from the user’s device (where it’s unpredictable) to the developer’s machine (where it’s controlled). This makes your code much more reliable and easier to maintain.
Main Benefits
Using this code generation approach gives you several important benefits:
Compile-Time Safety: Typos in parameter names are no longer silent runtime failures. Accessing a
non-existent parameter like RemoteConfigParams.welcomMessage
will result in a clear compile-time
error, preventing the bug from ever being created.
Automatic Type-Casting: The generated code automatically calls the correct
firebase_remote_config
getter (getString()
, getBool()
, getInt()
, getDouble()
). If the type
defined in the console (e.g., a String) does not match the type expected by the generated accessor
(e.g., an int), it can be flagged, preventing runtime TypeError exceptions.
Better IDE Support: The developer experience is vastly improved. By simply typing
RemoteConfigParams.
, the developer is presented with an autocomplete list of all available,
correctly-typed parameters. This enhances discoverability and reduces the need to context-switch to
the Firebase console.
Safe and Simple Refactoring: Renaming a parameter becomes a safe, compiler-guided process. The developer renames the key in the Firebase console, re-downloads the template, and re-runs the generator. The compiler will then show an error at every single location where the old parameter name was used, effectively providing a to-do list of all the necessary updates.
Table: String-Based vs. Code-Generated Access
This table provides an at-a-glance comparison, starkly illustrating the benefits of the code generation approach.
Feature | Standard firebase_remote_config | With remote_config_gen |
---|---|---|
Accessing a Value | remoteConfig.getBool("enable_new_feature") | RemoteConfigParams.enableNewFeature.getValue() |
Type Safety | ❌ None (Risk of runtime errors) | ✅ Full (Caught at compile-time) |
Key Safety (Typos) | ❌ None (Risk of silent failures) | ✅ Full (Caught at compile-time) |
IDE Support | ❌ None (Manual string entry) | ✅ Full (Autocomplete, go-to-definition) |
Refactoring | ☠️ High-risk, manual search | ✅ Safe, compiler-assisted |
Source of Truth | 💔 Split (Console + Code) | 🥇 Unified (Console via generated code) |
How to Set it Up
This section shows you exactly how to add remote_config_gen
to your Flutter project. Following
these steps will make your Remote Config implementation much safer and more reliable.
Step 0: Pre-requisites
Before you begin, ensure your development environment meets the following criteria:
- A functioning Flutter project that has already been configured with a Firebase project.
- The Firebase Command Line Interface (CLI) is installed on your machine and you are authenticated
(
firebase login
).
Step 1: Project Setup
First, add the necessary packages to your project’s pubspec.yaml
file. remote_config_gen
is a
code generator, so it belongs in dev_dependencies
, while firebase_remote_config
and
firebase_core
are standard runtime dependencies.
# pubspec.yaml
dependencies:
flutter:
sdk: flutter
firebase_core: ^2.27.0
firebase_remote_config: ^4.3.10
dev_dependencies:
flutter_test:
sdk: flutter
remote_config_gen: ^0.0.2 # Use the latest version from pub.dev
After adding these lines, run flutter pub get
in your terminal to install the packages.
Step 2: The Source of Truth - Your Remote Config Template
Navigate to your Firebase project’s console, select “Remote Config” from the Engage section, and define the parameters you want to manage. For this tutorial, we will create the following:
welcome_message
(Type: String, Default value: “Hello from Firebase!“)max_retry_count
(Type: Number, Default value: 3)- A parameter group named
ui_settings
containing one parameter:dark_mode
(Type: Boolean, Default value: false)
Once your parameters are defined and published, use the Firebase CLI to download the configuration template into your project’s root directory. This file will serve as the input for our code generator.
firebase remoteconfig:get -o remoteconfig.template.json
This command fetches your current Remote Config template from the server and saves it as
remoteconfig.template.json
in your project.
Step 3: Configuring the Generator
Next, create a new file in your project’s root directory named remote_config_gen.yaml
. This file
tells the generator where to find the input template and where to place the generated Dart code.
# remote_config_gen.yaml
input: remoteconfig.template.json
output: lib/generated/remote_config.dart
This configuration specifies that the generator should read from remoteconfig.template.json
and
write the output to a new file at lib/generated/remote_config.dart
.
Step 4: Generating the Code
With the configuration in place, you can now run the code generator. Execute the following command in your project’s terminal:
dart run remote_config_gen
Step 5: Putting It to Use
If you inspect the newly created lib/generated/remote_config.dart
file, you will see the generated
code. It will contain a RemoteConfigParams
class with static getters for each of your parameters,
including nested classes for parameter groups.
Now, let’s change the simple MyApp
widget from the first section to use this new, type-safe API.
Before (Using Magic Strings):
// old_widget.dart
import 'package:firebase_remote_config/firebase_remote_config.dart';
//...
class MyWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
final remoteConfig = FirebaseRemoteConfig.instance;
final welcomeText = remoteConfig.getString('welcome_message');
final isDarkMode = remoteConfig.getBool('dark_mode'); // Prone to error if key is in a group
return Text(welcomeText);
}
}
After (Type-Safe and Robust):
// new_widget.dart
// 1. Import the generated file
import 'package:your_app_name/generated/remote_config.dart';
import 'package:flutter/material.dart';
//...
class MyWidget extends StatelessWidget {
const MyWidget({super.key});
@override
Widget build(BuildContext context) {
// 2. Access values via the generated class. No magic strings!
final welcomeText = RemoteConfigParams.welcomeMessage.getValue();
final maxRetries = RemoteConfigParams.maxRetryCount.getValue();
// 3. Access grouped parameters through the nested class
final isDarkMode = RemoteConfigParams.uiSettings.darkMode.getValue();
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(welcomeText),
Text('Max retries: $maxRetries'),
Text('Dark mode: $isDarkMode'),
],
);
}
}
This direct comparison highlights the immediate benefits. The code is cleaner, self-documenting, and, most importantly, safe. Any typo in a parameter name or mismatch in type would now be caught by the compiler before the app is even run, completely eliminating the class of runtime errors discussed previously.
Building Feature Flags with remote_config_gen
While Remote Config is useful for many types of dynamic configuration, its most powerful use is for
feature flagging. This section shows how to combine the type-safe code from remote_config_gen
with
Remote Config’s real-time features to build a solid feature flagging system.
What are Feature Flags?
Feature flags (or feature toggles) are an important part of modern software development. They allow teams to add new, incomplete features to the main codebase but keep them turned off. This gives you several benefits:
Lower Risk: New features can be rolled out slowly to a small percentage of users. If crashes or other issues happen, the feature can be turned off instantly without needing an emergency app release.
Deploy Anytime: Code can be deployed to production at any time, but the feature is only “released” to users when the flag is enabled. This separates the technical work from the business decision.
Test Safely: Features can be enabled only for internal testers or specific user groups, allowing for real-world testing before a public launch.
A/B Testing: Flags are the foundation for A/B testing different versions of a feature to see which performs better.
Firebase Remote Config is an excellent backend for managing these flags due to its powerful targeting and real-time update capabilities.
Implementing a Type-Safe Feature Flag
Let’s compare the implementation of a feature flag with and without our code generation workflow.
Scenario: We are developing a new, enhanced search results page and want to control its
visibility with a flag named enable_enhanced_search
.
Before (The Risky Way):
// Accessing the flag with a magic string
final useEnhancedSearch = FirebaseRemoteConfig.instance.getBool('enable_enhanced_search');
// In the widget tree
if (useEnhancedSearch) {
return EnhancedSearchPage();
} else {
return ClassicSearchPage();
}
As established, this approach is fragile. A typo in the key or a type mismatch in the console (e.g., saving the value as the string “true”) will cause the flag to silently fail, likely defaulting to false and preventing the new feature from ever being seen by users.
After (The Robust Way):
// Import the generated code
import 'package:your_app_name/generated/remote_config.dart';
// Accessing the flag with the type-safe getter
final useEnhancedSearch = RemoteConfigParams.enableEnhancedSearch.getValue();
// In the widget tree
if (useEnhancedSearch) {
return EnhancedSearchPage();
} else {
return ClassicSearchPage();
}
The improvement is profound. The code is not only cleaner but fundamentally safer. The existence of
RemoteConfigParams.enableEnhancedSearch
is a compile-time guarantee that this flag is defined in
our single source of truth, the remoteconfig.template.json
file. The call to .getValue()
is
guaranteed to access a boolean value, protecting against type errors.
Real-Time Toggling for Instant Control
The true power of this system is realized when we combine our type-safe accessors with Remote Config’s real-time update listener. This allows for the creation of features that can be enabled or disabled for all active users instantly and safely.
The following example demonstrates how this could be implemented using a simple ValueNotifier for state management. In a real application, a more robust state management solution like Riverpod or BLoC would be used.
// A simple service to manage the feature flag state
class FeatureFlagService {
// Initialize the notifier with the current, type-safe value
final enhancedSearchEnabled = ValueNotifier<bool>(
RemoteConfigParams.enableEnhancedSearch.getValue(),
);
FeatureFlagService() {
// Listen for real-time updates from Firebase
FirebaseRemoteConfig.instance.onConfigUpdated.listen((event) async {
// When an update is detected, activate the new config
await FirebaseRemoteConfig.instance.activate();
// Update the notifier with the new, type-safe value
enhancedSearchEnabled.value = RemoteConfigParams.enableEnhancedSearch.getValue();
});
}
}
// In the UI:
ValueListenableBuilder<bool>(
valueListenable: myFeatureFlagService.enhancedSearchEnabled,
builder: (context, isEnabled, child) {
// The UI automatically rebuilds when the flag changes
return isEnabled ? EnhancedSearchPage() : ClassicSearchPage();
},
)
This combination creates a system that is both immensely powerful and remarkably safe. The real-time
listener provides the ability to flip a switch in the Firebase console and have it affect all active
users within seconds. This is invaluable for deploying a “kill switch” to disable a buggy feature.
The danger of such power is that a mistake in the switching mechanism itself could be catastrophic.
However, because our implementation uses the type-safe, generated
RemoteConfigParams.enableEnhancedSearch.getValue()
accessor, we have a compile-time guarantee that
our switching mechanism is robust. We are protected from typos and type errors, allowing us to use
this powerful real-time capability with a high degree of confidence. This synergy between real-time
updates and type safety is a massive victory for operational stability and developer peace of mind.
Conclusion: Write Safer, Ship Faster
Firebase Remote Config is an indispensable tool for the modern Flutter developer, offering the agility to dynamically update applications, test new ideas, and manage features without the friction of constant app store releases. However, we have seen that the standard, string-based API, while functional, introduces a significant class of preventable runtime errors that can lead to silent failures, production crashes, and a brittle codebase that is difficult to maintain.
By adopting a code-generation workflow with a library like remote_config_gen
, we elevate our
development practice. We shift the responsibility of ensuring correctness from fallible human memory
to an automated, reliable build tool. This approach provides:
- Compile-time safety, eliminating bugs caused by typos and type mismatches.
- A superior developer experience with IDE autocompletion and effortless navigation.
- Safe and simple refactoring, allowing the codebase to evolve without fear.
This is not just about convenience; it’s a smart decision to build more professional, reliable, and maintainable applications. By creating a strong, type-safe connection between your app and its remote configuration, you can use the full power of Firebase Remote Config with confidence. This allows development teams to move faster, reduce release risk, and ship better products to their users.

Bootstrap your project Ship everywhere with Flutter. Fast!
Our boilerplate project with Flutter superpowers can help you ship your idea in days instead of months. Release your project for Android, iOS, Desktop and Web with our Flutter starter-kit builder.