Skip to content
Closed
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
aa53111
add deduplicate function
ABTastyAdel Jul 7, 2025
a4ceda6
wip
ABTastyAdel Jul 8, 2025
3424206
clean code and clean deduplication
ABTastyAdel Jul 8, 2025
f2bcdf6
clean code
ABTastyAdel Jul 8, 2025
60e58d2
add unit test
ABTastyAdel Jul 9, 2025
bc0b685
clean code
ABTastyAdel Jul 11, 2025
845dcf5
clean code
ABTastyAdel Jul 11, 2025
304e631
increment version
ABTastyAdel Jul 11, 2025
c8a8745
update version
ABTastyAdel Jul 28, 2025
3721121
post review
ABTastyAdel Jul 28, 2025
c1c38e3
clean
ABTastyAdel Oct 16, 2025
9f90059
manage lookup visitor
ABTastyAdel Oct 16, 2025
fec269b
Update lib/visitor.dart
ABTastyAdel Oct 16, 2025
e3ece89
Update lib/Storage/database_management.dart
ABTastyAdel Oct 16, 2025
4e5e276
manage cache in bucketing mode to handle the xcp
ABTastyAdel Oct 17, 2025
54a9674
Update lib/visitor.dart
ABTastyAdel Oct 17, 2025
5bfc9a1
unlock xpc for buckting mode
ABTastyAdel Oct 20, 2025
865e0b4
fix bux post QA
ABTastyAdel Oct 21, 2025
861e220
fix unit test
ABTastyAdel Nov 3, 2025
6f37262
fix unit test
ABTastyAdel Nov 3, 2025
721da60
fix build demo apk
ABTastyAdel Nov 3, 2025
cf99fd3
Merge pull request #56 from flagship-io/xpcBucketing
ABTastyAdel Nov 3, 2025
0afcfaf
Update lib/visitor/strategy/default_strategy.dart
ABTastyAdel Nov 3, 2025
957aaff
Update lib/cache/default_cache.dart
ABTastyAdel Nov 3, 2025
851f83c
Update test/activate_test.dart
ABTastyAdel Nov 3, 2025
def16af
Update example/lib/widgets/configuration.dart
ABTastyAdel Nov 3, 2025
508039d
Update lib/visitor/strategy/default_strategy.dart
ABTastyAdel Nov 3, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions example/ios/.vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "chrome",
"request": "launch",
"name": "Launch Chrome against localhost",
"url": "http://localhost:8080",
"webRoot": "${workspaceFolder}"
}
]
}
2 changes: 1 addition & 1 deletion example/lib/Providers/fs_data.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import 'package:flutter/material.dart';

