Dynamic Config & Feature Toggles
Production mobile applications rely on Dynamic Remote Configuration and Feature Toggles (using tools like Firebase Remote Config, LaunchDarkly, or Flagsmith) to manage feature rollouts, enable instant emergency kill-switches, perform A/B testing, and decouple code deployments from feature releases.
1. Feature Toggle Use Cases
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β FEATURE TOGGLE CATEGORIES β
ββββββββββββββββββββ¬βββββββββββββββββββ¬ββββββββββββββββββ¬βββββββββββββββββ€
β 1. Release Toggleβ 2. Kill Switch β 3. Experiment β 4. Permission β
ββββββββββββββββββββΌβββββββββββββββββββΌββββββββββββββββββΌβββββββββββββββββ€
β - Dark launch β - Disable broken β - A/B testing β - Premium vs β
β new features β payment gatewayβ checkout flow β free user β
β - Phased % rolloutβ instantly β - Measure conversion feature accessβ
ββββββββββββββββββββ΄βββββββββββββββββββ΄ββββββββββββββββββ΄βββββββββββββββββ2. Remote Config Lifecycle (FirebaseRemoteConfig)
Firebase Remote Config operates across a 3-stage lifecycle:
setDefaults(): In-memory local default values loaded instantly on app launch.fetch(): Fetches remote configurations from cloud servers and stores them in a local disk cache.activate(): Applies fetched values to the active runtime configuration used by the application.
import 'package:firebase_remote_config/firebase_remote_config.dart';
class RemoteConfigService {
final FirebaseRemoteConfig _remoteConfig = FirebaseRemoteConfig.instance;
Future<void> initialize() async {
// 1. Set local fallback defaults
await _remoteConfig.setDefaults(const {
'enable_new_checkout': false,
'max_upload_size_mb': 10,
'banner_message': 'Welcome!',
});
// 2. Configure fetch settings (e.g. 1 hour cache in production)
await _remoteConfig.setConfigSettings(RemoteConfigSettings(
fetchTimeout: const Duration(seconds: 10),
minimumFetchInterval: const Duration(hours: 1),
));
// 3. Fetch & activate remote values asynchronously
try {
await _remoteConfig.fetchAndActivate();
} catch (e) {
print('Remote Config fetch failed; using local fallback defaults.');
}
}
bool get isNewCheckoutEnabled => _remoteConfig.getBool('enable_new_checkout');
}3. Real-Time Remote Config Listener
In Firebase Remote Config (Flutter 3.10+), real-time updates can be received instantly using addOnConfigUpdateListener:
void listenToRealtimeUpdates() {
_remoteConfig.addOnConfigUpdateListener(
onConfigUpdate: (configUpdate, error) async {
// Activate new keys immediately when changed on backend console
await _remoteConfig.activate();
print('Updated keys: ${configUpdate.updatedKeys}');
},
onError: (error) => print('Config update error: $error'),
);
}4. Type-Safe Remote Config Wrapper Pattern
Avoid hardcoding string keys across UI widgets. Encapsulate Remote Config keys inside a strongly-typed service interface:
abstract class FeatureFlags {
bool get isRedesignEnabled;
int get maxCartItems;
}
class FirebaseFeatureFlags implements FeatureFlags {
final FirebaseRemoteConfig _config;
FirebaseFeatureFlags(this._config);
@override
bool get isRedesignEnabled => _config.getBool('is_redesign_enabled');
@override
int get maxCartItems => _config.getInt('max_cart_items');
}5. Trade-offs & Production Considerations
- UI Jarring & Mid-Session Changes: Activating new configurations mid-session (e.g. changing
isRedesignEnabledwhile a user is actively interacting with a screen) causes jarring layout jumps. Apply new configs upon app restart or navigation boundaries. - Throttling Risks: Setting
minimumFetchIntervalto 0 in production triggers Firebase HTTP 429 throttling errors. KeepminimumFetchIntervalto at least 1 hour in production (and 0 only during development).