skip to content

A flag changed in the console hours ago still hasn't reached users of a Flutter app using firebase_remote_config; how do minimumFetchInterval and throttling explain it?

level: seniorimportance: should knowfreq 38%

answer

  1. default window is 12 hours
  2. fetches inside the window use cache
  3. too-low interval hits the quota
  4. throttled code, throttle status
  5. real-time updates bypass the interval

basics

~10 s

minimumFetchInterval defaults to 12 hours, so fetches inside that window reuse the cached config. Lowering it in production risks backend throttling (code throttled, lastFetchStatus throttle); onConfigUpdated delivers published changes regardless of the interval.

solid answer

~50 s

`RemoteConfigSettings` has two durations, both required when you construct it. `minimumFetchInterval` is the maximum age of the cached config before it counts as stale, and it defaults to **12 hours**. Inside that window, extra `fetch()` or `fetchAndActivate()` calls do not download a new template, so a console change can take up to 12 hours to reach an app that only fetches at startup. The fix is not a near-zero interval in production. The guide reserves short intervals for development, because many devices fetching too often hit the backend quota. A throttled fetch throws a `FirebaseException` with code `throttled`, and `lastFetchStatus` becomes `RemoteConfigFetchStatus.throttle`. For fast propagation, listen to `onConfigUpdated`: real-time updates are fetched as soon as they are published, bypassing the interval. `fetchTimeout` (default 60 s; zero is treated as 60 s) only bounds how long one fetch waits.

code

dart · 21 lines
dart
import 'package:firebase_remote_config/firebase_remote_config.dart';
import 'package:flutter/foundation.dart';

Future<void> configureRemoteConfig() async {
  final rc = FirebaseRemoteConfig.instance;
  await rc.setConfigSettings(RemoteConfigSettings(
    fetchTimeout: const Duration(seconds: 10),
    minimumFetchInterval:
        kDebugMode ? const Duration(minutes: 5) : const Duration(hours: 1),
  ));
  await rc.setDefaults(const {'new_checkout_enabled': false});

  try {
    await rc.fetchAndActivate();
  } on FirebaseException catch (e) {
    if (e.code == 'throttled') {
      // Expected under load: keep the active values and try later.
    }
  }
  print('status: ${rc.lastFetchStatus}, last fetch: ${rc.lastFetchTime}');
}

go deeper

for a junior

Remember the two settings: fetchTimeout limits one request, and minimumFetchInterval, 12 hours by default, limits how often the backend is actually asked.

for a middle

Explain why fetches inside the interval return cached values, what throttled and RemoteConfigFetchStatus.throttle mean, and why short intervals are for development only.

for a senior

Diagnose a stuck flag with lastFetchStatus, lastFetchTime and getValue().source, and fix propagation with onConfigUpdated rather than a near-zero interval.

for a principal

Balance freshness against quota and startup latency across the whole install base, and decide which flags justify real-time propagation versus next-launch activation.

## Two settings, two different jobs **firebase_remote_config**, the FlutterFire plugin for Firebase Remote Config, takes its tuning through `setConfigSettings(RemoteConfigSettings(...))`. Both fields are required when you construct the settings object: | Setting | Meaning | Default | |---|---|---| | `fetchTimeout` | how long one fetch may wait for the backend | 60 s (a zero duration is replaced by 60 s) | | `minimumFetchInterval` | maximum age of the cached config before it is stale | 12 hours | Negative durations fail an assertion. They are easy to confuse: the timeout limits a single request, and the interval limits how often a request actually reaches the backend. ## Why a change can take hours to arrive With the default 12-hour interval, the guide states that configs are not fetched from the backend more than once in a 12-hour window, however many fetch calls the app makes. So: 1. A user opens the app at 09:00, and `fetchAndActivate()` downloads the template. 2. You flip `new_checkout_enabled` in the console at 10:00. 3. The user reopens the app at 11:00. The fetch is inside the window, so the cached template is used and the old value stays active. 4. Only after 21:00 does a fetch download the new value, and only then if the app fetches again. Nothing is broken: that is the configured behaviour. ## Throttling: what happens if you lower the interval - During development, the guide suggests a short interval, such as `Duration(minutes: 5)`, so a small team can iterate. - It warns that this is for **development only**. Shipping a very low interval to many devices will probably exceed the service's quota. - When that happens, `fetch()` or `fetchAndActivate()` throws a `FirebaseException` with code `throttled`, and `lastFetchStatus` reads `RemoteConfigFetchStatus.throttle`. The app keeps its active values. - `lastFetchTime` shows the last successful fetch. Before any successful fetch it is the Unix epoch. Treat `throttled` as expected, not fatal: log it, keep the active values and try later. Since firebase_remote_config 6.6.0, fetch failures are classified into codes such as `network-error`, `throttled` and `forbidden` instead of all reporting `internal`. ## The right fix for fast propagation For changes that must land quickly, such as switching off a broken checkout, **real-time Remote Config** is the supported route: - `onConfigUpdated` opens a connection to the backend. When a new template is published, the SDK fetches the changes **regardless of the minimum fetch interval**. - Your listener then calls `activate()` when it is safe to apply the change. - Keep a moderate interval, such as one hour or the default, for the startup fetch path. ## A production-ready configuration - Use a short interval only in debug builds, for example chosen with `kDebugMode`, and keep the default or an hour in release. - Keep `fetchTimeout` modest so a slow network does not hold up the first frame. Do not block `runApp` on a fetch that may take up to the full timeout. - Handle `FirebaseException` from the fetch and fall back to the active or default values. - Add an `onConfigUpdated` listener for flags that need to change within minutes. ## Diagnosing a stuck flag | Observation | Likely cause | |---|---| | `lastFetchStatus` is `success`, value old | inside the fetch interval; nothing new downloaded | | `lastFetchStatus` is `throttle` | interval too low in production | | `getValue(k).source` is `valueDefault` | app never activated a remote value for that key | | fetch throws `forbidden` | Remote Config API disabled for the project | Rollout policy, such as percentages, conditions and kill-switch design, is a release-engineering topic. The mechanics above are what the Flutter client controls.

  • What is the difference between fetchTimeout and minimumFetchInterval?
    `fetchTimeout` bounds a single fetch request; it defaults to 60 seconds, and zero is treated as 60. `minimumFetchInterval` is the maximum age of the cached config; it defaults to 12 hours, and fetches inside the window do not download a new template. Lowering the timeout never makes changes arrive sooner.
  • Does a listener on onConfigUpdated still wait for minimumFetchInterval?
    No. Real-time Remote Config fetches a newly published template as soon as the backend signals it, bypassing the minimum fetch interval. The listener still has to call `activate()` before getters see the new values.

saying these in an interview costs you the question

  • Set minimumFetchInterval to zero in production so flags update instantly.
  • Lowering fetchTimeout makes new config values arrive sooner.
  • A throttled fetch wipes the active config back to defaults.
  • Calling fetchAndActivate on every resume guarantees fresh values.
  • minimumFetchInterval defaults to one minute.