Firebase Remote Config in Flutter
By Ann Tech · 18 March 2025
Firebase Remote Config lets you change app behaviour without shipping a new app version. Change feature flags, tune UI copy, adjust thresholds — instantly, without an app store review cycle. Here is how to integrate it properly and avoid the common mistakes.
When to use Remote Config
- Feature flags: roll out a new feature to 10% of users, then expand
- A/B testing: show different UI to different user segments
- Emergency kill switches: disable a broken feature without an update
- Dynamic text: change button labels, promotional copy, onboarding messages
- Configuration: API timeouts, max retry counts, pagination sizes
Setup
dependencies:
firebase_remote_config: ^5.0.0
firebase_core: ^3.0.0
Initialization with defaults
Always set in-app defaults. Remote Config values won't be available until the first successful fetch — without defaults, you'd show empty strings or zero values on first launch.
Future<void> initRemoteConfig() async {
final remoteConfig = FirebaseRemoteConfig.instance;
await remoteConfig.setConfigSettings(RemoteConfigSettings(
fetchTimeout: const Duration(minutes: 1),
minimumFetchInterval: const Duration(hours: 1), // Reduce in dev
));
await remoteConfig.setDefaults({
'new_checkout_enabled': false,
'max_items_per_order': 50,
'promo_banner_text': '',
'api_timeout_seconds': 30,
'onboarding_variant': 'control',
});
await remoteConfig.fetchAndActivate();
}
Call this early in app startup, before runApp if possible, or in a splash screen.
Reading values
final remoteConfig = FirebaseRemoteConfig.instance;
// Boolean flags
final isNewCheckoutEnabled = remoteConfig.getBool('new_checkout_enabled');
// Integers
final maxItems = remoteConfig.getInt('max_items_per_order');
// Strings
final bannerText = remoteConfig.getString('promo_banner_text');
// Doubles
final timeoutSeconds = remoteConfig.getDouble('api_timeout_seconds');
// JSON (store complex config as a JSON string)
final configJson = remoteConfig.getString('feature_config');
final config = jsonDecode(configJson) as Map<String, dynamic>;
Reactive updates with streams
Listen for real-time config changes (useful for pushing emergency changes while the app is open):
FirebaseRemoteConfig.instance.onConfigUpdated.listen((event) async {
await FirebaseRemoteConfig.instance.activate();
// Update UI or state with new values
ref.invalidate(remoteConfigProvider);
});
Wrapping in a provider
Don't scatter FirebaseRemoteConfig.instance calls throughout the app. Wrap in a typed class:
class AppRemoteConfig {
AppRemoteConfig(this._config);
final FirebaseRemoteConfig _config;
bool get isNewCheckoutEnabled => _config.getBool('new_checkout_enabled');
int get maxItemsPerOrder => _config.getInt('max_items_per_order');
String get promoBannerText => _config.getString('promo_banner_text');
Duration get apiTimeout => Duration(
seconds: _config.getInt('api_timeout_seconds'),
);
String get onboardingVariant => _config.getString('onboarding_variant');
}
// Riverpod provider
final remoteConfigProvider = Provider<AppRemoteConfig>((ref) {
return AppRemoteConfig(FirebaseRemoteConfig.instance);
});
Usage in a widget:
final config = ref.watch(remoteConfigProvider);
if (config.isNewCheckoutEnabled) {
return const NewCheckoutFlow();
}
return const LegacyCheckoutFlow();
Conditions and targeting
In the Firebase console, set conditions to target specific users:
- Platform: iOS vs Android
- App version:
>=2.5.0 - User property:
premium_user == true - Random percentile:
<= 10(for 10% rollout) - Firebase audience: any Analytics audience
Example rollout strategy:
- Set
new_checkout_enabled: false(default) - Create condition:
random_percentile <= 5→new_checkout_enabled: true - Monitor Crashlytics and Analytics for 48 hours
- Expand to
<= 25, then<= 100if healthy - Remove the condition and set the default to
true
Development setup
For development, set minimumFetchInterval: Duration.zero so you see config changes immediately:
await remoteConfig.setConfigSettings(RemoteConfigSettings(
fetchTimeout: const Duration(minutes: 1),
minimumFetchInterval: kDebugMode
? Duration.zero
: const Duration(hours: 1),
));
Caching and offline behaviour
Remote Config caches the last successful fetch. If the app is offline, it uses the cached values. If there's no cache (first launch, offline), it uses the in-app defaults. This means:
- First launch offline: in-app defaults
- First launch online, then go offline: last fetched values
- Config changes while offline: won't take effect until next online fetch
Common pitfalls
No in-app defaults. If a key doesn't have a default and hasn't been fetched yet, getBool() returns false, getInt() returns 0, getString() returns ''. That's often wrong. Always set defaults.
Fetching too frequently. Remote Config has quota limits (5 fetches per hour per app instance). The minimumFetchInterval prevents quota exhaustion. In production, 1–12 hours is appropriate.
Using Remote Config for secrets. Remote Config values are not encrypted and can be extracted from the app. Never store API keys, signing secrets, or PII in Remote Config.
Not activating after fetch. fetch() downloads new config but doesn't activate it. Call fetchAndActivate() or activate() separately. Without activation, old values are returned.
Sign in to like, dislike, or report.