Flutter V2.0.X

Introduction

SDK overview

Welcome to the Flagship Flutter SDK documentation!

The following documentation helps you run Flagship on your Flutter app using our client library.

Feel free to contact us if you have any questions regarding this documentation.

SDK features

This SDK version will help you:

Prequisites

  • Flagship SDK supports Dart 2.12.0 minimum or higher
  • Flagship is null safety

Good to know

Getting Started

Our Flagship flutter SDK is available for distribution through pub.dev

Installing

Run this command:

flutter pub add flagship

This will add a line like this to your package's pubspec.yaml (and run an implicit dart pub get):

dependencies:
  flagship: ^2.0.0

Initialization

To run experiments with Flagship, you will need to start the SDK using:

start(String envId, String apiKey, {FlagshipConfig? config})

ParameterTypeRequiredDescription
envIdStringYesIdentifies your Flagship account and environment (preprod or prod). Find this ID
apiKeyStringYesIdentifies your Flagship api key (preprod or prod). Find this api key
configFlagshipConfigNoObject that represent client's configuration

📘

You can get your own envId & API Key in the Flagship Platform.

  1. Navigate to SettingsAPI Key & Settings
  2. Copy the environment ID & API Key
// import package
import 'package:flagship/flagship.dart';
import 'package:flagship/utils/logger/log_manager.dart';

//////////////////////////////////////////////
/////// Start sdk with default options ///////
//////////////////////////////////////////////

Flagship.start("your_env_id", "your_api_key");

Flagship configuration options

The sdk provides the FlagshipConfig object in the start function as an optional parameter where we can set the desired configuration

Decision Mode :

  • ConfigBuilder withMode(Mode newMode)

DECISION_API

When the SDK is running in DecisionApi mode, the campaign assignments and targeting validation take place server-side. In this mode, each call to the fetchFlags method to refresh the flags will create an HTTP request.

BUCKETING

When the SDK is running in BUCKETING mode, the SDK downloads all the campaigns configurations at once in a single bucketing file so that variation assignment can be computed client-side by the SDK. This bucketing file is stored in cache and will only be downloaded again when campaign configurations are modified in the Flagship interface.

Timeout :
This delay only concerns the request for fetching campaigns under the API mode. If the API didn't answer during this interval of time, the default values will be returned from the getModification function.

  • ConfigBuilder withTimeout(int newTimeout)
ParameterTypeDescription
newTimeoutintMilliseconds, default value is 2000 ms

statusListener :

This listener is called when SDK status has changed.

  • ConfigBuilder withStatusListener(StatusListener listener)
ParameterTypeDescription
listenerStatusListenerIndicate the actual status of the sdk, this listener is called when the status change

logLevel :

ConfigBuilder withLogLevel(Level newLevel)

ParameterTypeDescription
newLevelLevelLevel of detail, default value is Level.ALL

You can change log's level of detail during the runtime void setLoggerLevel(Level newLevel)

Polling Interval Time :

Only available for Bucketing Mode:

Define time interval between two bucketing updates.

  • ConfigBuilder withBucketingPollingIntervals(int newPollingTime)
ParameterTypeDescription
newPollingTimeinttime interval between two bucketing updates, default value is 60s
//////////////////////////////////////////////
/////// Start SDK with custom options  ///////
//////////////////////////////////////////////

// - timeout   = 1500 ms
// - level     = warning message
// - statusListener callback

FlagshipConfig customConfig = ConfigBuilder()
    .withMode(Mode.DECISION_API)
    .withStatusListener((newStatus) {
      // Do things when status change ...
    })
    .withTimeout(1500)
    .withLogLevel(Level.WARNING)
    .build();
    
// Start SDK
Flagship.start("your_env_id", "your_api_key", config: customConfig);

SDK Status

It is possible to get the current status via the method getStatus() from the Flagship class.

  • static Status getStatus()

Return an enum that represent the current SDK status

List of the possible SDK status :

