- Android version 21 & above
- iOS version 12 & Above
- Apple TvOS version 13 & Above
- Apple WatchOS version 7 & Above
Installation
Add this to yourpubspec.yaml file
Quick Usage
Create aGrowthBookSDK instance with GBSDKBuilderApp, set attributes, then evaluate features or run experiments.
Using Features
Thefeature method takes a String feature name and returns a GBFeatureResult object with a few useful properties:
- value (
dynamic) - The assigned value of the feature - on (
bool) - The value cast to a boolean - off (
bool) - The value cast to a boolean and then negated - source (
String) - Why the value was assigned to the user. One of “unknownFeature”, “defaultValue”, “force”, or “experiment”
- experiment (
GBExperiment) - experimentResult (
GBExperimentResult)
Attributes
Attributes define the current user and request “context” used for targeting rules and bucketing.Common attributes
id(string): primary user identifier for consistent bucketingdeviceId(string): device/install-scoped fallback when user not logged incountry,locale(string)plan,company,role(string)loggedIn(bool)appVersion(string, supports semver targeting)
setAttributes() method to set the user context.
What is the user context?
- Identity: stable identifiers for bucketing (e.g.,
id, and optionally a logged-out fallback likedeviceId). - Demographics and traits: e.g.,
country,company,plan,loggedIn. - App/runtime context: e.g.,
appVersion,platform,locale. - Request context (if applicable): e.g.,
url,path,device.
Identity changes (login/logout)
- On login, switch from
deviceIdtoid(stable user id). - On logout, remove
idand usedeviceIduntil the next login.
cacheKeyAttributes and refreshing when identity changes:
Experimentation (A/B Testing)
To run the experiements withGrowthBook, use run method, which takes a GBExperiment object as an argument and returns a GBExperimentResult object:
GBExperiment class has two required properties - key and variations. There are also a number of optional properties:
- key (
String) - The unique identifier for this experiment - variations (
dynamic[]) - Array of variations to decide between - weights (
double[]) - How to weight traffic between variations. Must add to 1. - active (
bool) - If set to false, always return the control (first variation) - coverage (
double) - What percent of users should be included in the experiment (between 0 and 1, inclusive) - condition (
GBCondition) - Optional targeting condition - namespace (
[String, int, int]) - Adds the experiment to a namespace - force (
int) - All users included in the experiment will be forced into the specific variation index - hashAttribute (
String) - What user attribute should be used to assign variations (defaults toid)
GBExperimentResult object returns the following properties:
- inExperiment (
bool) - variationId (
int) - The array index of the assigned variation - value (
dynamic) - The value of the assigned variation - hashAttribute (
String) - The user attribute used to assign a variation - hashValue (
String) - The value of the attribute used to assign a variation
Tracking & Subscriptions
UsegrowthBookTrackingCallBack to receive experiment exposure events whenever a user is assigned to a variation (via features or inline experiments).
For feature usage events (non-experiment), wrap your feature(...) calls in a small utility to emit custom app analytics.
When subscriptions fire vs. don’t fireWhen subscriptions fire vs. don’t fire
- Fire: on first assignment per experiment key and after assignments change due to attribute updates or payload changes.
- Don’t fire: repeated reads of the same assignment that hasn’t changed.
Efficient subscription patterns
- Keep callbacks lightweight; offload heavy work to background tasks.
- Coalesce multiple exposure events before sending to analytics to reduce overhead.
- Include
experiment.key,variationId, and optionalfeatureIdfor correlation.
Unsubscribing and memory management
- Remove any of your app-layer listeners in
dispose(). - Dispose the SDK instance when not needed (e.g., app shutdown) to release resources and close streams.
Loading Features
Built-in fetching and caching
If you pass ahostURL and apiKey into the builder, the SDK handles network requests, caching, retry/backoff, and decryption (when configured in your SDK connection). You can enable streaming updates with backgroundSync: true.
Custom integration (local evaluation)
If you prefer to control network and caching yourself, you can set a payload directly on the SDK. This enables fully local evaluation and offline-first behavior.Caching
The SDK persists downloaded feature payloads and related metadata. Configure TTL to control how long cached features are considered fresh before a background refresh.- To use Stale-While-Revalidate strategy with TTL and streaming:
- Manual refresh at lifecycle boundaries:
- Identity or environment switch:
Web-specific caching considerations
- For Flutter Web, ensure your CDN respects cache-control headers for GrowthBook endpoints.
- Avoid overly aggressive service worker caching for evaluated payloads unless you manage invalidation carefully.
Real-time Updates (SSE)
Setup and lifecycle
- Initialization
- Enable
backgroundSync: truewhen building the SDK instance. - Optionally pass
streamingRequestHeaders(e.g., auth) andLast-Event-Idto resume after restarts.
- Enable
- Refresh handler
- Use
onFeaturesRefreshedto update UI or invalidate caches after new data is applied. - The callback fires for both streaming updates and manual refreshes.
- Use
- Teardown
- Dispose your SDK instance when your app shuts down. The connection will be closed automatically.
Network failures and reconnection
- The SDK uses an exponential backoff strategy on streaming errors and attempts to auto-reconnect.
- Provide stable connectivity hints to users only when necessary; otherwise allow background reconnection.
- Persist and reuse the
Last-Event-Idto avoid duplicate events on resume.
Performance Considerations
- Prefer streaming when you need near real-time flag updates (admin toggles, ops tooling).
- Prefer polling/manual refresh when updates are infrequent or the app is latency- or battery-sensitive.
- Background sync opens a single lightweight SSE connection; avoid running multiple SDK instances with streaming in the same app.
Remote Evaluation
Run GrowthBook in Remote Evaluation mode to evaluate flags on a private server (e.g., GrowthBook Proxy). Sensitive rules never reach the client.Sticky Bucketing with Remote EvaluationIf you want Sticky Bucketing with Remote Evaluation, configure your remote backend (e.g., GrowthBook Proxy) with a persistent store (e.g., Redis). You do not need to provide a sticky service on the client.
Remote vs local evaluation
- Use remote evaluation when:
- You need to keep targeting rules and unused variations off-device for privacy/compliance.
- Payload size is large and you want device-optimized responses for the current user.
- Use local evaluation when:
- You need offline-first behavior or minimal round trips.
- The full feature payload is small and regularly reused.
Configuration
- Endpoint: Set
hostURLto your GrowthBook Proxy or secured backend base URL. - Auth: Send authentication via
requestHeadersto both fetching and remote eval endpoints. - Cache keys: Use
cacheKeyAttributesto limit remote calls to only when relevant identity fields change.
Debugging Remote Evaluation
- Verify required attributes are present before calling
refreshForRemoteEval(). - Log the response status/source from your SDK callbacks to confirm whether updates came from network or cache.
- Check your proxy/server logs for rejected requests, auth failures, or schema mismatches.
Security considerations
- Treat the
apiKeyas public. Protect sensitive data with server-side auth on your proxy. - Use short-lived tokens in
requestHeaderswhen possible and rotate regularly. - Avoid putting PII directly in attributes unless hashed/secured per your policies.
Fallback strategies
- If remote eval fails, keep using the last known values from cache and retry later.
- Consider a hybrid approach: bootstrap with a local payload and switch to remote eval when online.
- Implement a user-visible retry or manual refresh action when business-critical.
Sticky Bucketing
Sticky bucketing ensures users see the same experiment variant across sessions. Read more about Sticky Bucketing Feature- Enable Sticky Bucketing on the experiment or feature rule in GrowthBook.
- Provide a stable identity in attributes (e.g.,
id) after login; use a device-scoped fallback when logged out. - Use a globally unique
idfor logged-in users; avoid mutable identifiers. - For logged-out sessions, use a device/install identifier and switch to
idon login. - When identity changes (login/logout), update attributes immediately to avoid cross-user assignment mixing.
GBStickyBucketService to persist assignments. Attach your GBStickyBucketService when building the SDK to persist assignments across sessions.
Custom Sticky Bucketing Service configurations
- Namespacing: use an environment-specific key prefix (e.g.,
prod_,staging_). - Storage backend: choose durable storage for your platform (
SharedPreferences, secure storage, SQLite). - Migration: when changing storage layout, migrate existing assignment keys to preserve continuity.
Troubleshooting stale assignments
- Logout or user-switch: clear assignments for the previous identity or use identity-specific keys.
- Experiment retired/changed: allow non-sticky behavior to take effect; optionally prune retired experiment keys.
- Unexpected variant: confirm which identity key is being used and verify read/write paths in your storage.
Encrypted features
Configure SDK Connection to deliver encrypted features, provide the decryption key during initialization.Performance optimizations
- Track refresh times and payload sizes; reduce attribute surface area where possible.
- Avoid multiple SDK instances with streaming enabled; prefer a single shared instance.
- Use
onFeaturesRefreshedto scope UI rebuilds and avoid unnecessary widget tree updates.
Memory usage patterns
- Keep attribute maps small and reuse them when doing partial updates.
- Periodically prune stale sticky assignments if you implement a custom store.
- Avoid holding multiple SDK instances; share a single instance across the app.
Error handling and debugging
Debug logging configuration
Common error scenarios and solutions
- Initialization returned from cache only
- Ensure network connectivity and correct
hostURL/apiKey. - Call
await sdk.refreshCache()on resume or via pull-to-refresh.
- Ensure network connectivity and correct
- Unauthorized (401/403)
- Verify
requestHeaders(e.g., Bearer token) and that your proxy/CDN forwards headers.
- Verify
- Decryption errors
- Confirm
decryptionKeymatches the SDK Connection and is valid; prefer server-side decryption.
- Confirm
Feature evaluation debugging techniques
Network connectivity troubleshooting
Platform-specific considerations
Web vs mobile behavior differences
- SSE support and networking may differ in browsers; validate CORS and service worker behavior for Flutter Web.
- Storage persistence differs (IndexedDB/localStorage on Web vs SharedPreferences/Secure Storage on mobile).
- For Web, prefer IndexedDB for larger storage; be careful with quota and private browsing modes.
- For Mobile: use
SharedPreferencesor secure storage for sticky assignments; consider SQLite for large datasets.
iOS/Android specific considerations
- iOS backgrounding may pause network activity; prefer manual refresh on foreground via app lifecycle hooks.
- Android Doze/App Standby can throttle background sync; design UX that tolerates delayed updates.
refreshCache() and rely on backoff reconnects.
See Flutter SDK repo: https://github.com/growthbook/growthbook-flutter

