feature_gate_pro 1.2.5
feature_gate_pro: ^1.2.5 copied to clipboard
A high-performance, robust Feature Flag SDK for Flutter. Supports Local JSON, Remote Config, custom REST APIs, Percentage Rollouts, and Audience Targeting.
FeatureGate Pro #
A high-performance, robust, and extensible Feature Flag SDK for Flutter.
FeatureGate Pro is designed for enterprise and production scale, offering a unified Merge Engine that seamlessly cascades between Local JSON configs, Firebase Remote Config, and custom REST APIs. It includes built-in support for Percentage Rollouts, Audience Targeting, Analytics Sampling, and a powerful Developer Debug Dashboard.
π Key Features #
- π§ Merge Engine Architecture: Define multiple providers. If a flag isn't found in your API, the engine seamlessly falls back to Firebase, and then down to local JSON.
- π― Advanced Audience Targeting: Target specific user cohorts using operators like
==,!=,<,>,>=,<=,in, andnot_inacross User ID, Country, Platform, App Version (Semantic Versioning aware!), and custom JSON attributes. - π² Deterministic Percentage Rollouts: Safely roll out a feature to 25% of your user base using blazing-fast, predictable FNV-1a hashing. A user in the 25% bucket stays in the 25% bucket across sessions.
- β»οΈ Reactive Flutter Widgets:
FeatureFlagWidgetandFeatureFlagBuilderautomatically listen for background syncs and instantly rebuild your UI without manual state management. - π Analytics Layer with Sampling: Track flag evaluations and A/B test variants to Firebase (or your own provider). Includes an event sampling rate (
sampleRate: 0.10) to massively reduce analytics billing costs. - π‘οΈ Concurrency Locks: Rapid successive
refresh()calls are deduplicated into a single network request to protect battery and bandwidth. - π QA Debug Dashboard: A drop-in UI for your QA teams to inspect their user context, view all flag states, and force runtime overrides that instantly bypass targeting logic.
π¦ 1. Installation #
Add feature_gate_pro to your pubspec.yaml:
dependencies:
feature_gate_pro: ^1.0.3
π 2. Initialization & Merge Engine #
Initialize the FlagFlow engine before your app starts. You can provide multiple FlagProviders. The Merge Engine evaluates them in the exact order they are listed.
The Setup #
import 'package:flutter/material.dart';
import 'package:feature_gate_pro/feature_gate_pro.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await FlagFlow.initialize(
// 1. (Optional) Provide Context for Targeting & Rollouts
userContext: const UserContext(
id: 'user_123',
country: 'US',
platform: 'ios',
appVersion: '2.1.0',
),
// 2. Define your cascading providers
providers: [
// Priority 1: Check your custom REST API First
RestApiProvider(endpoint: 'https://api.my-app.com/flags'),
// Priority 2: Fallback to Firebase Remote Config
FirebaseAdapterProvider(
onFetchAndActivate: () async => await FirebaseRemoteConfig.instance.fetchAndActivate(),
onGetAll: () => FirebaseRemoteConfig.instance.getAll(), // Note: Pass the function! () =>
),
// Priority 3: Fallback to Local Defaults (assets are compiled into the app)
LocalJsonProvider(assetPath: 'assets/flags.json'),
],
// 3. (Optional) Setup periodic background syncing every 15 minutes
// Note: Local asset files are read-only and won't dynamically update.
// This timer is meant to trigger RestApiProvider or Firebase updates over the network.
refreshInterval: const Duration(minutes: 15),
// 4. (Optional) Attach an Analytics Adapter for A/B tracking
analytics: FirebaseAnalyticsAdapter(
sampleRate: 0.25, // Only log 25% of evaluations to save costs
logEvent: (name, params) async {
await FirebaseAnalytics.instance.logEvent(name: name, parameters: params);
},
),
);
runApp(const MyApp());
}
Firebase Setup Requirement #
Because FeatureGate Pro is completely dependency-free, you must install firebase_core and firebase_remote_config in your app yourself. Configure it using flutterfire configure, run Firebase.initializeApp() in main(), and pass the methods to FirebaseAdapterProvider. The SDK uses dynamic dispatch to automatically extract the strings behind the scenes!
Testing Firebase refreshInterval: By default, Firebase caches data for 12 hours. If you pass refreshInterval: Duration(minutes: 1) to FlagFlow, Firebase will ignore the network requests and return cached data. To actually test live background refreshes, you must lower Firebase's internal cache limit in your main.dart before initializing FlagFlow:
await FirebaseRemoteConfig.instance.setConfigSettings(RemoteConfigSettings(
fetchTimeout: const Duration(seconds: 10),
minimumFetchInterval: const Duration(minutes: 1), // Lower this for testing!
));
π¨ 3. UI Integration (Reactive Widgets) #
FeatureGate Pro provides highly optimized widgets that automatically rebuild whenever flag configurations change (e.g., via background refresh or a forced developer override).
FeatureFlagWidget (Declarative UI Toggling) #
Best for wrapping entire screens, features, or large UI blocks for simple true/false flags.
FeatureFlagWidget(
flagKey: 'new_checkout_flow',
// (Optional) Set to false if you don't want the UI to shift automatically when flags refresh in the background
listenToChanges: true,
// Rendered if 'new_checkout_flow' evaluates to true
child: const NewCheckoutScreen(),
// Rendered if false, missing, or targeting fails
fallback: const OldCheckoutScreen(),
)
FeatureFlagBuilder (Imperative Extraction for Strings/JSON) #
Best for extracting underlying strings, integers, or JSON maps dynamically.
FeatureFlagBuilder(
flagKey: 'enabled_modules',
listenToChanges: true,
builder: (context, flagValue) {
// flagValue.asString, .asBool, .asInt, .asDouble, .asJson
final modules = flagValue.asJson;
final isHomeEnabled = modules['home'] == true;
final isTasksEnabled = modules['tasks'] == true;
return Column(
children: [
if (isHomeEnabled) const HomeWidget(),
if (isTasksEnabled) const TasksWidget(),
],
);
}
)
JSON Parsing Magic: If you provide a raw JSON map in Firebase (e.g. {"home": true, "tasks": true}), FlagFlow automatically parses it and returns it via .asJson.
π― 4. Audience Targeting #
You can restrict a feature to a specific demographic without touching your Dart code. This works seamlessly across LocalJsonProvider, FirebaseAdapterProvider, and RestApiProvider.
To use advanced targeting, your flag in Firebase/JSON must be wrapped in a "value" key, alongside a "metadata" object.
Supported Operators #
==/!=(Equality)>,<,>=,<=(Numerical & Semantic Versioning)in,not_in(List inclusion)
Example JSON Format (Advanced Mode) #
{
"beta_feature": {
"value": true,
"metadata": {
"custom": {
"targeting": [
{
"attribute": "country",
"operator": "in",
"value": ["US", "CA", "UK"]
},
{
"attribute": "appVersion",
"operator": ">=",
"value": "2.0.0"
},
{
"attribute": "is_premium",
"operator": "==",
"value": true
}
]
}
}
}
}
In this example, beta_feature will only evaluate to true if the user is in US/CA/UK, running an app version of 2.0.0 or higher, and their UserContext indicates they are premium.
Updating UserContext at Runtime #
If a user logs in, you can hot-swap their context and refresh the flags:
FlagFlow.setContext(
UserContext(
id: user.uid,
country: user.country,
customAttributes: {'is_premium': user.isPremium}
)
);
π² 5. Percentage Rollouts #
Gradually roll out a high-risk feature to a random subset of users. FeatureGate Pro uses consistent hashing (FNV-1a), ensuring a user assigned to the "Enabled" bucket stays in that bucket forever.
Example JSON Ruleset #
{
"experimental_algorithm": {
"value": true,
"metadata": {
"custom": {
"rollout": 15
}
}
}
}
This configures a 15% rollout. The other 85% of users will fall back to the next FlagProvider in the Merge Engine (or evaluate to false if no fallback exists).
(Note: Rollouts require a UserContext with an id to hash against. If id is missing, the rollout fails gracefully).
π 6. QA Debug Dashboard & Runtime Overrides #
Empower your QA team to test every variant of your app without relying on production API changes.
FeatureGate Pro ships with FeatureGateDebugScreen, a drop-in Material dashboard.
Opening the Dashboard #
Navigator.push(
context,
MaterialPageRoute(builder: (_) => const FeatureGateDebugScreen()),
);
Dashboard Capabilities: #
- Inspect Context: View the active User ID, App Version, and Country.
- Flag Inventory: View every flag registered in the SDK alongside its current evaluation.
- Runtime Overrides: Tap any flag to force it to
TRUEorFALSE. - Bypass Logic: Overrides have absolute maximum priority in the Merge Engine. They instantly bypass all network providers, Targeting Rules, and Rollout maths.
- (Note: Overrides are strictly in-memory. They reset on app restart to prevent QA testers from accidentally bricking their local installation permanently).
π 7. API Reading (Manual Evaluation) #
If you need to check a flag imperatively (e.g., inside a Bloc or ViewModel) rather than using a Widget:
final isEnabled = FlagFlow.getBool('new_checkout', defaultValue: false);
final themeString = FlagFlow.getString('theme', defaultValue: 'light');
final maxRetries = FlagFlow.getInt('max_retries', defaultValue: 3);
final config = FlagFlow.getJson('config', defaultValue: {});
Manual Refreshing #
You can force a sync with your providers at any time (e.g. on a "Pull to Refresh" action):
// Safely mutex-locked! 10 rapid calls will only result in 1 network request.
await FlagFlow.refresh();
Debug Logging #
If you want to understand why a flag evaluated a certain way (e.g., why did targeting fail? Which provider won the merge conflict?), enable debug logging:
FlagFlow.enableDebugLogging = true;
// Console Output: [FlagFlow Debug] Evaluated 'beta_feature' to 'false' (Source: Default Fallback)
π§ͺ 8. How to Test Every SDK Feature #
This SDK is incredibly robust. Here is a checklist on how to verify every single feature locally:
1. Testing Firebase Live Updates (refreshInterval) #
By default, Firebase heavily caches data for 12 hours. If you want to test the refreshInterval: Duration(minutes: 1) feature, you must disable Firebase's cache during testing:
await FirebaseRemoteConfig.instance.setConfigSettings(RemoteConfigSettings(
fetchTimeout: const Duration(seconds: 10),
minimumFetchInterval: const Duration(minutes: kDebugMode ? 0 : 60), // π CRITICAL: 0 for testing!
));
2. Testing Percentage Rollouts #
- In your Dart code, initialize
FlagFlowwith a User ID:userContext: const UserContext(id: 'user_123'). - In Firebase or JSON, create a parameter
beta_featureand set"rollout": 50. - Try changing the
idfrom'user_123'to'user_456'. You will see the feature perfectly split 50/50 between different user IDs, but it will always be consistent for the same user!
3. Testing Audience Targeting #
- In your Dart code, pass attributes:
userContext: const UserContext(id: '1', country: 'US'). - In Firebase or JSON, create a parameter with targeting:
{
"value": true,
"metadata": { "custom": { "targeting": [ { "attribute": "country", "operator": "==", "value": "US" } ] } }
}
- Run the app, the feature will show. Change
country: 'IN'in your code and restartβthe feature will instantly be blocked!
4. Testing the Offline Cache #
- Initialize
FlagFlowwithcache: SharedPreferencesCache(). - Run your app online so the cache can silently save the Firebase data to your phone.
- Turn off Wi-Fi and Cellular Data on your phone (Airplane Mode).
- Restart the app. The SDK will detect there is no internet, and immediately load your UI perfectly using the offline cache!
5. Testing the QA Debug Dashboard #
- Anywhere in your app, add a temporary button that navigates to
FeatureGateDebugScreen. - Open the dashboard. You will see your active Context and a list of all your flags.
- Tap on a flag to force a Runtime Override (e.g. force it to TRUE).
- Go back to your app. The UI will instantly update, completely bypassing Firebase and Targeting logic! (Restart the app to clear the override).
6. Testing the Custom REST API #
- Go to a mock JSON site like
npoint.ioand create a JSON payload. - Add
RestApiProvider(endpoint: 'https://api.npoint.io/your_id')as the FIRST provider in your list. - Update the JSON on
npoint.io. Within 1 minute, the app will download the new JSON and instantly update the screen!
7. Testing Local JSON Fallbacks #
- Create a
flags.jsonfile in yourassets/folder and add it topubspec.yaml. - Add
LocalJsonProvider(assetPath: 'assets/flags.json')as the LAST provider in your list. - Try deleting a flag from Firebase. When the SDK fails to find the flag online, it will perfectly fall back to the value in your local asset file.
8. Testing Analytics Tracking #
- Add
analytics: FirebaseAnalyticsAdapter(sampleRate: 1.0, logEvent: ...)toFlagFlow.initialize(). - Open your app and let your flags evaluate.
- Go to the Firebase Analytics Dashboard (or look at your terminal console). You will see
"feature_evaluated"events successfully tracked!
π License #
This project is licensed under the MIT License - see the LICENSE file for details.