Help on feature flag. :rotating_light: I’ve imple...
# sdk-flutter
b
Help on feature flag. 🚨 I’ve implemented
feature flag
in flutter and reading the feature but it is not picking the value from
Percentage Rollout
instead it is showing me the default value of GB. Do anyone know or faced similar issue before? I am using
growthbook_sdk_flutter: ^3.9.4
. here is the code I shared above. https://growthbookusers.slack.com/archives/C038XP683NC/p1720552142529219 @most-spoon-61816 @brief-honey-45610
m
сс: @calm-dog-24239
c
Hi, @breezy-activity-86410. We will check that and write to you.
b
Thanks @calm-dog-24239, we are in middle of launching our feature and hoping this chat will help us.
👍 1
@calm-dog-24239 I am using flutter web. cc @most-spoon-61816
👍 1
c
Ok, thank you. It will help us to reproduce.
👍 1
import ‘dart:developer’; import ‘dart:io’; import ‘package:flutter/foundation.dart’; import ‘package:flutter/material.dart’; import ‘package:growthbook_sdk_flutter/growthbook_sdk_flutter.dart’; void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return MaterialApp( darkTheme: ThemeData.dark(), themeMode: ThemeMode.dark, home: const Home(), ); } } class Home extends StatefulWidget { const Home({Key? key}) : super(key: key); @override State<Home> createState() => _HomeState(); } class _HomeState extends State<Home> with SingleTickerProviderStateMixin { List<String> tabNames = <String>[ ‘All’, ‘Travel’, ‘Lifestyle’, ‘Fitness’, ‘Education’, ‘Elections’, ‘Original’, ‘World’, ‘Travel’ ]; late List<Tab> tabs; /// Initialization of controllers. late TabController _tabController; final userAttr = {“id”: (!kIsWeb && Platform.isIOS) ? “foo” : “foo_bar”}; GrowthBookSDK? gb; @override void initState() { super.initState(); _tabController = TabController(length: tabNames.length, vsync: this); tabs = <Tab>[...tabNames.map((e) => Tab(text: e)).toList()]; initializeSDK(); } void initializeSDK() async { gb = await GBSDKBuilderApp( apiKey: kReleaseMode ? ‘<PROD_KEY>’ : ‘<DEV_KEY>’, hostURL: ‘https://example.growthbook.io/’, attributes: userAttr, growthBookTrackingCallBack: (exp, rst) {}, ).initialize(); for (final featureFlag in FeatureFlag.values) { final result = gb?.feature(featureFlag.id); log(‘Feature flag: ${featureFlag.id}, ’ ‘value: ${result?.value}, source: ${result?.source}’); } setState(() {}); } Widget _getRightWidget() { if (gb?.feature(‘some-feature’).on! ?? false) { return TabBar( isScrollable: true, tabs: tabs, controller: _tabController, indicator: CircleTabIndicator( color: Theme.of(context).colorScheme.primary, radius: 4, ), ); } else { return TabBar( isScrollable: true, tabs: tabs, controller: _tabController, ); } } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text(‘GrowthBook SDK’), ), body: Column( children: [ _getRightWidget(), Expanded( child: TabBarView( controller: _tabController, children: [ for (var i = 0; i < tabNames.length; i++) Center( child: Column( mainAxisSize: MainAxisSize.min, children: [ Text(tabNames[i]), // Text(features), ], ), ), ], ), ), ], ), ); } } //// To create dot shaped indicator. class CircleTabIndicator extends Decoration { final BoxPainter _painter; CircleTabIndicator({required Color color, required double radius}) : _painter = _CirclePainter(color, radius); @override BoxPainter createBoxPainter([VoidCallback? onChanged]) => _painter; } enum FeatureFlag { wappShort(‘wapp_short’, FeatureFlagType.bool, false), wappPick(‘wapp_pick’, FeatureFlagType.bool, false), moreRecipes(‘wapp_paywall’, FeatureFlagType.bool, false), ; const FeatureFlag(this.id, this.type, this.defaultValue); final String id; final FeatureFlagType type; final dynamic defaultValue; } enum FeatureFlagType { bool } class _CirclePainter extends BoxPainter { final Paint _paint; final double radius; _CirclePainter(Color color, this.radius) : _paint = Paint() ..color = color ..isAntiAlias = false; @override void paint(Canvas canvas, Offset offset, ImageConfiguration cfg) { final Offset circleOffset = offset + Offset(cfg.size!.width / 2, cfg.size!.height - radius); canvas.drawCircle(circleOffset, radius, _paint); }
We tried like this and we received values
b
@calm-dog-24239 seems like you are getting the default value’s from GB, are you able to get percentage rollout?
Screenshot 2024-07-22 at 6.35.50 pm.png
c
We will try it, but you said previously that you can’t get value, you received nil
image.png
b
@calm-dog-24239 Yes sorry for the misunderstanding. That was couple of days ago, I updated the GB SDK to latest version then I received the values. But the real issue is -> I am getting default value always. It should be pick the value from precentage rollout as according to GB percentage rollout overrides the default value.
@calm-dog-24239 as you can see the above SS of my GB console, the
default
value is
false
but rollout feature
serve
value is
true
. so I should get true in the app.
c
@breezy-activity-86410, we checked info. This is how we get rollout in the response. It is with name “coverage”(here we see percentage) and value true or false in the “force”.
var feature = gb?.features[featureFlag.id]; var rules = feature?.rules; rules?.forEach((rule) { var coverage = rule.coverage; if (coverage != null) { log(‘COVERAGE $coverage’); } });
Is that work for you?
b
@calm-dog-24239 I am checking. here is my init function where i am looping through features one by one.
Copy code
final featureFlags = <FeatureFlag, FeatureFlagResult<dynamic>>{
    for (final featureFlag in FeatureFlag.values)
      featureFlag: () {
        final result = _growthBook.feature(featureFlag.id);
        logInfo('Feature flag: ${featureFlag.id}, '
            'value: ${result.value}, source: ${result.source}');

        final experimentResult = (result.experiment != null &&
                result.experimentResult != null)
            ? ExperimentResult(
                experimentId: result.experiment!.key,
                variantId: result.experimentResult!.variationID!.toString(),
              )
            : null;

        return switch (featureFlag.type) {
          FeatureFlagType.bool => FeatureFlagResult<bool>(
              feature: featureFlag,
              value:
                  result.value as bool? ?? featureFlag.defaultValue as bool,
              experimentResult: experimentResult,
            ),
        };
      }(),
  };

  return Experiments(
    allFeatureFlags: featureFlags,
  );
}
@calm-dog-24239 in my result there is no coverage field.
c
tried to get it like this var feature = gb?.features[featureFlag.id]; var rules = feature?.rules; rules?.forEach((rule) { var coverage = rule.coverage; if (coverage != null) { log(‘COVERAGE $coverage’); } });
gb?.features[featureFlag.id]; you need to get it like this not gb?.features(featureFlag.id)
b
@calm-dog-24239 give me a moment, i am checking
👍 1
@calm-dog-24239 Amazing, I am getting force value as expected.
🙌 1
c
Nice, to hear that.
f
@calm-dog-24239 We still have issue with this, let me try and explain. We can get the default value We can get the coverage and forced value We can’t run A/B tests For A/B tests, the frontend should be “stupid”, i.e. just get the value from the backend. And frontend shouldn’t care if it came from hardcoded value or a split test. But this is not the case. Now, we can solve this by assigning values to users based on: default value, coverage, and forced value. But this is something that the GrowthBook should do under-the-hood, and just give us the actual value assigned to the user. Does that make sense? Are we missing something?
c
Hi, @future-salesclerk-97791. Let me check that and I will write to you.
👍 1
Also, it’s the way it works in all GrowthBook SDKs. If you want to make it simpler, you can use the latest feature, remote evaluation, which will provide you with the result. However, for that, you need your own host.
f
Thanks, then it’s all good @calm-dog-24239! We added the logic in the app. Just wanted to sure we aren’t missing anything.
b
@calm-dog-24239 we're very confused. Our understanding was GB would do the dice-roll and provide a value to our app. Is this not the case?
c
GrowthBook is designed to handle the logic of experiments, including the dice-roll mechanism to randomly assign values or variations. GB will provide a value (e.g., a variation or bucket) based on the dice-roll to your application. You just need to set up an experiment, add variations, and retrieve the assigned variations from the experiment.
b
@calm-dog-24239 what about feature-flag? We want to provide a 50/50 true/false value to our app
@calm-dog-24239 is it possible to do a quick call next week? We’re just about to cancel our subscription and use firebase remote config
c
Yes, we can setup a call on next week. What time and day will be better for you?
b
@calm-dog-24239 9am CET is typically a good time for us. What days next week are you available? What time zone are you in? Thanks so much! Cc: @future-salesclerk-97791 @breezy-activity-86410
👍 1
c
That time works for us too. It might be better to schedule it on Tuesday or Wednesday. Which day works better for you?
b
@calm-dog-24239 I'll DM you to confirm times
🙌 1
@calm-dog-24239 any update on this one? @fresh-football-47124 this is the issue I mentioned on LinkedIn Thanks folks, excited to make this work
c
Hi, @brief-continent-41453. As you mentioned before, the main idea is to divide users into two groups. After reviewing this, we understand that it is indeed possible. So, the main focus now is to find the best way to implement it, right?
b
Yup 👍 that’s right @calm-dog-24239
👍 1
@calm-dog-24239 can you intro us to any Flutter Devs who are using GB successfully to run feature flags?
c
Hi, @brief-continent-41453. Yes, okay, I will do that. Do you need any instructions for that, or would you like a call with the developer?
This is the result of our Flutter developer.