StatusDescription
NOT_INITIALIZED Flagship SDK has not been started or initialized successfully.
PANIC_ON Flagship SDK is ready but is running in Panic mode: All visitor's features are disabled except 'fetchFlags' which refreshes this status.
READY Flagship SDK is ready to use.
// Get the current status for the SDK
   var currentStatus = Flagship.getStatus();

// Using getStatus to check if the SDK is READY

// Create the visitor 
   var visitor = Flagship.newVisitor("user1",{"isVip":true});

// Fetch Flags the modifications 
   visitor.fetchFlags().whenComplete(() {
    
// Get the status and check the mode
   Status currentState =  Flagship.getStatus();
    if (currentState == Status.READY) {
      // The SDK is ready
    }
  });

Create a visitor

Once Flagship SDK is initialized, you can create a visitor.

The Visitor instance is an object that lets you manage the context, activate experiments and track events.

  • VisitorBuilder(this.visitorId, {this.instanceType = Instance.SINGLE_INSTANCE});
ParameterTypeDescription
visitorIdStringUnique visitor identifier
instanceTypeInstanceHow Flagship SDK should handle the newly created visitor instance. (Default is SINGLE_INSTANCE)

SINGLE_INSTANCE :
The newly created visitor instance will be returned and saved into the Flagship singleton. Call Flagship.getCurrentVisitor() to retrieve the instance.

NEW_INSTANCE:
The newly created visitor instance wont be saved and will simply be returned.

Visitor Builder methods:

Represent the visitor's initial context key/values used for targeting.

Context keys must be String, and values types must be one of the following: String, bool, int, double, Boolean, String.

  • VisitorBuilder withContext(Map<String, Object> context)
ParameterTypeDescription
contextMap<String, Object>initial context
  • VisitorBuilder hasConsented(bool hasConsented)

Specify if the visitor has consented to personal data usage. When false some features will be deactivated. The default value is true.

ParameterTypeDescription
hasConsentedbooltrue when user has given consent, false otherwise.
  • isAuthenticated(bool authenticated)

The visitorId will be considered as authenticated if true otherwise is anonymous.

ParameterTypeDescription
authenticatedbooltrue for authenticated user, false for anonymous.
// import package
import 'package:flagship/flagship.dart';

//////////////////////////////////////////////
///////        Create visitor          ///////
//////////////////////////////////////////////

Visitor visitor1 =
      Flagship.newVisitor("visitor_1")
              .withContext({"isVip": true})
              .hasConsented(true)
              .isAuthenticated(true).build();

Get & Set the current visitor

Most of the time, your Flutter application will handle only 1 visitor at any given time.
For that use case, Flagship implement 2 useful methods to store the created visitor as the current singleton visitor instance.

static setCurrentVisitor(Visitor visitor)

Stores the current visitor instance created at the newVisitor.

// import package
import 'package:flagship/flagship.dart';

//////////////////////////////////////////
/////// set the current visitor //////////
//////////////////////////////////////////

// Create not shared instance visitor
Visitor newVisitor = Flagship.newVisitor("visitor_1", instanceType: Instance.NEW_INSTANCE)
    .withContext({"isVip": true})
    .hasConsented(true)
    .isAuthenticated(true)
    .build();

// Set as current visitor
Flagship.setCurrentVisitor(newVisitor);

static Visitor? getCurrentVisitor()

Return the current visitor instance stored when setting the current visitor.

// import package
import 'package:flagship/flagship.dart';

//////////////////////////////////////////
/////// Get the current visitor //////////
//////////////////////////////////////////

var visitor = Flagship.getCurrentVisitor();

🚧

This method returns a nullable Visitor (in case the current visitor has not been previously set).
Make sure to check whether it is null or not before using it.

Updating the visitor context

The visitor context is a property dataset that defines the current visitor of your app. This dataset is sent and used by the Flagship Decision API as targeting criteria for campaign assignment.

The following method from the Visitor instance allows you to set new context values matching the given keys.

void updateContext(String key, Object value)

Update the visitor context values, matching the given keys, used for targeting. Only String, bool, int, double typed values are accepted.

ParameterTypeDescription
keyStringContext key.
valueObjectContext value.