class FSData extends ChangeNotifier {
// Apikey
String _apiKey = "apiKey"; //
String _apiKey = "DxAcxlnRB9yFBZYtLDue1q01dcXZCw6aM49CQB23"; //
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove the key

// EnvId
String _envId = "bkk9glocmjcg0vtmdlng"; //
// Mode
Expand Down
3 changes: 1 addition & 2 deletions example/lib/widgets/configuration.dart
Original file line number Diff line number Diff line change
Expand Up @@ -406,8 +406,7 @@ class _ConfigurationState extends State<Configuration> with ShowDialog {
GestureBinding.instance.pointerRouter
.addGlobalRoute(_emotionAIGlobalPointerRoute);
} catch (e) {
// Todo later add flagship logger
print(e);
Flagship.logger(Level.ERROR, e.toString());
}

Flagship.sharedInstance()
Expand Down
2 changes: 1 addition & 1 deletion example/pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ packages:
path: ".."
relative: true
source: path
version: "4.1.1-beta"
version: "4.1.2-beta"
flutter:
dependency: "direct main"
description: flutter
Expand Down
2 changes: 1 addition & 1 deletion lib/flagship_version.dart
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
/// This file is automatically updated
const FlagshipVersion = "4.1.2-beta";
const FlagshipVersion = "4.2.0-beta";
8 changes: 6 additions & 2 deletions lib/model/exposed_flag.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,11 @@ class ExposedFlag<T> implements IFlag {
final T _value;
// Metadata
final FlagMetadata _metadata;
// If flag is already activated
bool alreadyActivatedCampaign = false;

ExposedFlag(this._key, this._value, this._defaultValue, this._metadata);
ExposedFlag(this._key, this._value, this._defaultValue, this._metadata,
{bool alreadyActivatedCampaign = false});

T get value {
return _value;
Expand All @@ -37,7 +40,8 @@ class ExposedFlag<T> implements IFlag {
"key": this.key,
"value": this.value,
"defaultValue": this.defaultValue,
"metadata": this.metadata().toJson()
"metadata": this.metadata().toJson(),
"alreadyActivatedCampaign": this.alreadyActivatedCampaign
};
}
}
20 changes: 20 additions & 0 deletions lib/visitor.dart
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ enum Instance {
NEW_INSTANCE
}

const Duration FSSessionVisitor = Duration(seconds: 1 * 60 * 30); // 30 min

class Visitor with EmotionAiDelegate {
/// VisitorId
String visitorId;
Expand Down Expand Up @@ -77,6 +79,7 @@ class Visitor with EmotionAiDelegate {
Map<String, dynamic> assignmentsHistory = {};

/// Delegate visitor

late VisitorDelegate _visitorDelegate;

/// Delegate to update the status
Expand Down Expand Up @@ -137,6 +140,9 @@ class Visitor with EmotionAiDelegate {
}
}

// Init the sesssion
DateTime sessionDuration = DateTime.now();

// Create new instance for visitor
Visitor(
this.config,
Expand Down Expand Up @@ -208,11 +214,13 @@ class Visitor with EmotionAiDelegate {

// Update context directely with map for <String, Object>
void clearContext() {
sessionDuration = DateTime.now();
_context.clear();
}

// Update context directely with map for <String, Object>
void updateContextWithMap(Map<String, Object> context) {
sessionDuration = DateTime.now();
var oldContext = Map.fromEntries(_context.entries);
_context.addAll(context);
if (mapEquals(oldContext, _context) == false) {
Expand Down Expand Up @@ -241,6 +249,7 @@ class Visitor with EmotionAiDelegate {
/// otherwise the update context skip with warnning log

void updateContext<T>(String key, T value) {
sessionDuration = DateTime.now();
var oldContext = Map.fromEntries(_context.entries);

/// Delegate the action to strategy to update
Expand All @@ -259,6 +268,7 @@ class Visitor with EmotionAiDelegate {

/// Update with predefined context
void updateFlagshipContext<T>(FlagshipContext flagshipContext, T value) {
sessionDuration = DateTime.now();
if (FlagshipContextManager.chekcValidity(flagshipContext, value)) {
updateContext(rawValue(flagshipContext), value);
} else {
Expand All @@ -270,6 +280,7 @@ class Visitor with EmotionAiDelegate {
// Get Flag
// - Return Flag instance
Flag getFlag<T>(String key) {
sessionDuration = DateTime.now();
if (_flagSyncStatus != FlagSyncStatus.FLAGS_FETCHED) {
Flagship.logger(
Level.ALL, _flagSyncStatus.warningMessage(visitorId, key));
Expand All @@ -280,6 +291,7 @@ class Visitor with EmotionAiDelegate {
// Get the colllection flags
/// - Returns: an instance of FSFlagCollection with flags
FlagCollection getFlags() {
sessionDuration = DateTime.now();
Map<String, Flag> ret = {};

this.modifications.forEach((keyItem, modifItem) {
Expand All @@ -289,6 +301,8 @@ class Visitor with EmotionAiDelegate {
}

Future<void> fetchFlags() async {
sessionDuration = DateTime.now();

/// Delegate the action to strategy
this.flagStatus = FlagStatus.FETCHING;
return _visitorDelegate.fetchFlags().then((fetchResponse) {
Expand All @@ -308,12 +322,14 @@ class Visitor with EmotionAiDelegate {

/// Send hit
Future<void> sendHit(BaseHit hit) async {
sessionDuration = DateTime.now();
// Delegate the action to strategy
_visitorDelegate.sendHit(hit);
}

/// Set Consent
void setConsent(bool newValue) {
sessionDuration = DateTime.now();
// flush the hits from the pool
if (newValue == false) {
this.trackingManager?.flushAllTracking(this.visitorId);
Expand Down Expand Up @@ -343,6 +359,7 @@ class Visitor with EmotionAiDelegate {
/// - Requires: Make sure that the experience continuity option is enabled on the flagship platform before using this method

authenticate(String visitorId) {
sessionDuration = DateTime.now();
_isAuthenticated = true;
_visitorDelegate.getStrategy().authenticateVisitor(visitorId);
this.flagStatus = FlagStatus.FETCH_REQUIRED;
Expand All @@ -353,6 +370,7 @@ class Visitor with EmotionAiDelegate {

/// Use authenticate methode to go from Logged in session to logged out session
unauthenticate() {
sessionDuration = DateTime.now();
_isAuthenticated = false;
_visitorDelegate.getStrategy().unAuthenticateVisitor();
this.flagStatus = FlagStatus.FETCH_REQUIRED;
Expand All @@ -368,11 +386,13 @@ class Visitor with EmotionAiDelegate {

@visibleForTesting
FlagSyncStatus getFlagSyncStatus() {
sessionDuration = DateTime.now();
return _flagSyncStatus;
}

// Add emotionAI function
collectEmotionsAIEvents(String screenName) {
sessionDuration = DateTime.now();
if (Flagship.sharedInstance().eaiCollectEnabled == true) {
if (eaiVisitorScored == true) {
Flagship.logger(Level.INFO,
Expand Down
2 changes: 1 addition & 1 deletion lib/visitor/Ivisitor.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ abstract class IVisitor {
// Fetch Flags
Future<void> fetchFlags();
// Activate modification
Future<void> activateModification(String key);
// Future<void> activateModification(String key);
// Activate flag
Future<void> activateFlag(Modification pModification);
// Send Hits
Expand Down
124 changes: 73 additions & 51 deletions lib/visitor/strategy/default_strategy.dart
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ import 'package:flagship/visitor/Ivisitor.dart';
// This class represent the default behaviour
class DefaultStrategy implements IVisitor {
final Visitor visitor;

DefaultStrategy(this.visitor);

@override
Expand All @@ -39,65 +38,85 @@ class DefaultStrategy implements IVisitor {
}
}

// Activate
Future<void> _sendActivate(Modification pModification) async {
// Check if the callback is defined
String? exposedFlag;
String? exposedVisitor;
if (Flagship.sharedInstance().getConfiguration()?.onVisitorExposed !=
null) {
exposedFlag = jsonEncode(ExposedFlag(
pModification.key,
pModification.value,
pModification.defaultValue,
FlagMetadata.withMap(pModification.toJsonInformation()))
.toJson());

exposedVisitor = jsonEncode(VisitorExposed(
visitor.visitorId, visitor.anonymousId, visitor.getContext())
.toJson());
}
// Build the activate hit
Activate activateHit = Activate(
pModification,
Future<void> _sendActivate(
Modification modification,
bool isDuplicated,
) async {
// Get config and callback
final config = Flagship.sharedInstance().getConfiguration();
final onExposed = config?.onVisitorExposed;

// Prepare exposure object
ExposedFlag? exposedFlag;
VisitorExposed? exposedVisitor;
if (onExposed != null) {
exposedFlag = ExposedFlag(
modification.key,
modification.value,
modification.defaultValue,
FlagMetadata.withMap(modification.toJsonInformation()),
);
exposedVisitor = VisitorExposed(
visitor.visitorId,
visitor.anonymousId,
Flagship.sharedInstance().envId ?? "",
exposedFlag,
exposedVisitor);
// Process the troubleShooting
DataUsageTracking.sharedInstance().processTroubleShootingHits(
CriticalPoints.VISITOR_SEND_ACTIVATE.name, visitor, activateHit);
visitor.trackingManager?.sendActivate(activateHit).then((activateResponse) {
if (activateResponse.statusCode >= 200 &&
activateResponse.statusCode < 300) {
} else {
Flagship.logger(
Level.ERROR,
ACTIVATE_FAILED +
" status code = ${activateResponse.statusCode.toString()}");
visitor.getContext(),
);
}

// When deduplicated
if (isDuplicated) {
if (onExposed != null && exposedFlag != null && exposedVisitor != null) {
exposedFlag.alreadyActivatedCampaign = true;
onExposed(exposedVisitor, exposedFlag);
}
});
}
Flagship.logger(Level.INFO, " The camapign's flag already activated ");
return;
}

@override
Future<void> activateModification(String key) async {
if (visitor.modifications.containsKey(key)) {
try {
var modification = visitor.modifications[key];
// When not duplicated
final String? flagJson =
exposedFlag != null ? jsonEncode(exposedFlag) : null;
final String? visitorJson =
exposedVisitor != null ? jsonEncode(exposedVisitor) : null;

final activateHit = Activate(
modification,
visitor.visitorId,
visitor.anonymousId,
Flagship.sharedInstance().envId ?? '',
flagJson,
visitorJson,
);

// Send troubleshooting
DataUsageTracking.sharedInstance().processTroubleShootingHits(
CriticalPoints.VISITOR_SEND_ACTIVATE.name,
visitor,
activateHit,
);

if (modification != null) {
await _sendActivate(modification);
}
} catch (exp) {
Flagship.logger(Level.EXCEPTIONS, EXCEPTION.replaceFirst("%s", "$exp"));
// Send Activate hit
try {
final response = await visitor.trackingManager?.sendActivate(activateHit);
final status = response?.statusCode ?? -1;
if (status < 200 || status >= 300) {
Flagship.logger(
Level.ERROR,
'ACTIVATE_FAILED: status code = $status',
);
}
} catch (e, stack) {
Flagship.logger(
Level.ERROR,
'ACTIVATE_FAILED: exception = $e\n$stack',
);
}
}

@override
Future<void> activateFlag(Modification pModification) async {
return _sendActivate(pModification);
Future<void> activateFlag(Modification pModification,
{bool isDuplicated = false}) async {
return _sendActivate(pModification, isDuplicated);
}

@override
Expand Down Expand Up @@ -147,7 +166,10 @@ class DefaultStrategy implements IVisitor {
}
if (activate && hasSameType) {
// Send activate later
_sendActivate(modification);

this._sendActivate(modification, false);

/// Attention a remove cette partie ou a revoir
}
} catch (exp) {
Flagship.logger(Level.INFO,
Expand Down
9 changes: 5 additions & 4 deletions lib/visitor/strategy/no_consent_strategy.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,13 @@ class NoConsentStrategy extends DefaultStrategy {

// The activate modification is not allowed
@override
Future<void> activateModification(String key) async {
Flagship.logger(Level.INFO, CONSENT_ACTIVATE);
}
// Future<void> activateModification(String key) async {
// Flagship.logger(Level.INFO, CONSENT_ACTIVATE);
// }

@override
Future<void> activateFlag(Modification pFlag) async {
Future<void> activateFlag(Modification pFlag,
{bool isDuplicated = false}) async {
Flagship.logger(Level.INFO, CONSENT_ACTIVATE);
}

Expand Down
8 changes: 2 additions & 6 deletions lib/visitor/strategy/not_ready_strategy.dart
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,8 @@ class NotReadyStrategy extends DefaultStrategy {
}

@override
Future<void> activateModification(String key) async {
Flagship.logger(Level.ERROR, ACTIVATE_NOT_READY);
}

@override
Future<void> activateFlag(Modification pFlag) async {
Future<void> activateFlag(Modification pFlag,
{bool isDuplicated = false}) async {
Flagship.logger(Level.ERROR, ACTIVATE_NOT_READY);
}

Expand Down
Loading
Loading