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 user_id for stable rollout buckets) updates immediately, with targeting rules re-evaluated against the new attributes. Flag values are held in memory, so a cold start with no network serves your defaults until the first fetch succeeds. The one thing the SDK writes to shared_preferences is the anonymous ID it generates for a signed-out user, which keeps that user in the same rollout bucket across launches.

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
sendEvaluationEventsbooltrueReport which flags the app reads, so flags it no longer reads can be archived. See Read reporting
wrapperNameString?nullFor libraries that wrap the SDK: a name sent after the SDK’s own identity token
wrapperVersionString?nullVersion sent with wrapperName
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), and false after close() — a closed handle serves the caller’s default from every accessor. 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,
});

Featureflip counts each event name, per environment. The metadata you pass is transmitted with the event but is not stored, and event counts are not surfaced in the app or API.

Forces all buffered events to be sent immediately.

await client.flush();

Since 2.7.0, the SDK reports the flags your app reads, which is how Featureflip tells a client-side flag still in use from one whose code is gone. boolVariation, stringVariation, numberVariation and jsonVariation count, whether you call them on the client or on flagProvider. Reading flags in build() is fine. Each flag, variation and user is reported about once an hour per device and again whenever the app returns to the foreground, in the same batches as track() events.

allFlags() is the exception. A flag your app reaches only through it looks unused, so it can be marked stale and archived.

To opt out, pass sendEvaluationEvents: false, and Featureflip counts every flag it sends the app as read, so none of them can be archived while the app is in use.

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