void updateContextWithMap(Map<String, Object> context)

Update the visitor context values, matching the given keys, used for targeting. Only String, bool, double and int type values are accepted.

ParameterTypeDescription
contextMap<String, Object>Map of keys, values.

📘

  • User context keys must have a type of String
    • User context values must have a type of String, bool, double or int
    • User context keys and values are case sensitive

void updateFlagshipContext<T>(FlagshipContext flagshipContext, T value)

Update the context with a predefined context see FlagshipContext

ParameterTypeDescription
flagshipContextFlagshipContextPredefined context key, see FlagshipContext
valueTvalue of the associated Predefined key
// import package
import 'package:flagship/flagship.dart';

//////////////////////////////
/////// Update context ///////
//////////////////////////////

var currentVisitor = Flagship.getCurrentVisitor();

// Update context with key/value
currentVisitor?.updateContext("lastPurchaseDate", 1615384464);

// Update context with Map
currentVisitor?.updateContextWithMap({"isVip": true, "key1": 12.5, "key2": "title", "key3": 2});

// Update the location country
currentVisitor?.updateFlagshipContext(FlagshipContext.LOCATION_COUNTRY, "FRANCE");

// Update the carrier name
currentVisitor?.updateFlagshipContext(FlagshipContext.CARRIER_NAME, "SFR");

void clearContext()

Empty the visitor context values, used for targeting.

// Import package
import 'package:flagship/flagship.dart';

/////////////////////////////
/////// Clear context ///////
/////////////////////////////

var currentVisitor = Flagship.getCurrentVisitor();

currentVisitor?.clearContext();

Managing visitor campaigns and theirs flags

Fetching Flags

The fetchFlags() method of the visitor instance automatically calls the Flagship Decision API to run campaign assignments according to the current visitor context and retrieve applicable flags.

These flags are updated asynchronously when fetchFlags() is called.

/////////////////////////////////////////
///////     Fetch Flags           ///////
/////////////////////////////////////////

import 'package:flagship/flagship.dart';


// Get the current visitor
var currentVisitor = Flagship.getCurrentVisitor();
// Fetch Flags
currentVisitor?.fetchFlags().whenComplete(() {
  // The fetch is done.
  // If the panic mode is ON or the fetch fail for another reason
  // the value for flag will be a default value
});

Future<void> fetchFlags() async

This function will call the decision api and update all the campaigns flags from the server according to the visitor context.

Getting flags

Once the campaign has been assigned and fetched, all the flags are stored in the SDK.
You can retrieve these flags using the following functions from the Visitor instance:

////////////////////////////////
///////    Get Flag      ///////
////////////////////////////////

import 'package:flagship/flagship.dart';
import 'package:flagship/model/flag.dart';


// Get the current visitor
    var currentVisitor = Flagship.getCurrentVisitor();

// Fetch flags
    currentVisitor?.fetchFlags().whenComplete(() {
      // Ex: get flag for vip feature
      Flag flag = currentVisitor.getFlag("displayVipFeature",false);
    });

// The getFlag function can be directly called through the "currentVisitor" instance if you
// already fetched it elsewhere in the application

Flag getFlag<T>(String key, T defaultValue)

Retrieve a Flag object by its key. If no flag match the given key an empty flag will be returned. Call exists() to check if the flag has been found.

ParameterTypeRequiredDescription
keyStringYesKey associated to the modification.
defaultValueTYesFlag default value to return.

Getting flags current values

To retrieve flag current value, simply call value() method of the Flag object.

  • T value({bool userExposed: true})
    Returns the value from the assigned campaign variation or the Flag default value if the Flag does not exist, or if types are different.
ParameterTypeDefault valueDescription
userExposedbooltrueTells Flagship the user have been exposed and have seen this flag. This will increment the visits for the current variation on your campaign reporting.
If needed it is possible to set this param to false and call userExposed() afterward when the user has really been exposed to it.
import 'package:flagship/flagship.dart';
import 'package:flagship/model/flag.dart';

