Skip to content

Commit

Permalink
Merge pull request #24 from growthbook/changelog-3.9.5
Browse files Browse the repository at this point in the history
changelog 3.9.5
  • Loading branch information
vazarkevych authored Sep 19, 2024
2 parents 8ea1fb9 + 32c957b commit 44ba844
Show file tree
Hide file tree
Showing 10 changed files with 115 additions and 41 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
# 3.9.5
- Add subscription logic
- Fix issue with null handling
- Fix hashing on web
- Update cache saving logic

# 3.9.4
- New Operators $inGroup and $notInGroup to check Saved Groups by reference
- Add argument to evalCondition for definition of Saved Groups
Expand Down
2 changes: 1 addition & 1 deletion lib/src/Evaluator/experiment_evaluator.dart
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ class ExperimentEvaluator {
minExperimentBucketVersion: experiment.minBucketVersion ?? 0,
meta: experiment.meta ?? [],
expHashAttribute: experiment.hashAttribute ?? "id",
expFallBackAttribute: experiment.fallbackAttribute!,
expFallBackAttribute: experiment.fallbackAttribute,
attributeOverrides: attributeOverrides,
);
foundStickyBucket = stickyBucketResult.variation >= 0;
Expand Down
35 changes: 25 additions & 10 deletions lib/src/Features/features_view_model.dart
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,8 @@ class FeatureViewModel {
} else {
String receivedDataJson = utf8Decoder.convert(receivedData);
final receiveFeatureJsonMap = json.decode(receivedDataJson);
Map<String, GBFeature> featureMap = {};

GBFeatures featureMap = {};
if (encryptionKey.isNotEmpty) {
receiveFeatureJsonMap.forEach((key, value) {
if (value is Map<String, dynamic>) {
Expand Down Expand Up @@ -126,12 +126,25 @@ class FeatureViewModel {
}

void handleValidFeatures(FeaturedDataModel data) {
if (data.features != null && data.features!.isNotEmpty) {
if (data.features != null && data.encryptedFeatures == null) {
delegate.featuresAPIModelSuccessfully(data);
String jsonString = json.encode(data.toJson());
final bytes = utf8Encoder.convert(jsonString);
manager.putData(fileName: Constant.featureCache, content: bytes);
delegate.featuresFetchedSuccessfully(gbFeatures: data.features!, isRemote: true);
final featureData = utf8Encoder.convert(jsonEncode(data.features));
final featureDataOnUint8List = Uint8List.fromList(featureData);
manager.putData(
fileName: Constant.featureCache,
content: featureDataOnUint8List,
);

if (data.savedGroups != null) {
delegate.savedGroupsFetchedSuccessfully(savedGroups: data.savedGroups!, isRemote: true);
final savedGroupsData = utf8Encoder.convert(jsonEncode(data.savedGroups));
final savedGroupsDataOnUint8List = Uint8List.fromList(savedGroupsData);
manager.putData(
fileName: Constant.savedGroupsCache,
content: savedGroupsDataOnUint8List,
);
}
} else {
if (data.encryptedFeatures != null) {
handleEncryptedFeatures(data.encryptedFeatures!);
Expand Down Expand Up @@ -161,7 +174,7 @@ class FeatureViewModel {
);

if (extractedFeatures != null) {
delegate.featuresFetchedSuccessfully(gbFeatures: extractedFeatures, isRemote: false);
delegate.featuresFetchedSuccessfully(gbFeatures: extractedFeatures, isRemote: true);
final featureData = utf8Encoder.convert(jsonEncode(extractedFeatures));
final featureDataOnUint8List = Uint8List.fromList(featureData);
manager.putData(
Expand Down Expand Up @@ -237,9 +250,11 @@ class FeatureViewModel {
}

void cacheFeatures(FeaturedDataModel data) {
String jsonString = json.encode(data.toJson());
final bytes = utf8Encoder.convert(jsonString);

manager.putData(fileName: Constant.featureCache, content: bytes);
final featureData = utf8Encoder.convert(jsonEncode(data.features));
final featureDataOnUint8List = Uint8List.fromList(featureData);
manager.putData(
fileName: Constant.featureCache,
content: featureDataOnUint8List,
);
}
}
4 changes: 2 additions & 2 deletions lib/src/Model/context.dart
Original file line number Diff line number Diff line change
Expand Up @@ -69,15 +69,15 @@ class GBContext {

String? getFeaturesURL() {
if (hostURL != null && apiKey != null) {
return '${hostURL}api/features/$apiKey';
return '${hostURL}/api/features/$apiKey';
} else {
return null;
}
}

String? getRemoteEvalUrl() {
if (hostURL != null && apiKey != null) {
return '${hostURL}api/eval/$apiKey';
return '${hostURL}/api/eval/$apiKey';
} else {
return null;
}
Expand Down
2 changes: 2 additions & 0 deletions lib/src/Utils/constant.dart
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ typedef GBStickyBucketingService = LocalStorageStickyBucketService;
/// A function that takes experiment and result as arguments.
typedef TrackingCallBack = void Function(GBExperiment, GBExperimentResult);

typedef ExperimentRunCallback = void Function(GBExperiment, GBExperimentResult);

typedef GBFeatureUsageCallback = void Function(String, GBFeatureResult);

typedef SavedGroupsValues = Map<String, dynamic>;
Expand Down
4 changes: 2 additions & 2 deletions lib/src/Utils/gb_utils.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ class FNV {
// Constants for FNV-1a 32-bit hash
final int init32 = 0x811c9dc5;
final int prime32 = 0x01000193;
final int mod32 = 1 << 32; // Equivalent to 2^32
final int mod32 = 0x100000000; // Equivalent to 2^32

/// Fowler-Noll-Vo hash - 32 bit
/// Returns an integer representing the hash.
Expand Down Expand Up @@ -470,7 +470,7 @@ class GBUtils {
required int minExperimentBucketVersion,
required List<GBVariationMeta> meta,
required String expHashAttribute,
required String expFallBackAttribute,
required String? expFallBackAttribute,
required Map<dynamic, dynamic> attributeOverrides,
}) {
// Get the assignment key for the given experiment key and version.
Expand Down
11 changes: 11 additions & 0 deletions lib/src/Utils/gb_variation_meta.dart
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,14 @@ class GBTrackData {

Map<String, dynamic> toJson() => _$GBTrackDataToJson(this);
}

@JsonSerializable()
class AssignedExperiment {
AssignedExperiment({
required this.experiment,
required this.experimentResult,
});

final GBExperiment experiment;
final GBExperimentResult experimentResult;
}
68 changes: 55 additions & 13 deletions lib/src/growth_book_sdk.dart
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import 'dart:async';
import 'dart:convert';
import 'dart:developer';

import 'package:growthbook_sdk_flutter/growthbook_sdk_flutter.dart';
import 'package:growthbook_sdk_flutter/src/Model/remote_eval_model.dart';
import 'package:growthbook_sdk_flutter/src/Model/sticky_assignments_document.dart';
import 'package:growthbook_sdk_flutter/src/StickyBucketService/sticky_bucket_service.dart';
import 'package:growthbook_sdk_flutter/src/Utils/crypto.dart';
import 'package:growthbook_sdk_flutter/src/Utils/gb_variation_meta.dart';

typedef VoidCallback = void Function();

Expand All @@ -28,10 +30,7 @@ class GBSDKBuilderApp {
this.stickyBucketService,
this.backgroundSync = false,
this.remoteEval = false,
}) : assert(
hostURL.endsWith('/'),
'Invalid host url: $hostURL. The hostUrl should be end with `/`, example: `https://example.growthbook.io/`',
);
});

final String apiKey;
final String? encryptionKey;
Expand Down Expand Up @@ -132,6 +131,10 @@ class GrowthBookSDK extends FeaturesFlowDelegate {

Map<String, dynamic> _attributeOverrides;

List<ExperimentRunCallback> subscriptions = [];

Map<String, AssignedExperiment> assigned = {};

/// The complete data regarding features & attributes etc.
GBContext get context => _context;

Expand All @@ -144,16 +147,20 @@ class GrowthBookSDK extends FeaturesFlowDelegate {
required bool isRemote,
}) {
_context.features = gbFeatures;
if (_refreshHandler != null) {
_refreshHandler!(true);
if (isRemote) {
if (_refreshHandler != null) {
_refreshHandler!(true);
}
}
}

@override
void featuresFetchFailed({required GBError? error, required bool isRemote}) {
_onInitializationFailure?.call(error);
if (_refreshHandler != null) {
_refreshHandler!(false);
if (isRemote) {
if (_refreshHandler != null) {
_refreshHandler!(false);
}
}
}

Expand All @@ -179,10 +186,40 @@ class GrowthBookSDK extends FeaturesFlowDelegate {
if (_context.remoteEval) {
refreshForRemoteEval();
} else {
log(context.getFeaturesURL().toString());
await featureViewModel.fetchFeatures(context.getFeaturesURL());
}
}

void fireSubscriptions(GBExperiment experiment, GBExperimentResult result) {
String key = experiment.key;

// If assigned variation has changed, fire subscriptions
if (assigned.containsKey(key)) {
var assignedExperiment = assigned[key];

if (assignedExperiment!.experimentResult.inExperiment != result.inExperiment ||
assignedExperiment.experimentResult.variationID != result.variationID) {
updateSubscriptions(key: key, experiment: experiment, result: result);
}
}
}

void updateSubscriptions({required String key, required GBExperiment experiment, required GBExperimentResult result}) {
assigned[key] = AssignedExperiment(experiment: experiment, experimentResult: result);
for (var subscription in subscriptions) {
subscription(experiment, result);
}
}

void subscribe(ExperimentRunCallback result) {
subscriptions.add(result);
}

void clearSubscriptions() {
subscriptions.clear();
}

GBFeatureResult feature(String id) {
return FeatureEvaluator(
attributeOverrides: _attributeOverrides,
Expand All @@ -192,7 +229,8 @@ class GrowthBookSDK extends FeaturesFlowDelegate {
}

GBExperimentResult run(GBExperiment experiment) {
return ExperimentEvaluator(attributeOverrides: _attributeOverrides).evaluateExperiment(context, experiment);
final result = ExperimentEvaluator(attributeOverrides: _attributeOverrides).evaluateExperiment(context, experiment);
return result;
}

Map<StickyAttributeKey, StickyAssignmentsDocument> getStickyBucketAssignmentDocs() {
Expand Down Expand Up @@ -285,16 +323,20 @@ class GrowthBookSDK extends FeaturesFlowDelegate {
@override
void savedGroupsFetchFailed({required GBError? error, required bool isRemote}) {
_onInitializationFailure?.call(error);
if (_refreshHandler != null) {
_refreshHandler!(false);
if (isRemote) {
if (_refreshHandler != null) {
_refreshHandler!(false);
}
}
}

@override
void savedGroupsFetchedSuccessfully({required SavedGroupsValues savedGroups, required bool isRemote}) {
_context.savedGroups = savedGroups;
if (_refreshHandler != null) {
_refreshHandler!(true);
if (isRemote) {
if (_refreshHandler != null) {
_refreshHandler!(true);
}
}
}
}
2 changes: 1 addition & 1 deletion pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: growthbook_sdk_flutter
description: An open-source feature flagging and experimentation platform that makes it simple to alter features and execute A/B testing.
version: 3.9.4
version: 3.9.5
homepage: https://github.com/alippo-com/GrowthBook-SDK-Flutter
repository: https://github.com/growthbook/growthbook-flutter

Expand Down
22 changes: 10 additions & 12 deletions test/common_test/sdk_builder_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ void main() {
group('Initialization', () {
const testApiKey = '<API_KEY>';
const attr = <String, String>{};
const testHostURL = 'https://example.growthbook.io/';
const testHostURL = 'https://example.growthbook.io';
const client = MockNetworkClient();

CachingManager manager = CachingManager();
Expand Down Expand Up @@ -66,18 +66,16 @@ void main() {
manager.clearCache();
});

test('- with initialization assertion cause of wrong host url', () async {
expect(
() => GBSDKBuilderApp(
apiKey: testApiKey,
hostURL: "https://example.growthbook.io",
client: client,
growthBookTrackingCallBack: (_, __) {},
backgroundSync: false,
),
throwsAssertionError,
test('- with initialization without throwing assertion error for wrong host url', () async {
final sdkInstance = GBSDKBuilderApp(
apiKey: testApiKey,
hostURL: testHostURL,
client: client,
growthBookTrackingCallBack: (_, __) {},
backgroundSync: false,
);
manager.clearCache();
expect(sdkInstance, isNotNull);
manager.clearCache();
});

test('- with network client', () async {
Expand Down

0 comments on commit 44ba844

Please sign in to comment.