In Flutter's firebase_remote_config, how does onConfigUpdated deliver live changes, and what must your listener do before new values take effect?
answer
- a Stream of RemoteConfigUpdate
- updatedKeys: a Set<String>
- SDK fetches, you activate
- web needs a prior fetchAndActivate
- desktop: registered, no events
basics
~10 sonConfigUpdated is a Stream<RemoteConfigUpdate>: listening opens a real-time connection, the SDK fetches each published template, and the event lists updatedKeys. The listener must call activate() before getters return the new values.
solid answer
~40 s`FirebaseRemoteConfig.instance.onConfigUpdated` returns a `Stream<RemoteConfigUpdate>`. Listening opens a real-time connection to the backend, or reuses it for further listeners. When a new template is published, the SDK fetches it, bypassing `minimumFetchInterval`, and emits an event whose `updatedKeys` is a `Set<String>` of added, removed or changed parameters. The fetched values are **not active yet**: the listener calls `activate()` and then reads the getters. Check `updatedKeys` before acting, and decide whether to apply the change immediately or at a safe point. For example, do not swap the checkout flow halfway through a purchase. On web, events arrive only after an initial `fetchAndActivate()`. On Windows and other desktop platforms the listener registers but receives no events. Cancel the `StreamSubscription` when its owner is disposed.
code
dart · 31 linesimport 'dart:async';
import 'package:firebase_remote_config/firebase_remote_config.dart';
import 'package:flutter/foundation.dart';
class CheckoutFlags extends ChangeNotifier {
CheckoutFlags(this._rc);
final FirebaseRemoteConfig _rc;
StreamSubscription<RemoteConfigUpdate>? _sub;
bool get newCheckout => _rc.getBool('new_checkout_enabled');
Future<void> start() async {
await _rc.fetchAndActivate(); // also required first on web
_sub = _rc.onConfigUpdated.listen(
(update) async {
if (!update.updatedKeys.contains('new_checkout_enabled')) return;
await _rc.activate(); // fetched is not active until this runs
notifyListeners();
},
onError: (Object e) => debugPrint('real-time config error: $e'),
);
}
@override
void dispose() {
_sub?.cancel();
super.dispose();
}
}go deeper
Know that onConfigUpdated is a stream of RemoteConfigUpdate events, and that you call activate() inside the listener before reading values.
Explain that the SDK fetches on publish, bypassing the interval, what updatedKeys contains, and the platform gaps: web after an initial fetchAndActivate, desktop with no events.
Show where the subscription lives and is cancelled, how you avoid swapping a flow mid-task, and how real-time and startup fetches complement each other.
Decide which parameters deserve live propagation and which should only change between sessions, given user experience, consistency across screens and incident response.
## What real-time Remote Config adds Classic Remote Config is pull-based: the app calls `fetch()` and the backend answers, at most once per `minimumFetchInterval`. **Real-time Remote Config** adds a push signal. In the **firebase_remote_config** plugin that signal is the `onConfigUpdated` getter on `FirebaseRemoteConfig`: - It returns a `Stream<RemoteConfigUpdate>`. - **Listening** opens a connection to the Remote Config backend if one is not already open. Further listeners reuse the same connection. - When you publish a new template, the backend signals connected clients, and the SDK **fetches the changes automatically**, regardless of the minimum fetch interval. - Each event carries `updatedKeys`, a `Set<String>` of parameter keys that were added, deleted, or changed in value, source or metadata relative to the active config. ## Fetched is still not active The stream delivers the news that new values were fetched. It does **not** apply them. Getters keep returning the active config until you call `activate()`. This keeps the same fetch/activate split as the pull path: 1. The event arrives with `updatedKeys`. 2. Your code decides whether any of those keys matter to the current screen. 3. It calls `await remoteConfig.activate()` at a safe moment. 4. It reads the getters and updates state, for example by notifying a controller that the UI listens to. Forgetting step 3 is the classic bug: the listener fires, logs the keys, and the flag never changes. ## When to apply a live change Not every change should apply mid-session: | Change | Apply now? | Why | |---|---|---| | Banner copy on the home screen | yes | harmless to swap | | `new_checkout_enabled` while a user is paying | defer | switching flows mid-payment is jarring | | An emergency switch-off of a broken feature | yes | stopping harm outweighs a visual jump | A simple approach is to activate immediately but have the checkout screen read its flag once, when it opens. The new value then applies to the next checkout, not the one in progress. ## Platform support - **Android and iOS**: supported. In 6.7.0, iOS events are emitted on the platform thread, a fix in that release. - **Web**: supported since firebase_remote_config 6.1.0, but you must call `fetchAndActivate()` before listening. Events arrive only after that first call. The upstream getting-started guide still says real-time is unavailable on web; the plugin source and changelog supersede it. - **Windows and other desktop platforms**: the listener registers, but the underlying C++ SDK delivers no events. Use `fetchAndActivate()` to check for updates manually. ## Lifecycle and wiring - Own the subscription in a long-lived object, such as an app-level service or a `State` near the root, and cancel it in `dispose`. - Handle stream errors. A failed real-time connection should not crash the app, and the pull path still works. - Keep the startup `fetchAndActivate()` as well. Real-time covers changes published while the app runs, and the startup fetch covers changes made while it was closed. How a widget rebuilds from a stream, for example with `StreamBuilder`, belongs to the state-management topic. Here the point is the plugin contract: the SDK fetches, the event names the keys, and your code activates.
- The onConfigUpdated listener fires and logs the right key, but getBool still returns the old value. Why?The SDK fetched the new template, but nothing activated it. Getters read only the active config, so the listener must `await activate()` before reading. The upstream sample does exactly this as the first line of the listener.
- Should you drop the startup fetchAndActivate once you have a real-time listener?No. Real-time events cover templates published while the connection is open. Changes published while the app was closed are picked up by the startup fetch, subject to the interval. On web, the initial `fetchAndActivate()` is also a precondition for receiving events at all.
saying these in an interview costs you the question
- onConfigUpdated events mean the new values are already active.
- Real-time updates still wait for minimumFetchInterval.
- Each listener opens its own connection to the backend.
- onConfigUpdated delivers events on Windows the same as on Android.
- With real-time listening you no longer need a startup fetch.