// Get the current visitor
    var currentVisitor = Flagship.getCurrentVisitor();

// Fetch flags
    currentVisitor?.fetchFlags().whenComplete(() {
      // Ex: get flag for vip feature
      Flag flag = currentVisitor.getFlag("displayVipFeature", false);

      // Use this flag value to enable displaying the vip feature
      bool shouldDisplayVipFeature = flag.value();
    });

Getting flags campaigns metadata

You may need to send campaign's informations to a third-party for reporting and/or analytics purposes. The metadata method returns a dictionary with values you can use.

  • FlagMetadata metadata()
    Return the campaign metadata or an empty object if the Flag doesn't exist or if the default value type does not correspond to the Flag type in Flagship.
import 'package:flagship/flagship.dart';
import 'package:flagship/model/flag.dart';

// Get the current visitor
    var currentVisitor = Flagship.getCurrentVisitor();

// Fetch flags
    currentVisitor?.fetchFlags().whenComplete(() {
      // Ex: get flag for vip feature
      Flag flag = currentVisitor.getFlag("displayVipFeature", false);

      // Use this flag value to enable displaying the vip feature
      bool shouldDisplayVipFeature = flag.value();
      
      // Use this flag to get the metadata      
        FlagMetadata metadata = flag.metadata();

    });

The metadata you can access to are the following one:

FSFlagMetadataTypeDefault valueDescription
campaignIdString""id of the campaign
variationGroupIdString""Id of the variation group
variationIdString""id of the variation assigned
isReferenceboolfalseif true that means the assigned variation is the reference, otherwise it's not.
campaignTypeString""Type of the campaign. Ex: AB
slugString""campaign slug or empty string if not configured in the platform

Report a Flag exposition

By default when the method value() is called, the SDK considers that the user has seen your Flag unless you pass false to value(). In this last case, you will have to call the userExposed() method.

There are two options for exposing a user to a flag:

  • Pass an userExposed=true parameter to the value() method.
  • Use the following userExposed() method from the Flag instance.
  • Future<void> userExposed() async
// Get the current visitor
    var currentVisitor = Flagship.getCurrentVisitor();

// Fetch flags
    currentVisitor?.fetchFlags().whenComplete(() {
      // Ex: get flag for vip feature
      Flag flag = currentVisitor.getFlag("displayVipFeature", false);

      // Use this flag value to enable displaying the vip feature with expose to false
      bool shouldDisplayVipFeature = flag.value(userExposed: false);

      // Expose this flag later in the code
      flag.userExposed();
    });

Check if a Flag exists

  • bool exists()
    This method will return true if a Flag has been returned by Flagship.
// Get the current visitor
    var currentVisitor = Flagship.getCurrentVisitor();

// Fetch flags
    currentVisitor?.fetchFlags().whenComplete(() {
      
     // Ex: get flag for vip feature and check if it exists
      bool isDisplayVipFeatureExists = currentVisitor.getFlag("do_not_exists", false).exists();
    });
  }

Managing visitor consent

The Visitor class provides a method to let you manage visitor consent for data privacy usage. When false the activation and hits will be disabled.

//////////////////////////////////////
/////// Manage visitor consent ///////
//////////////////////////////////////

// Create visitor with no consent
var visitor = Flagship.newVisitor(visitorIdController.text, visitorContext, hasConsented:false);
// Set the consent to true on run time
visitor.setConsent(true);

Experience Continuity

Dealing with anonymous and logged-in users, experience continuity allows you to maintain consistency between sessions and devices.

🚧

Make sure that the experience continuity option is enabled on the flagship platform before using those methods.

Authenticate

There are 2 ways to authenticate a visitor:

  1. Set isAuthenticated to true when creating a new visitor
  2. Use authenticate method of Visitor instance
    Authenticate anonymous visitor
  • authenticateVisitor(String pVisitorId)
ParameterTypeDescription
pVisitorIdStringid of the new authenticated visitor.

🚧

Because we have changed the visitor data, we have to call the fetchFlags method after calling this one to update the decision from Flagship.

The targeting / Flags could be different for the visitor.

