Flutter Mobile Development · Lezione

Remote Config, feature flag e rollout graduali

Attivi e disattivi le funzionalità da remoto e distribuisca gradualmente le modifiche senza inviare nuovamente l’app.

Lezione 4 di 413 passaggi

Remote Config, feature flag e rollout graduali è una lezione Flutter Mobile Development gratuita su CoddyKit. Questa è la lezione 4 di 4. Puoi leggere la lezione completa qui gratuitamente — poi esercitati direttamente nel browser con un editor di codice integrato e un tutor IA disponibile 24/7. Fa parte del percorso di apprendimento Flutter Mobile Development, e i tuoi progressi si sincronizzano tra il web e l'app CoddyKit. Il corso Flutter Mobile Development include 4 lezioni in totale.

Parti di questa lezione non sono ancora state tradotte e vengono mostrate in inglese.

Why Remote Config

App store review cycles are slow. If a feature ships broken, you may wait days for a hotfix to reach users. Remote configuration decouples shipping the code from turning it on.

  • Feature flags gate code paths behind a boolean you control from a server.
  • Remote config delivers tunable values (strings, numbers, JSON) without a new build.
  • Staged rollouts expose a change to a growing percentage of users.

In Flutter the common tool is firebase_remote_config, but the same patterns apply to LaunchDarkly, ConfigCat, or your own backend.

Modeling a Flag Locally

Before wiring any SDK, model your flags as plain Dart so the rest of the app never touches raw strings. A typed config object keeps defaults explicit and makes the code testable.

This is pure Dart with sensible defaults baked in — exactly what you ship if the network is unavailable.

class AppConfig {
  final bool newCheckoutEnabled;
  final int maxUploadMb;
  final String welcomeMessage;

  const AppConfig({
    this.newCheckoutEnabled = false,
    this.maxUploadMb = 10,
    this.welcomeMessage = 'Welcome!',
  });
}

void main() {
  const defaults = AppConfig();
  print('checkout: ${defaults.newCheckoutEnabled}');
  print('maxUploadMb: ${defaults.maxUploadMb}');
  print('welcome: ${defaults.welcomeMessage}');
}

Firebase Remote Config Setup

With Firebase initialized, you grab the FirebaseRemoteConfig singleton, set in-app defaults, and configure fetch behavior via RemoteConfigSettings.

  • fetchTimeout caps how long a fetch waits.
  • minimumFetchInterval throttles fetches to respect quotas. Use a small value in debug, hours in production.

Defaults guarantee the app behaves sanely before the first successful fetch.

import 'package:firebase_remote_config/firebase_remote_config.dart';

Future<FirebaseRemoteConfig> initRemoteConfig() async {
  final rc = FirebaseRemoteConfig.instance;

  await rc.setConfigSettings(RemoteConfigSettings(
    fetchTimeout: const Duration(seconds: 10),
    minimumFetchInterval: const Duration(hours: 6),
  ));

  await rc.setDefaults(const {
    'new_checkout_enabled': false,
    'max_upload_mb': 10,
    'welcome_message': 'Welcome!',
  });

  return rc;
}

Fetch and Activate

Remote Config separates fetch (download values into a local cache) from activate (make the fetched values live). The convenience method fetchAndActivate() does both and returns whether new values were activated.

Always wrap the call: a network failure should fall back to the last activated values or your in-app defaults — never crash the launch path.

Future<void> refreshConfig(FirebaseRemoteConfig rc) async {
  try {
    final updated = await rc.fetchAndActivate();
    if (updated) {
      // New values are now live; rebuild dependent UI.
      print('Remote config activated with new values');
    }
  } catch (e) {
    // Offline or quota hit: keep last-known-good values.
    print('Remote config fetch failed, using cached/defaults: $e');
  }
}

Reading Typed Values

Remote Config stores everything as strings under the hood but exposes typed getters: getBool, getInt, getDouble, getString. Map them back into your AppConfig so the rest of the app stays typed.

Centralize this mapping in one place. If a key name changes, you fix it once.

import 'package:firebase_remote_config/firebase_remote_config.dart';

AppConfig readConfig(FirebaseRemoteConfig rc) {
  return AppConfig(
    newCheckoutEnabled: rc.getBool('new_checkout_enabled'),
    maxUploadMb: rc.getInt('max_upload_mb'),
    welcomeMessage: rc.getString('welcome_message'),
  );
}

Gating UI Behind a Flag

A feature flag should guard a single decision point. Read it once near the top of the widget tree (or from a provider) and branch.

Keep the gate shallow: branch on the flag, not deep inside business logic, so removing the flag later is a clean deletion.

Widget buildCheckout(BuildContext context, AppConfig config) {
  if (config.newCheckoutEnabled) {
    return const NewCheckoutScreen();
  }
  return const LegacyCheckoutScreen();
}

Parsing JSON-Valued Config

For richer payloads, store a JSON string in a single key and decode it. This lets one flag carry a whole structured experiment — themes, thresholds, ordered lists — without adding dozens of keys.

Below is standalone Dart showing the decode-and-fallback pattern you would apply to rc.getString('promo_banner').

import 'dart:convert';

class PromoBanner {
  final String text;
  final int priority;
  PromoBanner(this.text, this.priority);
}

PromoBanner parsePromo(String raw) {
  try {
    final map = jsonDecode(raw) as Map<String, dynamic>;
    return PromoBanner(
      map['text'] as String? ?? '',
      map['priority'] as int? ?? 0,
    );
  } catch (_) {
    return PromoBanner('', 0); // malformed JSON -> safe default
  }
}

void main() {
  final ok = parsePromo('{"text":"Summer Sale","priority":5}');
  print('${ok.text} (${ok.priority})');
  final bad = parsePromo('not-json');
  print('fallback text empty: ${bad.text.isEmpty}');
}

