Skip to content

Flutter SDK Reference

TL;DR: Add featureflip to pubspec.yaml, call client.boolVariation('flag-key', defaultValue: false), and you’re evaluating flags from any Flutter app.

The featureflip Flutter package is a single client SDK for evaluating feature flags across every Flutter platform — iOS, Android, web, macOS, Windows, and Linux. It is a client-side SDK: flag values arrive pre-evaluated from the Featureflip Evaluation API, so your targeting rules stay on the server and never get bundled into your release IPA, AAB, or web build. Distribution is pub.dev as featureflip, with automated publishing from the canonical mirror repo via OIDC.

The SDK is designed around Dart Streams and the ValueListenable pattern, so it integrates cleanly with StreamBuilder, ValueListenableBuilder, Provider, riverpod, bloc, and get_it. A FeatureflipBuilder widget rebuilds your subtree whenever a flag value changes — useful when percentage rollouts shift assignments while the app is running. The streaming connection is paused automatically when the app goes to the background (via WidgetsBindingObserver) and resumed when it returns to the foreground, conserving battery and avoiding unnecessary network use on mobile.

Identification works the way you’d expect: call identify() after sign-in, and the active context (anchored on userId for stable rollout buckets) updates immediately, with targeting rules re-evaluated against the new attributes. The SDK persists the latest snapshot to local storage via shared_preferences, so a cold start in airplane mode still resolves flags from the last successful sync rather than falling back to defaults.

View source on GitHub

  • Flutter 3.24+
  • Dart 3.5+

Add to your pubspec.yaml:

dependencies:
featureflip: ^2.0.0

Then run:

Terminal window
flutter pub get

The FeatureflipConfig class accepts the following options:

OptionTypeDefaultDescription
clientKeyStringrequiredClient SDK key from your project settings
baseUrlString"https://eval.featureflip.io"Evaluation API base URL
contextMap<String, dynamic>{}Initial evaluation context (user attributes)
streamingbooltrueEnable SSE for real-time flag updates
pollIntervalSecondsint30Polling interval in seconds (used when streaming is disabled)
flushIntervalSecondsint30Interval in seconds between event flush batches
flushBatchSizeint100Maximum number of events per flush batch
initTimeoutSecondsint10Maximum time in seconds to wait for initial flag fetch
final config = FeatureflipConfig(
clientKey: 'your-client-sdk-key',
context: {'user_id': 'u-123'},
streaming: true,
pollIntervalSeconds: 60,
initTimeoutSeconds: 15,
);

Obtain a client via the get() static factory and call initialize():

import 'package:featureflip/featureflip.dart';
final config = FeatureflipConfig(clientKey: 'your-client-sdk-key');
final client = FeatureflipClient.get('your-client-sdk-key', config: config);
await client.initialize();

initialize() fetches flag values from the API, starts SSE streaming (or polling), and begins the event processor. The SDK is usable after initialization completes, even if the network request fails (flags default to provided defaults).

FeatureflipClient.get() is singleton by construction — calling it multiple times with the same SDK key returns handles that share one underlying connection (refcounted). The connection shuts down only when the last handle is closed. This makes the client safe for any DI registration lifetime (singleton, scoped, or transient).

final client1 = FeatureflipClient.get('key', config: config);
final client2 = FeatureflipClient.get('key', config: config);
// Both share one connection. close() decrements the refcount.
client.isInitialized; // bool

A read-only property that returns true once initialize() has completed (regardless of whether the initial network fetch succeeded). For test clients created with forTesting(), this is immediately true.

All variation methods are synchronous and return immediately from an in-memory cache. They accept a flag key and a default value. The default is returned if the flag is missing or has an unexpected type.

Since this is a client-side SDK, evaluation context is set at initialization or via identify() — it is not passed per evaluation call.

final enabled = client.boolVariation('dark-mode', defaultValue: false);
final color = client.stringVariation('button-color', defaultValue: 'blue');
final limit = client.numberVariation('rate-limit', defaultValue: 100.0);

Returns a double.

final config = client.jsonVariation('ui-config', defaultValue: {'theme': 'light'});

Returns the raw dynamic value.

Call identify() to re-evaluate all flags for a new user context — typically after login. This makes a network request to the evaluation API with the new context and updates all cached flag values.

await client.identify({'user_id': 'u-123', 'plan': 'pro'});

The streaming or polling data source is also updated with the new context.

Enqueues a custom analytics event. Events are batched and flushed automatically.

client.track('purchase-completed', metadata: {
'plan': 'pro',
'amount': 29.99,
});

Forces all buffered events to be sent immediately.

await client.flush();

Decrements the refcount on the shared connection. If this is the last handle for the SDK key, stops streaming or polling and flushes remaining events. Safe to call multiple times — subsequent calls are no-ops.

await client.close();

The SDK automatically observes app lifecycle events via WidgetsBindingObserver:

  • Background (paused): Stops streaming/polling and flushes events
  • Foreground (resumed): Resumes streaming/polling

No manual intervention is required.

The SDK provides a FeatureflipProvider (a ChangeNotifier) for reactive Flutter widget updates. Access it via the client’s flagProvider property.

ListenableBuilder(
listenable: client.flagProvider,
builder: (context, _) {
final showBanner = client.flagProvider.boolVariation(
'show-banner',
defaultValue: false,
);
if (!showBanner) return const SizedBox.shrink();
return const Text('New feature available!');
},
)

The provider notifies listeners when flag values change via streaming or identify().

Creates a test client with static flag overrides. No network calls are made and no background tasks are started. The client is immediately initialized.

final client = FeatureflipClient.forTesting({
'dark-mode': true,
'pricing-tier': 'enterprise',
'rate-limit': 500,
});
client.boolVariation('dark-mode', defaultValue: false); // true
client.stringVariation('pricing-tier', defaultValue: 'free'); // 'enterprise'
client.boolVariation('unknown', defaultValue: false); // false (default)

Signature:

static FeatureflipClient forTesting(Map<String, dynamic> overrides)

Supported override value types: bool, String, int, double.

The internal representation of a flag returned by the server:

PropertyTypeDescription
valuedynamicThe evaluated flag value
variationStringThe key of the matched variation
reasonStringWhy this value was returned
ValueDescription
"Fallthrough"No rules matched; the fallthrough variation was served
"RuleMatch"A targeting rule matched the context
"FlagDisabled"The flag is disabled; the off variation was served
"FlagNotFound"The flag key does not exist
"Error"An error occurred during evaluation
"TEST"Returned by test clients
  • iOS
  • Android
  • Web
  • macOS
  • Linux
  • Windows