Unauthenticate

This function change authenticated Visitor to anonymous visitor

  • unAuthenticateVisitor()

🚧

Because we have changed the visitor datas, we have to call the fetchFlags method after calling this one to update the decision from Flagship.

The targeting / Flags could be different for the visitor.

code Example

Let's assume basic scenario to understand how things work:

  1. Your visitor arrives on your app for the first time.

We need to initialize the visitor but as we don't know anything about this visitor, we'll create a random_Id. You can also specify some visitor context if necessary.

// Create visitor with random_Id
   Visitor visitor = Flagship.newVisitor("random_Id").withContext({"isVip": true}).build();

// Fetch flags
  visitor.fetchFlags().whenComplete(() {
    // .. Do things
  });

The actual random_Id will be what we call the anonymous id.

  1. Your visitor is signing in.

To tell the SDK about this status modification, you'll have to call the authenticate function which takes the required visitor id as argument.

// Example
// You fetch the visitor_id from your DB
// let visitorId = db.getUserId();

// Authenticate
   visitor.authenticate("visitorId");

// Since your visitor has changed (is now logged-in)
// You have to check if the proper targeting and flags are set

  visitor.fetchFlags().whenComplete(() {
    // .. Do things
  });

The visitor is updated as authenticated, keeping the previous variations from campaigns that are still matched and thus gives you same flags as before being logged in.

  1. Your visitor decides to sign out.

If you want to keep the same visitor experience, then you should do:

// Unauthenticate

  visitor.unauthenticate();

// Since your visitor has changed (is now logged-out)
// You have to check if the proper targeting and flags are set

  visitor.fetchFlags().whenComplete(() {
    // .. Do things
  });

Final implementation example

// Create a visitor

    Visitor visitor = Flagship.newVisitor("random_Id").withContext({"isVip": true}).build();

// Fetch flags

    visitor.fetchFlags().whenComplete(() {
      // ... Do things ....
    });


// You fetch the visitor_id from your DB
// let visitorId = db.getUserId();

// Call the authenticate function

    visitor.authenticate("visitorId");

// Fetch the flags to update the visitor decision

    visitor.fetchFlags().whenComplete(() {
      // ... Do things ....
    });

// If you want to unauthenticate the visitor

    visitor.unauthenticate();

// Fetch the flags to update the visitor decision

    visitor.fetchFlags().whenComplete(() {
      // ... Do things ....
    });

Hit Tracking

This section helps send tracking and learn how to build hits in order to track campaign goals.

The different types of Hits are:

They must all be built and sent with the following method of the visitor instance:

Future<void> SendHit(hit HitProtocol)

Common parameters

These parameters can be sent with any type of hit.

ParameterTypeDescription
userIpStringoptional User IP
screenResolutionStringoptional Screen Resolution.
userLanguageStringoptional User Language
sessionNumberintoptional Session Number

Hit types

Screen

This hit should be sent each time a visitor arrives on a new interface.

Hit parameterTypeRequiredDescription
locationStringYesName of the screen
//////////////////////////
/////// Screen hit ///////
//////////////////////////

var visitor = Flagship.getCurrentVisitor();
// Send screen hit 
visitor?.sendHit(Screen(location: "My page"));

Transaction

This hit should be sent when a user complete a Transaction.

Hit ParameterTypeRequiredDescription
transactionIdStringYesTransaction unique identifier.
affiliationStringYesTransaction name. Name of the goal in the reporting.
revenuedouble?NoTotal revenue associated with the transaction. This value should include any shipping or tax costs.
shippingdouble?NoSpecifies the total shipping cost of the transaction.
shippingMethodString?NoSpecifies the shipping method of the transaction.
taxdouble?NoSpecifies the total taxes of the transaction.
currencyString?NoSpecifies the currency used for all transaction currency values. Value should be a valid ISO 4217 currency code.
paymentMethodString?NoSpecifies the payment method for the transaction.
itemCountint?NoSpecifies the number of items for the transaction.
couponCodeString?NoSpecifies the coupon code used by the customer for the transaction.
///////////////////////////////
/////// Transaction hit ///////
///////////////////////////////

