growthbook_sdk_flutter
一个开源的特性标志和实验平台,可轻松更改功能并执行A/B测试。
GrowthBook Flutter SDK
^5.0.0^2.0.7{"sdk":"flutter"}^4.9.0^4.0.0^2.2.3^2.0.2^3.2.1^2.0.0{"sdk":"flutter"}^3.0.1^2.1.11^6.2.0any^22.7.2以下为英文项目原文快照,最新内容请访问 GitHub。
GrowthBook Flutter SDK
pub package License: MIT Platform Dart
🎯 Feature flags • 🧪 A/B testing • 📊 Analytics integration
Quick Start • Features • Documentation • Examples • Resources
GrowthBook is an open source feature flagging and experimentation platform. This Flutter SDK allows you to use GrowthBook with your Flutter based mobile application.
Platform Support:
| Feature | Support | Since Version |
|---|---|---|
| ✅ Feature Flags | Full Support | All versions |
| ✅ A/B Testing | Full Support | All versions |
| ✅ Sticky Bucketing | Full Support | ≥ v3.8.0 |
| ✅ Remote Evaluation | Full Support | ≥ v3.7.0 |
| ✅ Streaming Updates | Full Support | ≥ v3.4.0 |
| ✅ Prerequisites | Full Support | ≥ v3.2.0 |
| ✅ Encrypted Features | Full Support | ≥ v3.1.0 |
| ✅ v2 Hashing | Full Support | ≥ v3.1.0 |
| ✅ SemVer Targeting | Full Support | ≥ v3.1.0 |
| ✅ TTL Caching | Full Support | ≥ v3.9.10 |
Add to your pubspec.yaml:
dependencies:
growthbook_sdk_flutter: ^3.9.10
import 'package:growthbook_sdk_flutter/growthbook_sdk_flutter.dart';
// Initialize the SDK
final sdk = await GBSDKBuilderApp(
apiKey: "sdk_your_api_key",
hostURL: "https://growthbook.io",
attributes: {
'id': 'user_123',
'email': 'user@example.com',
'country': 'US',
},
growthBookTrackingCallBack: (trackData) {
// Track experiment exposures
final experiment = trackData.experiment;
final result = trackData.experimentResult;
print('Experiment: ${experiment.key}, Variation: ${result.variationID}');
},
).initialize();
// Use feature flags
final welcomeMessage = sdk.feature('welcome_message');
if (welcomeMessage.on) {
print('Feature is enabled: ${welcomeMessage.value}');
}
// Run A/B tests
final buttonExperiment = GBExperiment(key: 'button_color_test');
final result = sdk.run(buttonExperiment);
final buttonColor = result.value ?? 'blue'; // Default color
class MyHomePage extends StatelessWidget {
final GrowthBookSDK sdk;
@override
Widget build(BuildContext context) {
final newDesign = sdk.feature('new_homepage_design');
return Scaffold(
body: newDesign.on
? NewHomepageWidget()
: ClassicHomepageWidget(),
);
}
}
final sdk = await GBSDKBuilderApp(
apiKey: "your_api_key",
growthBookTrackingCallBack: (trackData) {
final experiment = trackData.experiment;
final result = trackData.experimentResult;
// Google Analytics
FirebaseAnalytics.instance.logEvent(
name: 'experiment_viewed',
parameters: {
'experiment_id': experiment.key,
'variation_id': result.variationID,
'variation_name': result.key,
},
);
// Mixpanel
Mixpanel.track('Experiment Viewed', {
'Experiment ID': experiment.key,
'Variation ID': result.variationID,
});
// Custom analytics
YourAnalytics.trackExperiment(experiment, result);
},
).initialize();
final sdk = await GBSDKBuilderApp(
// Required
apiKey: "sdk_your_api_key",
hostURL: "https://growthbook.io",
// User Context
attributes: {
'id': 'user_123',
'email': 'user@example.com',
'plan': 'premium',
'country': 'US',
},
// Performance & Caching
ttlSeconds: 300, // Cache TTL (default: 60s)
backgroundSync: true, // Real-time updates
// Testing & QA
qaMode: false, // Disable randomization for QA
forcedVariations: { // Force specific variations
'button_test': 1,
},
// Analytics Integration
growthBookTrackingCallBack: (trackData) {
// Send to your analytics platform
final experiment = trackData.experiment;
final result = trackData.experimentResult;
analytics.track('Experiment Viewed', {
'experiment_id': experiment.key,
'variation_id': result.variationID,
'variation_name': result.key,
});
},
// Advanced Features
remoteEval: false, // Server-side evaluation
encryptionKey: "...", // For encrypted features
).initialize();
The SDK logs internal events (feature evaluation skips, cache errors, refresh
attempts) through a logger package instance. Verbosity is controlled by
GrowthBookSDK.setLogLevel(...), which accepts an SDK-owned GBLogLevel enum:
import 'package:growthbook_sdk_flutter/growthbook_sdk_flutter.dart';
void main() {
// Configure once at app startup — before initializing the SDK.
GrowthBookSDK.setLogLevel(GBLogLevel.debug);
runApp(const MyApp());
}
Available levels: verbose, debug, info, warning (default), error, off.
⚠️ Process-global: log level is shared across all
GrowthBookSDKinstances in the same process. Set it once at app startup rather than per-instance. If you run multiple SDK instances, they will share the same verbosity.
// Boolean flags
final isEnabled = sdk.feature('new_feature').on;
// String values
final welcomeText = sdk.feature('welcome_message').value ?? 'Welcome!';
// Number values
final maxItems = sdk.feature('max_items').value ?? 10;
// JSON objects
final config = sdk.feature('app_config').value ?? {
'theme': 'light',
'animations': true,
};
// Check feature source
final feature = sdk.feature('premium_feature');
switch (feature.source) {
case GBFeatureSource.experiment:
print('Value from A/B test');
break;
case GBFeatureSource.force:
print('Forced value');
break;
case GBFeatureSource.defaultValue:
print('Default value');
break;
}
// Define experiment
final experiment = GBExperiment(
key: 'checkout_button_test',
variations: ['🛒 Buy Now', '💳 Purchase', '✨ Get It Now'],
weights: [0.33, 0.33, 0.34], // Traffic distribution
);
// Run experiment
final result = sdk.run(experiment);
// Use result
Widget buildButton() {
final buttonText = result.value ?? '🛒 Buy Now';
return ElevatedButton(
onPressed: () => handlePurchase(),
child: Text(buttonText),
);
}
// Track conversion
if (purchaseCompleted) {
// Your analytics will receive this via trackingCallBack
}
// Update user attributes dynamically
sdk.setAttributes({
'plan': 'enterprise',
'feature_flags_enabled': true,
'last_login': DateTime.now().toIso8601String(),
});
// Target users with conditions
// Example: Show feature only to premium users in US
// This is configured in GrowthBook dashboard, not in code
The SDK supports the following operators in targeting conditions:
| Operator | Description |
|---|---|
$eq |
Equal to |
$ne |
Not equal to |
$lt |
Less than |
$lte |
Less than or equal |
$gt |
Greater than |
$gte |
Greater than or equal |
| Operator | Description |
|---|---|
$in |
Value is in array |
$nin |
Value is not in array |
$all |
Array contains all values |
$ini |
Value is in array (case-insensitive string comparison) |
$nini |
Value is not in array (case-insensitive string comparison) |
$alli |
Array contains all values (case-insensitive string comparison) |
For
$ini,$nini, and$alli: string values are compared after lowercasing; non-string values (numbers, booleans, null) are compared as-is.
| Operator | Description |
|---|---|
$regex |
Matches regex (case-sensitive) |
$notRegex |
Does not match regex (case-sensitive) |
$regexi |
Matches regex (case-insensitive) |
$notRegexi |
Does not match regex (case-insensitive) |
| Operator | Description |
|---|---|
$exists |
Attribute exists (true) or is absent (false) |
$type |
Attribute type matches string ("string", "number", "boolean", "array", "object", "null") |
$not |
Negates a condition |
$size |
Array length matches condition |
$elemMatch |
At least one array element matches condition |
$vgt / $vlt / $vgte / $vlte / $veq / $vne |
Semantic version comparison |
// Example: case-insensitive membership
// Matches users where country is "us", "US", "Us", etc.
final condition = {
'country': {'\$ini': ['US', 'CA', 'GB']}
};
// Example: case-insensitive regex
// Matches "Hello", "hello", "HELLO", etc.
final condition2 = {
'greeting': {'\$regexi': '^hello'}
};
The SDK implements an intelligent caching system for optimal performance:
final sdk = await GBSDKBuilderApp(
apiKey: "your_api_key",
ttlSeconds: 300, // Cache TTL: 5 minutes
backgroundSync: true, // Enable background refresh
).initialize();
Ensure consistent user experiences across sessions:
class MyAppStickyBucketService extends GBStickyBucketService {
@override
Future<Map<String, String>?> getAllAssignments(
Map<String, dynamic> attributes,
) async {
// Retrieve from local storage
final prefs = await SharedPreferences.getInstance();
final json = prefs.getString('gb_sticky_assignments');
return json != null ? jsonDecode(json) : null;
}
@override
Future<void> saveAssignments(
Map<String, dynamic> attributes,
Map<String, String> assignments,
) async {
// Save to local storage
final prefs = await SharedPreferences.getInstance();
await prefs.setString('gb_sticky_assignments', jsonEncode(assignments));
}
}
// Use with SDK
final sdk = await GBSDKBuilderApp(
apiKey: "your_api_key",
stickyBucketService: MyAppStickyBucketService(),
).initialize();
The Flutter SDK may be run in Remote Evaluation mode. This mode brings the security benefits of a backend SDK to the front end by evaluating feature flags exclusively on a private server. Using Remote Evaluation ensures that any sensitive information within targeting rules or unused feature variations are never seen by the client.
You must enable Remote Evaluation in your SDK Connection settings. Cloud customers are also required to self-host a GrowthBook Proxy Server or a custom remote evaluation backend.
To use Remote Evaluation, add the remoteEval: true property to your SDK instance.
final sdk = await GBSDKBuilderApp(
apiKey: "your_api_key",
remoteEval: true, // Enable remote evaluation
attributes: userAttributes,
).initialize();
// Features are evaluated server-side
// Sensitive targeting rules never reach the client
The GrowthBook SDK supports streaming with Server-Sent Events (SSE). When enabled, changes to features within GrowthBook will be streamed to the SDK in realtime as they are published. This is only supported on GrowthBook Cloud or if running a GrowthBook Proxy Server.
final sdk = await GBSDKBuilderApp(
apiKey: "your_api_key",
backgroundSync: true, // Enable streaming updates
).initialize();
// Features automatically update when changed in GrowthBook
// No need to restart the app or refresh manually
Plugins observe SDK lifecycle events (feature evaluated, experiment viewed) and can implement custom side effects such as forwarding events to an analytics backend. The SDK ships with GrowthBookTrackingPlugin, which batches events and sends them to the GrowthBook ingest endpoint.
import 'package:growthbook_sdk_flutter/growthbook_sdk_flutter.dart';
Future<void> main() async {
final sdk = await GBSDKBuilderApp(
apiKey: 'sdk-xxx',
hostURL: 'https://cdn.growthbook.io',
growthBookTrackingCallBack: (_) {},
)
.addPlugin(GrowthBookTrackingPlugin())
.initialize();
runApp(MyApp(sdk: sdk));
}
final trackingPlugin = GrowthBookTrackingPlugin(
config: GrowthBookTrackingPluginConfig(
ingestorHost: 'https://ingest.growthbook.io',
batchSize: 50,
batchTimeout: Duration(seconds: 30),
),
);
await GBSDKBuilderApp(...)
.addPlugin(trackingPlugin)
.initialize();
dispose()Tracking plugins buffer events in memory to reduce network overhead. You must call await sdk.dispose() when the SDK instance is no longer needed so buffered events are flushed and any resources (timers, HTTP clients) are released. Without this, queued tracking events will be dropped during app shutdown.
class _MyAppState extends State<MyApp> {
late final GrowthBookSDK sdk;
@override
void initState() {
super.initState();
_initSdk();
}
Future<void> _initSdk() async {
sdk = await GBSDKBuilderApp(...)
.addPlugin(GrowthBookTrackingPlugin())
.initialize();
}
@override
void dispose() {
// Flushes pending tracking events and releases plugin resources
sdk.dispose();
super.dispose();
}
}
For CLI tools, server processes, or short-lived isolates, wrap SDK usage in a try/finally:
final sdk = await GBSDKBuilderApp(...).addPlugin(GrowthBookTrackingPlugin()).initialize();
try {
// ... SDK usage
} finally {
await sdk.dispose();
}
Extend GrowthBookPlugin to implement your own tracking, logging, or analytics forwarding:
class MyAnalyticsPlugin extends GrowthBookPlugin {
@override
void initialize(String clientKey) {
// one-time setup (e.g. start a periodic flush timer)
}
@override
void onFeatureEvaluated(String id, GBFeatureResult result, Map<String, dynamic>? attributes) {
// forward to your analytics
}
@override
void onExperimentViewed(GBExperiment experiment, GBExperimentResult result, Map<String, dynamic>? attributes) {
// forward to your analytics
}
@override
Future<void> close() async {
// flush any buffered state, release resources
}
}
⚠️ Plugin errors are isolated per plugin — a throw in one plugin's callback does not affect other plugins or SDK evaluation. If you need stronger delivery guarantees than best-effort batching, implement retry/requeue with bounded storage inside your plugin's
close()method.
class ProductPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
final sdk = context.read<GrowthBookSDK>();
// Feature flags
final showReviews = sdk.feature('show_product_reviews').on;
final freeShipping = sdk.feature('free_shipping_threshold').value ?? 50.0;
// A/B test for pricing display
final pricingExperiment = GBExperiment(key: 'pricing_display_test');
final pricingResult = sdk.run(pricingExperiment);
return Scaffold(
body: Column(
children: [
ProductImage(),
ProductTitle(),
// Dynamic pricing display based on A/B test
if (pricingResult.value == 'with_discount')
PricingWithDiscount()
else
StandardPricing(),
// Conditional features
if (showReviews) ProductReviews(),
if (freeShipping > 0) FreeShippingBanner(threshold: freeShipping),
AddToCartButton(),
],
),
);
}
}
class NewFeatureService {
final GrowthBookSDK sdk;
NewFeatureService(this.sdk);
bool get isNewDashboardEnabled {
final feature = sdk.feature('new_dashboard_v2');
// Feature is rolled out gradually:
// 0% → 5% → 25% → 50% → 100%
// Configured in GrowthBook dashboard
return feature.on;
}
Widget buildDashboard() {
return isNewDashboardEnabled
? NewDashboardWidget()
: LegacyDashboardWidget();
}
}
# Clone the repository
git clone https://github.com/growthbook/growthbook-flutter.git
cd growthbook-flutter
# Install dependencies
flutter pub get
# Generate code (for json_serializable)
dart run build_runner build --delete-conflicting-outputs
# Run all tests
flutter test
# Run tests with coverage
flutter test --coverage
# Run specific test file
flutter test test/features/feature_test.dart
The SDK uses json_serializable for JSON parsing. When you modify model classes with @JsonSerializable(), run:
# Watch for changes and auto-generate
dart run build_runner watch
# One-time generation
dart run build_runner build --delete-conflicting-outputs
# Check linting
dart analyze
# Format code
dart format .
# Fix auto-fixable issues
dart fix --apply
cd example
flutter pub get
flutter run
// Mock SDK for testing
class MockGrowthBookSDK implements GrowthBookSDK {
final Map<String, dynamic> mockFeatures;
MockGrowthBookSDK({required this.mockFeatures});
@override
GBFeatureResult feature(String key) {
final value = mockFeatures[key];
return GBFeatureResult(
value: value,
on: value == true,
source: GBFeatureSource.force,
);
}
}
// Use in tests
void main() {
testWidgets('shows new feature when enabled', (tester) async {
final mockSDK = MockGrowthBookSDK(
mockFeatures: {'new_feature': true},
);
await tester.pumpWidget(MyApp(sdk: mockSDK));
expect(find.text('New Feature'), findsOneWidget);
});
}
// Different configurations for different environments
final sdk = await GBSDKBuilderApp(
apiKey: kDebugMode
? "sdk_dev_your_dev_key"
: "sdk_prod_your_prod_key",
hostURL: kDebugMode
? "https://growthbook-dev.yourcompany.com"
: "https://growthbook.yourcompany.com",
qaMode: kDebugMode, // Disable randomization in debug
).initialize();
We welcome contributions! Here's how to get started:
flutter testdart format .feat:, fix:, docs:, etc.This project is licensed under the MIT License - see the LICENSE file for details.
Originally contributed by the team at Alippo. The core GrowthBook platform remains open-source and free forever.
Made with ❤️ by the GrowthBook community