Staged Rollout by Percentage

A staged rollout exposes a feature to, say, 1% of users, then 10%, then 50%, then 100% — watching crash and metric dashboards at each step. Firebase Remote Config does this server-side with percentage conditions based on a stable, randomized user bucket.

The key requirement: bucketing must be stable. A user assigned to the rollout must stay in it across sessions, otherwise the UI flickers and metrics are meaningless.

Stable Client-Side Bucketing

If you roll your own backend instead of Firebase, you implement bucketing on the client. Hash a stable user id into a value in [0, 100) and compare against the rollout threshold. The same id always lands in the same bucket.

This deterministic hashing is the heart of staged rollouts and A/B splits.

int bucketOf(String userId) {
  // Simple stable hash -> 0..99
  var hash = 0;
  for (final code in userId.codeUnits) {
    hash = (hash * 31 + code) & 0x7fffffff;
  }
  return hash % 100;
}

bool isInRollout(String userId, int percent) {
  return bucketOf(userId) < percent;
}

void main() {
  const id = 'user-42';
  print('bucket: ${bucketOf(id)}');
  print('in 10% rollout: ${isInRollout(id, 10)}');
  print('stable on retry: ${bucketOf(id) == bucketOf(id)}');
}

Kill Switches and Safe Defaults

The most valuable flag is a kill switch: a remote boolean that instantly disables a risky feature without a release. Two rules make kill switches reliable:

  • The safe state must be the default. A kill switch should default to false (feature off) so a failed fetch never accidentally enables the risky path.
  • Read the flag at the decision point every time, not once at startup, so flipping it takes effect on the next config refresh.

For Remote Config, schedule a refresh on app resume so a kill switch propagates within one foreground cycle.

// Default OFF => a fetch failure leaves the risky feature disabled.
bool experimentalSyncEnabled(AppConfig config) {
  return config.newCheckoutEnabled; // example risky path
}

void onAppResumed(FirebaseRemoteConfig rc) {
  // Re-fetch so a flipped kill switch reaches the user quickly.
  refreshConfig(rc);
}

Listening for Real-Time Updates

Newer Firebase Remote Config supports real-time updates: a stream that fires when the server publishes new values, so you do not have to wait for the next scheduled fetch. You still call activate() to make them live.

Use this for kill switches where minutes matter. Combine the listener with setState/provider invalidation to rebuild affected widgets.

import 'package:firebase_remote_config/firebase_remote_config.dart';

void subscribeToUpdates(FirebaseRemoteConfig rc, void Function() onChanged) {
  rc.onConfigUpdated.listen((event) async {
    await rc.activate();
    onChanged(); // e.g. trigger a UI rebuild
  });
}

Quick Check

Test your understanding of safe rollout design.

Recap

You learned how to ship features dark and control them remotely:

  • Model flags as a typed AppConfig with explicit defaults.
  • Initialize Remote Config with setDefaults and tuned RemoteConfigSettings, then fetchAndActivate() defensively.
  • Read typed values and gate UI at a single shallow decision point; carry rich payloads as JSON strings.
  • Run staged rollouts with stable percentage bucketing so users stay consistently in or out.
  • Make every kill switch default to its safe state, and use real-time updates plus on-resume refresh so flips propagate fast.

Together these let you decouple deploy from release and respond to incidents without an app-store round trip.

Gratis per iniziare

Impara Dart con un tutor IA — gratis

Scrivi ed esegui vero codice nel tuo browser, ricevi aiuto istantaneo da un tutor IA disponibile 24/7, e riprendi da dove hai lasciato sul web o nell'app.

Corsi
22
Lezioni
88

Domande Frequenti

La lezione «Remote Config, feature flag e rollout graduali» è gratuita?

Sì — il testo completo di «Remote Config, feature flag e rollout graduali» è gratuito qui sul web. Per esercitarvi in modo interattivo (un editor di codice integrato e un tutor IA 24/7) e sbloccare il resto del corso Flutter Mobile Development, passa a CoddyKit PRO. Il corso Flutter Mobile Development include 4 lezioni in totale.

Cosa imparerò in «Remote Config, feature flag e rollout graduali»?

Attivi e disattivi le funzionalità da remoto e distribuisca gradualmente le modifiche senza inviare nuovamente l’app. Eserciti Flutter Mobile Development con codice pratico che esegui direttamente nel browser, e un tutor IA 24/7 risponde alle tue domande mentre lavori sulla lezione.

Ho bisogno di esperienza per iniziare Flutter Mobile Development?

Non è richiesta alcuna esperienza precedente. Flutter Mobile Development su CoddyKit è strutturato per principianti e studenti avanzati, quindi puoi iniziare da qui o dall'inizio e procedere al tuo ritmo. Questa è la lezione 4 di 4.

Quanto tempo richiede la lezione «Remote Config, feature flag e rollout graduali»?

La maggior parte delle lezioni CoddyKit richiede circa 5–10 minuti. Ogni lezione è breve e interattiva, quindi fai progressi costanti e riprendi esattamente da dove hai lasciato su web e app.

Posso scrivere ed eseguire codice in questa lezione Flutter Mobile Development?

Sì. Ogni lezione Flutter Mobile Development include un editor di codice integrato, quindi scrivi ed esegui codice reale direttamente nel tuo browser e ricevi feedback istantaneo dall'IA — nessuna configurazione locale necessaria.

Tutte le lezioni di questo corso

  1. Varianti di build e configurazione dell’ambiente
  2. Pipeline automatizzate con Fastlane e GitHub Actions
  3. Crash reporting e stack trace con simboli
  4. Remote Config, feature flag e rollout graduali
← Torna a Flutter Mobile Development