var visitor = Flagship.getCurrentVisitor();
visitor?.sendHit(Transaction(
  transactionId   : "YOUR_TRANSACTION_ID",
  affiliation     : "GOAL_NAME", // The goal name set in Flagship campaign
  revenue         : 100,
  shipping        : 10,
  tax             : 5,
  currency        : "EUR",
  couponCode      : "discount",
  paymentMethod   : "Card",
  shippingMethod  : "postal",
  itemCount       : 2,
));

Item

This hit is linked to a transaction. It must be sent after the corresponding transaction.

Hit ParameterTypeRequired Description
transactionIdStringYesTransaction unique identifier.
nameStringYesProduct name.
codeStringYesSpecifies the item code or SKU.
pricedouble?NoSpecifies the item price.
categoryString?NoSpecifies the item category.
quantityint?NoSpecifies the item quantity
////////////////////////
/////// Item hit ///////
////////////////////////

var visitor = Flagship.getCurrentVisitor();
visitor?.sendHit(Item(
  transactionId : "YOUR_TRANSACTION_ID",
  name                  : "item name",
  code                  : "item code",
  price                 : 10.5,
  quantity          : 5,
  category          : "item category"
));

Event

This hit can be anything you want: for example a click or a newsletter subscription.

Hit ParameterTypeRequired Description
CategoryCategoryEventYesCategory of the event ("Action_Tracking" or "User_Engagement").
ActionStringYesThe event action. Should match the goal name of the campaign
LabelStringNoLabel of the event.
ValueintNoSpecifies a value for this event. must be non-negative.
/////////////////////////
/////// Event hit ///////
/////////////////////////

var visitor = Flagship.getCurrentVisitor();
visitor?.sendHit(Event(
  action        : "Event action", // The name of the goal defined in your campaign
  category  : CategoryEvent.Action_Tracking,
  label         : "custom label",
  value         : 2
));

Appendix

Predefined user context keys

The Flagship SDK contains predefined user context keys.

The keys marked as Yes in the Auto-set by SDK column will be automatically set, while the ones marked as No need to be set by the client.

They are nevertheless overridable at anytime. Then these predefined context keys-value pairs will be sent to the server and be editable in the Persona section of the Flagship platform.

SDK Variable nameDescriptionContext Variable nameTypeAuto-set by SDKExample
FIRST_TIME_INITFirst init of the appsdk_firstTimeInitboolYtrue
DEVICE_LOCALLanguage of the devicesdk_deviceLanguageStringYesfr_FR
DEVICE_TYPEType fo the devicesdk_deviceTypeStringYesmobile
LOCATION_CITYCity geolocationsdk_cityStringNoParis
LOCATION_REGIONRegion geolocationsdk_regionStringNoile de france
LOCATION_COUNTRYCountry geolocationsdk_countryStringNoFrance
LOCATION_LATCurrent Latitudesdk_latDoubleNo43.623647
LOCATION_LONGCurrent Longitudesdk_longDoubleNo1.445397
IPIP of the devicesdk_ipStringNo127.0.0.1
OS_NAMEName of the OSsdk_osNameStringYesiOS / macOS
OS_VERSION_CODEVersion of OSsdk_osVersionCodeStringYes9.0
MVNO / carrierNameMobile virtual network operatorsdk_carrierNameStringNoorange
DEV_MODEIs the app in debug mode?sdk_devModeboolNotrue
INTERNET_CONNECTIONWhat is the internet connectionsdk_internetConnectionStringNo4G
APP_VERSION_NAMEVersion name of the appsdk_versionNameStringNo1.1.2-beta
APP_VERSION_CODEVersion code of the appsdk_versionCodeintNo40
FLAGSHIP_VERSIONVersion of the Flagship SDKsdk_fsVersionStringYes3.0
INTERFACE_NAMEName of the interfacesdk_interfaceNameStringNoProductPage

Here you can see how a predefined key is used to filter a report in the Flagship interface:

23842384