iOS V3.1.X
Introduction
SDK overview
Welcome to the Flagship iOS SDK documentation!
The following documentation helps you to run Flagship on your native iOS app using our client library with preconfigured methods to implement the Decision API.
Feel free to contact us if you have any questions regarding this documentation.
SDK features
This SDK version will help you:
- Set a visitor ID
- Update user context
- Assign campaigns via the Decision API or Bucketing
- Get flags
- Expose campaigns
- Send hits to our Universal Collect
Prequisites
- Your app must be a native app written in Swift or Objective C.
- Swift Client application must use Swift 5 or higher
Good to know
- Github repository
- Swift and Objective-C code supported
- Compatible with SwiftUI
Supported platforms
Platform | Version |
---|---|
iOS | 11.0 |
tvOS | 11.0 |
macOS | 10.13 |
watchOS | 5.0 |
Getting Started
Our Flagship SDK is available for distribution through CocoaPods, Swift Package Manager, Carthage or manual installation.
Cocoapods
-
Open Terminal and browse to the directory that contains your project then, enter the following command:
pod init
-
Open your Podfile and add the following line to the Podfile
target 'Your App' do
use_frameworks!
pod 'FlagShip'
end
-
Run
pod install
from your Xcode project's base directory -
Make sure you always open the Xcode workspace and not the project file when building your project
Swift Package Manager (SPM)
You can search for Flagship package on GitHub.
Add your GitHub or GitHub Enterprise account in Xcode’s preferences, and a package repository appear as you type.
For more information about Swift Package Manager, refer to Apple documentation.
Carthage
-
Install Carthage
-
Create a Cartfile in the same directory where your .xcodeproj or .xcworkspace is.
-
Add this line to the Cartfile
github "flagship-io/flagship-ios" ~> 3.1.0
-
Run
carthage update --use-xcframeworks
-
A Cartfile.resolved file and a Carthage directory will appear in the same directory where your .xcodeproj or .xcworkspace is.
-
Drag the built Flagship.xcframework bundles from Carthage/Build into the "Frameworks, Libraries, and Embedded Content" section of your application’s Xcode project.
For More information refer to Carthage
Initialization
To run experiments with Flagship, you will need to start the SDK.
Flagship uses a sharedInstance
then create a visitor to activate experiments and track events.
func start(envId:String, apiKey:String, config:FSConfig)
Parameter | Type | Required | Description |
---|---|---|---|
envId | String | Yes | Identifies your Flagship account and environment (pre-prod or prod). |
apiKey | String | Yes | Identifies your Flagship api key (pre-prod or prod) |
config | FSConfig | No | Object that represent configuration client |
API Key & Environment ID required
You can find your apiKey and your environmentId on your Flagship account, in Parameters > Environment & Security.
import Flagship
//Start the Flagship SDK in Api mode and 5 seconds for timeout.
Flagship.sharedInstance.start(envId:"_ENV_ID_", apiKey: "_API_KEY_",
config: FSConfigBuilder().DecisionApi()
.withTimeout(5000)
.withLogLevel(.ALL).build())
//Start the Flagship SDK in Bucketing mode with custom cache manager
Flagship.sharedInstance.start(envId: "_ENV_ID_", apiKey: "_API_KEY_", config: FSConfigBuilder().Bucketing()
.withTimeout(5000)
.withLogLevel(.INFO)
.withCacheManager(FSCacheManager(customCacheManager))
.build())
@import Flagship;
/// Create config object
FlagshipConfig *config = [[[[[FSConfigBuilder alloc] init] withTimeout:5000]withLogLevel:FSLevelALL] build];
/// Start Flagship sdk
[[ Flagship sharedInstance] startWithEnvId:@"_ENV_ID_" apiKey:@"_API_KEY_" config:config];
Advanced Configuration
FlagshipConfig class help you to configure the SDK via the following two available config implementations: DecisionApi and Bucketing. See Decision Mode section.
Timeout
This delay only concerns the request on fetching campaigns under the api mode. If the API didn't answer during this interval of time, the SDK will not wait longer for it and will use the modifications values already present on device's cache.
If there is no cache yet, the default values will be returned from the getFlag function.
-
func withTimeout(_ timeout:TimeInterval)->FSConfigBuilder
Parameter | Type | Description |
---|---|---|
timeout | TimeInterval | Milliseconds, default value is 2000 ms |
The unit of measure for the timeout is Millisecond.
Log Level
func withLogLevel(_ logLevel:FSLevel)->FSConfigBuilder
Specifies a log level to filter logs emitted by the SDK.
Parameter | Type | Description |
---|---|---|
level | FSLevel | The levels in ascending order are : NONE(0), EXCEPTIONS(1), ERROR(2), WARNING(3), DEBUG(4), INFO(5), ALL(6). |
Decision Mode
DecisionApi
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. Learn more
The default value is DECISION API
Polling Interval Time
Only available for Bucketing Mode:
func withBucketingPollingIntervals(_ pollingTime:TimeInterval)->FSConfigBuilder
Define time interval between two bucketing updates. Default is 60 seconds.
Parameter | Type | Description |
---|---|---|
pollingTime | TimeInterval | interval time, default is 60 seconds. |
Tracking Config Manager
func withTrackingManagerConfig(_ trackingMgrConfig: FSTrackingManagerConfig)->FSConfigBuilder
The SDK through the TrackingManager class report analytics event using batching process, each time a visitor emits a hit, the tracking manager will stack it in the event pool.
When the time interval batchIntervals is over or pool Maximum Size poolMaxSize is reached, the batch process is triggered and the pool emptied. In the meantime, the caching process depends on the cache strategy adopted.
The advantages:
- Use less network traffic
- Avoid data loss
- Catch all hits that would fail
The TrackingManager stand on three options to setup :
- Time Interval
Time interval to process and send event through batch
The batchIntervals parameter is an integer, default value is 5 seconds
- Pool Max Size
Specifies the maximum number of hits in the pool
The poolMaxSize parameter is an integer, default value is 10
- Cache Strategy
Define a strategy to adopt, we have two strategies represented by an enum BatchCachingStrategy.
1 - CONTINUOUS_CACHING
Recommended for client-side applications
Each time a visitor emits an event, this strategy duplicates events in cache using cacheHit (see InterfaceCache), on completion batching process the saved hits are removed from the cache.
Note: The SDK has a default cache implementation using a database
2 - PERIODIC_CACHING
Recommended for server-side applications
This strategy periodically make a copy in the cache of the existing event in the pool, those events will be removed once the batching process completed and succeed
// Create FSTrackingManagerConfig
// - Time Intreval : 20
// - Maximum size pool : 20
// - Strategy : BATCH_CONTINUOUS_CACHING
let trackingConfig = FSTrackingManagerConfig(poolMaxSize: 20, batchIntervalTimer: 20, strategy: .CONTINUOUS_CACHING)
// Create FlagshipConfig
let conf: FlagshipConfig = FSConfigBuilder().withTrackingConfig(trackingConfig).build()
// Start the SDK Flagship
Flagship.sharedInstance.start(envId: "your_env_id", apiKey: "your_api_key", config: conf)
Cache Management
Use a default cache Management provided by the SDK or create your own custom cache manager .
func withCacheManager(_ customCacheManager:FSCacheManager)->FSConfigBuilder
Parameter | Type | Description |
---|---|---|
customCacheManager | FSCacheManager | Managing the cache |
FSCacheManager
This class affords two protocols to handle the cache management.
- init( visitorCacheImp: FSVisitorCacheDelegate? = nil, hitCacheImp: FSHitCacheDelegate? = nil, visitorLookupTimeOut: TimeInterval = 0.2, hitCacheLookupTimeout: TimeInterval = 0.2)
Parameter | Type | Default Value | Description |
---|---|---|---|
visitorCacheImp | Protocol | nil | class implementing visitor protocol , use FSDefaultCacheVisitor if nil |
hitCacheImp | Protocol | nil | class implementing hit protocol, use FSDefaultCacheHit if nil |
visitorLookupTimeOut | TimeInterval | 200 ms | The time duration milliseconds given to lookup visitor, over this delay the operation is stopped |
hitCacheLookupTimeout | TimeInterval | 200 ms | The time duration milliseconds given to lookup hits, over this delay the operation is stopped |
SDK Status
List of the possible SDK status :
Status | Description |
---|---|
NOT_INITIALIZED | Flagship SDK has not been started or initialized successfully. |
POLLING | Flagship SDK has been started successfully but is still polling campaigns. (Only when Bucketing mode is used) |
PANIC | 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. |
It is possible to get the current status via the method getStatus() from the Flagship class.
-
func getStatus()->FStatus
Return the current SDK status
Create a new visitor
The visitor
instance is an object that lets you manage the context, activate experiments and track events.
The user context is a property dataset that defines the current user of your app.
This dataset is sent and used by the Flagship Decision API as targeting criteria for campaign assignment.
For example, if you wanted to enable or disable a specific feature based on VIP status, you would pass that attribute as a key-value pair in the user context so that the Decision API can enable or disable the corresponding feature flag for the user.
import Flagship
let visitor1 = Flagship.sharedInstance.newVisitor("visitor_1")
.withContext(context: ["age" : 32, "isVip":true])
.isAuthenticated(true)
.hasConsented(hasConsented: true)
.build()
@import Flagship;
/// Create visitor with context age : 18 & isVip :YES
FSVisitor * visitor1 = [[[[Flagship sharedInstance] newVisitor:@"visitor_1"
instanceType:InstanceSHARED_INSTANCE]
withContextWithContext:@{@"age":@18, @"isVip":@YES}]
build];
func newVisitor(_ visitorId:String, instanceType:Instance = .SHARED_INSTANCE)-> FSVisitorBuilder
Parameter | Type | Description |
---|---|---|
visitorId | String | Unique visitor identifier. |
instanceType | Instance | How 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.getVisitor() to retrieve the instance. NEW_INSTANCE: The newly created visitor instance wont be saved and will simply be returned. |
Visitor Builder methods :
func withContext(context:[String:Any])->FSVisitorBuilder
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: Number, Boolean, String.
Parameter | Type | Description |
---|---|---|
context | [String:Any] | initial context |
func hasConsented(hasConsented:Bool)->FSVisitorBuilder
Specify if the visitor has consented to personal data usage. When false
some features will be deactivated; the cache will be deactivated and cleared. The default value is true
.
Parameter | Type | Description |
---|---|---|
hasConsented | Bool | true when user has given consent, false otherwise. |
The default value is
true
func isAuthenticated(_ autenticated:Bool)->FSVisitorBuilder
The visitorId will be considered as authenticated if true
otherwise is anonymous.
Parameter | Type | Description |
---|---|---|
autenticated | Bool | true for authenticated user, false for anonymous. |
The default value is
false
Updating the visitor context
The user context is a property dataset that defines the current user 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.
import Flagship
let visitor1 = Flagship.sharedInstance.newVisitor("visitor_1")
.withContext(context: ["age" : 32, "isVip":true])
.isAuthenticated(true)
.hasConsented(hasConsented: true)
.build()
// To check if Flagship have to make a decision with the data you are providing at the SDK init you should fetch the flags
visitor1.fetchFlags {
// Fetch completed , you can retreive your flags
}
// Update the visitor context with lastPurchaseDate key and the value is 1615384464
visitor1.updateContext("lastPurchaseDate", 1615384464)
// Your visitor context has changed (you have updated it) you should fetch the flags to check if the decision has changed
visitor1.fetchFlags {
// Fetch completed , you can retreive your flags
}
@import Flagship;
/// Create visitor
FSVisitor * visitor1 = [[[[Flagship sharedInstance] newVisitor:@"visitor_1" instanceType:InstanceSHARED_INSTANCE] withContextWithContext:@{@"age":@18} ] build];
// Update context
// - isVip with true value
// - lastPurchaseDate with value 1615384464 in the user context
[visitor1 updateContext:@{@"lastPurchaseDate":@1615384464, @"isVip":@YES}];
func updateContext(_ key:String, _ newValue:Any)
Upsert the visitor context values, matching the given keys, used for targeting. Only Integer, String, Boolean, Double typed values are accepted.
Parameter | Type | Description |
---|---|---|
key | String | Context key |
newValue | Any | Context value |
func updateContext(_ context:[String:Any])
Update visitor context using dictionary which contain several keys/values
Parameter | Type | Description |
---|---|---|
context | [String:Any] | Only Integer, String, Boolean, Double typed values are accepted. |
User context values must have a type of Integer, String, Boolean, Double.
func updateContext(_ flagshipContext:FlagshipContext, _ value:Any)
flagshipContext | FlagshipContext | Predefined context key |
value | Any | value of the associated Predefined key |
import Flagship
let visitor1 = Flagship.sharedInstance.newVisitor("visitor_1")
.withContext(context: ["age" : 32, "isVip":true])
.isAuthenticated(true)
.hasConsented(hasConsented: true)
.build()
// Update the visitor context with predefined key
visitor1.updateContext(.CARRIER_NAME, "SFR")
/// Create visitor
FSVisitor * visitor1 = [[[[Flagship sharedInstance] newVisitor:@"visitor_1" instanceType:InstanceSHARED_INSTANCE] withContextWithContext:@{@"age":@18} ] build];
/// Update the visitor context with predefined key
[visitor1 updateContext:@{@"sdk_carrierName":@SFR];
func clearContext()
Clear all the visitor context values used for targeting.
import Flagship
let visitor1 = Flagship.sharedInstance.newVisitor("visitor_1")
.withContext(context: ["age" : 32, "isVip":true])
.isAuthenticated(true)
.hasConsented(hasConsented: true)
.build()
// Clear the context
visitor1.clearContext()
/// Create visitor
FSVisitor * visitor1 = [[[[Flagship sharedInstance] newVisitor:@"visitor_1" instanceType:InstanceSHARED_INSTANCE] withContextWithContext:@{@"age":@18} ] build];
///Clear context
[visitor1 clearContext];
func getContext()->[String:Any]
Get visitor current context key / values.
import Flagship
let visitor1 = Flagship.sharedInstance.newVisitor("visitor_1")
.withContext(context: ["age" : 32, "isVip":true])
.isAuthenticated(true)
.hasConsented(hasConsented: true)
.build()
// Get the current context for the visitor1
let currentContexct = visitor1.getCurrentContext()
/// Create visitor
FSVisitor * visitor1 = [[[[Flagship sharedInstance] newVisitor:@"visitor_1" instanceType:InstanceSHARED_INSTANCE] withContextWithContext:@{@"age":@18} ] build];
///Get context
NSDictionary * context = [visitor1 getContext];
Managing visitor campaigns and their flags
Fetching Flags
The fetchFlags() method of the visitor instance automatically updates the campaign assignments according to the current user context and retrieves applicable flags.
func fetchFlags(onFetchCompleted: @escaping ()-> Void)
This function will call the decision api and update all the campaigns flags from the server according to the visitor context.
Parameter | Type | Description |
---|---|---|
onFetchCompleted | Code | Block to execute once the sync is completed |
import Flagship
// Create visitor
let visitor1 = Flagship.sharedInstance.newVisitor("visitor_1").build()
// Update context
visitor1.updateContext("isVip", true)
// Fetch flags
visitor1.fetchFlags {
// Fetch completed , you can retreive your flags
}
/// Create visitor
FSVisitor * visitor1 = [[[[Flagship sharedInstance] newVisitor:@"visitor_1" instanceType:InstanceSHARED_INSTANCE] withContextWithContext:@{@"age":@18} ] build];
/// Fetch flags
[visitor1 fetchFlagsOnFetchCompleted:^{
// Fetch completed , you can retreive your flags
}];
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:
func getFlag<T>(key:String, defaultValue : T?)->FSFlag
Retrieve a FSFlag 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.
Parameter | Type | Description |
---|---|---|
key | String | key associated to the flag |
defaultValue | T | flag default value |
import Flagship
// Create the visitor
let visitor1 = Flagship.sharedInstance.newVisitor("visitor_1").build()
// Fetch flags
visitor1.fetchFlags {
// Ex: get flag for vip feature
let flag = visitor1.getFlag(key: "displayVipFeature", defaultValue: false)
// Use this flag to display the vip feature
}
/// Create visitor
FSVisitor * visitor1 = [[[[Flagship sharedInstance] newVisitor:@"visitor_1" instanceType:InstanceSHARED_INSTANCE] withContextWithContext:@{@"age":@18} ] build];
/// Fetch flags
[visitor1 fetchFlagsOnFetchCompleted:^{
// Ex: get flag for vip feature
FSFlag * flag = [visitor1 getFlagWithKey:@"displayVipFeature" defaultValue:FALSE];
// Use this flag to enable displaying the vip feature
}];
Default value must be one of the following type : String, Boolean, Integer,Double Array, Dictionary.
Getting flags current values
To retrieve flag current value, simply call value() method of the Flag object.
func value(userExposed: Bool = true)->Any?
Returns the value from the assigned campaign variation or the Flag default value if the Flag does not exist, or if types are different.
Parameter | Type | Default value | Description |
---|---|---|---|
userExposed | Bool | true | Tells 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 Flagship
// Create the visitor
let visitor1 = Flagship.sharedInstance.newVisitor("visitor_1").build()
// Fetch flags
visitor1.fetchFlags {
// Ex: get flag for vip feature
let flag = visitor1.getFlag(key: "displayVipFeature", defaultValue: false)
// Use this flag value to enable displaying the vip feature
let shouldDisplayVipFeature = flag.value()
}
/// Create visitor
FSVisitor * visitor1 = [[[[Flagship sharedInstance] newVisitor:@"visitor_1" instanceType:InstanceSHARED_INSTANCE] withContextWithContext:@{@"age":@18} ] build];
/// Fetch flags
[visitor1 fetchFlagsOnFetchCompleted:^{
// Ex: get flag for vip feature
FSFlag * flag = [visitor1 getFlagWithKey:@"displayVipFeature" defaultValue:FALSE];
// Use this flag value to enable displaying the vip feature
BOOL shouldDisplayVipFeature = [flag valueWithUserExposed:YES];
}];
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.
func metadata()->FSFlagMetadata
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 Flagship
// Create the visitor
let visitor1 = Flagship.sharedInstance.newVisitor("visitor_1").build()
// Fetch flags
visitor1.fetchFlags {
// Ex: get flag for vip feature
let flag = visitor1.getFlag(key: "displayVipFeature", defaultValue: false)
// Use this flag to get the metadata
let metadata = flag.metadata()
// Get the dictionary for metadata
let campaign_info = metadata.toJson()
}
// Create visitor
FSVisitor * visitor1 = [[[[Flagship sharedInstance] newVisitor:@"visitor_1" instanceType:InstanceSHARED_INSTANCE] withContextWithContext:@{@"age":@18} ] build];
// Fetch flags
[visitor1 fetchFlagsOnFetchCompleted:^{
// Ex: get flag for vip feature
FSFlag * flag = [visitor1 getFlagWithKey:@"displayVipFeature" defaultValue:FALSE];
// Use this flag to get the metadata
FSFlagMetadata *metadata = [flag metadata];
// Get the dictionary for metadata
NSDictionary * campaign_info = [metadata toJson]
}];
The metadata
you can access to are the following one:
FSFlagMetadata | Type | Default value | Description |
---|---|---|---|
campaignId | String | "" | id of the campaign |
variationGroupId | String | "" | Id of the variation group |
variationId | String | "" | id of the variation assigned |
isReference | Bool | false | if true that means the assigned variation is the reference, otherwise it's not. |
campaignType | String | "" | Type of the campaign. Ex: AB |
slug | String | "" | campaign slug or empty string if not configured in the platform |
To get the dictionary of metadata use the toJson method through the FSFlagMetadata instance.
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.
func userExposed()
import Flagship
// Create visitor
let visitor1 = Flagship.sharedInstance.newVisitor("visitor_1").build()
// Update context
visitor1.updateContext("isVip", true)
// Fetch flags
visitor1.fetchFlags {
// Ex: get flag for vip feature
let flag = visitor1.getFlag(key: "displayVipFeature", defaultValue: false)
// Read the value without exposing it
let shouldDisplayVipFeature = flag.value(userExposed: false)
// Expose this flag later in the code
flag.userExposed()
}
/// Create visitor
FSVisitor * visitor1 = [[[[Flagship sharedInstance] newVisitor:@"visitor_1" instanceType:InstanceSHARED_INSTANCE] withContextWithContext:@{@"age":@18} ] build];
/// Fetch flags
[visitor1 fetchFlagsOnFetchCompleted:^{
// Ex: get flag for vip feature
FSFlag * flag = [visitor1 getFlagWithKey:@"displayVipFeature" defaultValue:FALSE];
// Use this flag value to enable displaying the vip feature
BOOL shouldDisplayVipFeature = [flag valueWithUserExposed:NO];
// Expose this flag later in the code
[flag userExposed];
}];
Check if a Flag exists
func exists()->Bool
This method will returntrue
if a Flag has been returned by Flagship.
import Flagship
// Create visitor
let visitor1 = Flagship.sharedInstance.newVisitor("visitor_1").build()
// Update context
visitor1.updateContext("isVip", true)
// Fetch flags
visitor1.fetchFlags {
// Ex: get flag for vip feature and check if it exists
let isDisplayVipFeatureExists = visitor1.getFlag(key:"do_not_exists", defaultValue:false).exists()
}
/// Create visitor
FSVisitor * visitor1 = [[[[Flagship sharedInstance] newVisitor:@"visitor_1" instanceType:InstanceSHARED_INSTANCE] withContextWithContext:@{@"age":@18} ] build];
/// Fetch flags
[visitor1 fetchFlagsOnFetchCompleted:^{
// Ex: get flag for vip feature and check if exist
BOOL isDisplayVipFeatureExists = [[visitor1 getFlagWithKey:@"displayVipFeature" defaultValue:FALSE] exists];
}];
Managing visitor consent
The Visitor
class provides a method to let you manage visitor consent for data privacy usage. When false
, campaign activation and hits will be disabled and cache cleared.
func setConsent(hasConsented:Bool)
Parameter | Type | Default value | Description |
---|---|---|---|
hasConsented | Bool | true | Set visitor consent for private data usage. When false some features will be deactivated, cache will be deactivated and cleared. |
import Flagship
// Create visitor
let visitor1 = Flagship.sharedInstance.newVisitor("visitor_1").build()
visitor.sendHitConsent(false)
/// Create visitor
FSVisitor * visitor1 = [[[[Flagship sharedInstance] newVisitor:@"visitor_1" instanceType:InstanceSHARED_INSTANCE] withContextWithContext:@{@"age":@18} ] build];
/// set the consent to NO
[visitor1 setConsentWithHasConsented:NO];
When consent is not given:
- Hits and Activations will be deactivated
- All the cached visitor data will be cleared until consent is given again
- Only consent tracking requests will be sent in order to clear server-side cached data.
Managing visitor cache
By default, the Flagship iOS SDK provides a default cache manager implementation.
This feature helps to cache visitors' data, to cache non-sent hits due to internet failures or server errors, to cache campaign assignations for offline mode and to prevent reallocation in Bucketing mode.
Indeed as the assignation is made on a local device in bucketing mode, changing campaign allocation in the platform would make visitors see different variations if there's no cache.
The default cache manager uses a local database.
Custom Cache Manager
It's possible to provide a custom cache implementation using the following protocols
protocol FSVisitorCacheDelegate
protocol FSVisitorCacheDelegate
This protocol specifies the methods to implement in order to cache visitors' information.
- func cacheVisitor(visitorId: String, _ visitorData: Data)
This method is called when the SDK needs to cache visitor information in your database.
Parameter | Type | Description |
---|---|---|
visitorId | String | Visitor unique identifier from which data need to be cached. |
visitorData | Data | Visitor data to cache |
- func lookupVisitor(visitorId: String) -> Data?
This method is invoked when the SDK needs to get visitor information from database. This method must to respect a time delay which is configurable.
Parameter | Type | Description |
---|---|---|
visitorId | String | Visitor unique identifier from which data need to be loaded. |
Return visitor data respecting the expected format.
- func flushVisitor(visitorId: String)
This method is called when the SDK needs to flush visitor information in your database. For example, when the user hasn't given his consent this method will be called.
Parameter | Type | Description |
---|---|---|
visitorId | String | Visitor unique identifier from which data need to be cleared. |
protocol FSHitCacheDelegate
protocol FSHitCacheDelegate
This interface specifies the methods to implement in order to cache visitors' hits when they failed to be sent.
- func cacheHits(hits: [String: [String: Any]])
This method will be called to cache hits depending on the cache strategy used.
Parameter | Type | Description |
---|---|---|
hits | Dictionary | - key is a unique Id for each hit - value is an Dictionary that represent hit |
- func lookupHits() -> [String: [String: Any]]
This method will be called to load all hits present in database. This method have to respect a time duration, over this delay the operation is cancelled.
Parameter | Type | Description |
---|---|---|
return | Dictionary | - key is a unique Id for each hit - value is an Dictionary that represent hit |
- func flushHits(hitIds: [String])
This method is called when the SDK needs to flush tracking hits in your database except the consent ones. For example when the user hasn't given his consent this method will be invoked.
Parameter | Type | Description |
---|---|---|
hitIds | Array | list of hit's id to remove from database |
- func flushAllHits()
This method will be called to erase all hits in your database without exception.
// Implement the protocol FSVisitorCacheDelegate
public class CustomVisitorCache: FSVisitorCacheDelegate {
public func cacheVisitor(visitorId: String, _ visitorData: Data) {
// Save the Data that represent the information for visitorId
}
public func lookupVisitor(visitorId: String) -> Data? {
// Return the saved data of visitorId
return Data()
}
public func flushVisitor(visitorId: String) {
// Remove the data for visitorId
}
}
// Implement the protocol FSHitCacheDelegate
public class CustomHitCache: FSHitCacheDelegate {
// Save the dictionary that represent hits
public func cacheHits(hits: [String: [String: Any]]) {}
// Return the saved hit in your database
public func lookupHits() -> [String: [String: Any]] {
return [:]
}
// Remove the hit's id given with List
public func flushHits(hitIds: [String]) {}
// Remove all hits in database
public func flushAllHits() {}
}
// Instanciate Custom cache manager
let customCacheManager = FSCacheManager(CustomVisitorCache(), CustomHitCache())
// Start the Flagship sdk
Flagship.sharedInstance.start(envId: "_ENV_ID_", apiKey: "_API_KEY_", config: FSConfigBuilder()
.DecisionApi()
.withCacheManager(customCacheManager)
.build())
@import Flagship;
/// Implement the protocol FSVisitorCacheDelegate
@interface CustomVisitorCache:NSObject <FSVisitorCacheDelegate>
@end
@implementation CustomVisitorCache
- (void)cacheVisitorWithVisitorId:(NSString * _Nonnull)visitorId :(NSData * _Nonnull)visitorData {
/// Upsert in your database
}
- (void)flushVisitorWithVisitorId:(NSString * _Nonnull)visitorId {
/// Clear from your database
}
- (NSData * _Nullable)lookupVisitorWithVisitorId:(NSString * _Nonnull)visitorId {
/// Load & delete from your database
return nil;
}
@end
/// Implement the protocol FSHitCacheDelegate
@interface CustomHitCache:NSObject<FSHitCacheDelegate>
@end
@implementation CustomHitCache
- (void)cacheHitsWithHits:(NSDictionary<NSString *,NSDictionary<NSString *,id> *> * _Nonnull)hits {
// Save the hits into your database
}
- (void)flushAllHits {
// Erase all hits
}
- (void)flushHitsWithHitIds:(NSArray<NSString *> * _Nonnull)hitIds {
// erase hits with a given list ids
}
- (NSDictionary<NSString *,NSDictionary<NSString *,id> *> * _Nonnull)lookupHits {
// Read hits already present in your database
return nil;
}
@end
// Create the custom cache
FSCacheManager * customCache = [[FSCacheManager alloc] init: [[CustomVisitorCache alloc]init] : [[CustomHitCache alloc] init] visitorLookupTimeOut:200 hitCacheLookupTimeout:200];
// Create config
FlagshipConfig *config =[[[[[[FSConfigBuilder alloc] init] withTimeout:5000]withLogLevel:FSLevelALL] withCacheManager:customCache] build];
// Start Flagship sdk
[[ Flagship sharedInstance] startWithEnvId:@"_ENV_ID_" apiKey:@"_API_KEY_" config:config];
Close
When your application is about to terminate, you can call the close method of Flagship class, by doing this, the batch process one last time before stop.
But don't worry if the close function is not invoked the data is stored in cache and will be processed on the next start
- func close()
// Process one last time the batch process then stop.
Flagship.sharedInstance.close()
// Close function to stop batch after processing one last time
[[Flagship sharedInstance] close];
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:
- Set key isAuthenticated to true when creating a new visitor
- Use authenticate method of Visitor instance
Authenticate anonymous visitor
func authenticate(visitorId:String)
Parameter | Type | Description |
---|---|---|
visitorId | String | id 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
func unauthenticate()
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:
- 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
let visitor = Flagship.sharedInstance.newVisitor("random_Id").withContext(context: ["key":
"value"]).build()
// Create visitor
FSVisitor * visitor1 = [[[[Flagship sharedInstance] newVisitor:@"visitor_1" instanceType:InstanceSHARED_INSTANCE] withContextWithContext:@{@"age":@18} ] build];
// Once the visitor log-in and is authenticated on your app.
[visitor1 authenticateWithVisitorId:@"visitor_id"];
// Once the visitor log-out and is unauthenticed on your app.
[visitor1 unauthenticate];
The actual randomId will be what we call the _anonymous id.
- 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: "visitorId")
// Since your visitor has changed (is now logged-in)
// You have to check if the proper targeting and flags are set
visitor.fetchFlags {
// ... 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.
Keep in mind that if the visitor also has its context changed, you might still have changes on flags as your visitor might target new campaigns.
- 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{
// ... Do things ....
}
Final implementation example
// Create a visitor
let visitor = Flagship.sharedInstance.newVisitor("randomId").withContext(context: ["key": "value"]).build()
// Call the authenticate function
visitor.authenticate(visitorId: "visitorId");
// Fetch the flags to update the visitor decision
visitor.fetchFlags(){
// ... Do things ....
}
// If you want to unauthenticate the visitor
visitor.unauthenticate()
// Fetch the flags to update the visitor decision
visitor.fetchFlags{
// ... Do things ....
}
Hit Tracking
This section helps you track your users in your application and learn how to build hits in order to feed campaign goals. For more information about our measurement protocol, read our Universal Collect documentation.
There are four different types of Hits available:
- Screen
- Transaction
- Item
- Event
They must all be sent with the following function through the visitor instance
func sendHit<T: FSTrackingProtocol>(_ event:T)
Common Parameters
These parameters can be sent with any type of hit.
Parameter | Type | Required | Description |
---|---|---|---|
userIp | String | No | Current user IP address |
screenResolution | String | No | Screen Resolution |
userLanguage | String | No | User Language |
currentSessionTimeStamp | Int64 | No | Current Session Timestamp |
sessionNumber | Int | No | Session Number |
///////////////////////////////////////////////////
/////// Create event with common parameters ///////
///////////////////////////////////////////////////
let eventScreen = FSScreen("loginScreen")
// Fill data for event screen
eventScreen.userIp = "168.192.1.0"
eventScreen.sessionNumber = 12
eventScreen.screenResolution = "750 x 1334"
eventScreen.screenColorDepth = "#fd0027"
eventScreen.sessionNumber = 1
eventScreen.userLanguage = "fr"
eventScreen.sessionEventNumber = 2
// Create visitor
let visitor1 = Flagship.sharedInstance.newVisitor("visitor_1").build()
// Send Event
visitor1.sendHit(eventScreen)
////////////////////////////////////////////////////
/////// Create event with common parameters ///////
///////////////////////////////////////////////////
FSScreen* eventScreen = [[FSScreen alloc] init:@"loginScreen"];
/// Fill data for event screen
eventScreen.userIp = @"168.192.1.0";
eventScreen.sessionNumber = @12;
eventScreen.screenResolution = @"750 x 1334";
eventScreen.screenColorDepth = @"#fd0027";
eventScreen.sessionNumber = @1;
eventScreen.userLanguage = @"fr";
eventScreen.sessionEventNumber = @2;
/// Create visitor
FSVisitor * visitor1 = [[[[Flagship sharedInstance] newVisitor:@"visitor_1" instanceType:InstanceSHARED_INSTANCE] withContextWithContext:@{@"age":@18} ] build];
/// Send event screen
[visitor1 sendScreenEvent:eventScreen];
Screen
This hit should be sent each time a visitor arrives on a new interface.
The FSScreen class represents this hit and it requires location
as a string parameter.
init(_ location:String)
Parameter | Type | Required | Description |
---|---|---|---|
location | String | Yes | location name |
///////////////////////////////////
/////// Create Screen Hit ///////
///////////////////////////////////
// Usage: this hit is usually sent when changing screens in the app
let eventScreen = FSScreen("loginScreen")
// Create visitor
let visitor1 = Flagship.sharedInstance.newVisitor("visitor_1").build()
// Send screenhit
visitor1.sendHit(eventScreen)
///////////////////////////////////
/////// Create Screen Event ///////
///////////////////////////////////
// Usage: this hit is usually sent when changing screens in the app
// Create screen event
FSScreen * eventScreen = [[FSScreen alloc] init:@"loginScreen"];
/// Create visitor
FSVisitor * visitor1 = [[[Flagship sharedInstance] newVisitor:@"visitor_1" instanceType:InstanceSHARED_INSTANCE] build]
// Send Event
[visitor1 sendScreenEvent:eventScreen];
Page
This hit should be sent each time a visitor visits a new local or web page in an embedded web view.
The FSPage class represent Page hit and requires location
as a string parameter.
Parameter | Type | Required | Description |
---|---|---|---|
location | String | Yes | Valid url |
///////////////////////////////////
/////// Create Page Hit ///////
///////////////////////////////////
// Usage: This hit should be sent each time a visitor arrives on a new url page
let eventPage = FSPage("https://www.my_domain_com/my_page")
// Create visitor
let visitor1 = Flagship.sharedInstance.newVisitor("visitor_1").build()
// Send screenhit
visitor1.sendHit(eventPage)
///////////////////////////////////
/////// Create Page Event ///////
///////////////////////////////////
// Usage: This hit should be sent each time a visitor arrives on a new url page
// Create Page event
FSPage * eventPage = [[FSPage alloc] init:@"loginScreen"];
/// Create visitor
FSVisitor * visitor1 = [[[Flagship sharedInstance] newVisitor:@"visitor_1" instanceType:InstanceSHARED_INSTANCE] build];
// Send Event
[visitor1 sendPageEvent:eventPage];
Transaction
Hit to send when a user completes a Transaction.
FSTransaction represents it and requires a unique transactionId
and affiliation
name.
The affiliation is the name of Transaction that should appear in the report
init(transactionId:String, affiliation:String)
Parameter | Type | Required | Description |
---|---|---|---|
transactionId | String | Yes | Transaction unique identifier. |
affiliation | String | Yes | Transaction name. Name of the goal in the reporting. |
revenue | Float | No | Total revenue associated with the transaction. This value should include any shipping or tax costs. |
shipping | Float | No | Specifies the total shipping cost of the transaction. |
tax | Float | No | Specifies the total taxes of the transaction. |
currency | String | No | Specifies the currency used for all transaction currency values. Value should be a valid ISO 4217 currency code. |
paymentMethod | String | No | Specifies the payment method for the transaction. |
shippingMethod | String | No | Specifies the shipping method of the transaction. |
itemCount | Int | No | Specifies the number of items for the transaction. |
couponCode | String | No | Specifies the coupon code used by the customer for the transaction. |
//////////////////////////////////////
/////// Create Transaction Hit ///////
//////////////////////////////////////
// The affiliation is the name of transaction that should appear in the report
let transacEvent:FSTransaction = FSTransaction(transactionId:"transacId", affiliation:"BasketTransac")
transacEvent.currency = "EUR"
transacEvent.itemCount = 0
transacEvent.paymentMethod = "PayPal"
transacEvent.shippingMethod = "Fedex"
transacEvent.tax = 2.6
transacEvent.revenue = 15
transacEvent.shipping = 3.5
// Create visitor
let visitor1 = Flagship.sharedInstance.newVisitor("visitor_1").build()
// Send Transaction hit
visitor1.sendHit(transacEvent)
//////////////////////////////////////
/////// Create Transaction Hit ///////
//////////////////////////////////////
/// The affiliation is the name of transaction that should appear in the report
FSTransaction * transacEvent = [[FSTransaction alloc] initWithTransactionId:@"transacId" affiliation:@"BasketTransac"];
transacEvent.currency = @"EUR";
transacEvent.itemCount = 0;
transacEvent.paymentMethod = @"PayPal";
transacEvent.shippingMethod = @"Fedex";
transacEvent.tax = @2.6;
transacEvent.revenue = @15;
transacEvent.shipping = @3.5;
/// Create visitor
FSVisitor * visitor1 = [[[Flagship sharedInstance] newVisitor:@"visitor_1" instanceType:InstanceSHARED_INSTANCE] build]
// Send the transaction event
[visitor1 sendTransactionEvent:transacEvent];
Item
Hit to send an item with a transaction. It must be sent after the corresponding transaction.
FSItem represents this hit and requires transactionId
and product name.
init(transactionId:String, name:String, code:String)
Parameter | Type | Required | Description |
---|---|---|---|
transactionId | String | Yes | Transaction unique identifier |
name | String | Yes | Product name |
price | Float | No | Specifies the item price |
code | String | Yes | Specifies the item code or SKU |
category | String | No | Specifies the item category |
quantity | Int | No | Specifies the item quantity |
//////////////////////////////////////
/////// Create Item Hit //////////////
//////////////////////////////////////
// Item usually represents a product. An item must be associated with a transaction event.
// Create item hit
let itemHit = FSItem(transactionId: "idTransaction", name: "itemName", code: "codeSku")
// Set price
itemHit.price = 20
// Set category
itemHit.category = "shoes"
// Set quantity
itemHit.quantity = 2
// Create visitor
let visitor1 = Flagship.sharedInstance.newVisitor("visitor_1").build()
// Send Item
visitor1.sendHit(itemHit)
//////////////////////////////////////
/////// Create Item Hit //////////////
//////////////////////////////////////
/// Item usually represents a product. An item must be associated with a transaction event.
FSItem * itemhit = [[FSItem alloc] initWithTransactionId:@"transacId" name:@"MicroTransac" code:@"codeSku"];
/// Set Price
itemhit.price = @20;
/// Set category
itemhit.category = @"category";
/// Set quantity
itemhit.quantity = @1;
/// Create visitor
FSVisitor * visitor1 = [[[Flagship sharedInstance] newVisitor:@"visitor_1" instanceType:InstanceSHARED_INSTANCE] build]
/// Send item event
[visitor1 sendItemEvent:itemhit];
Event
Hit which represents an event. It can be anything you want: for example a click on an Add to Cart button or a newsletter subscription.
FSEvent represents this hit and requires a category event and action name string.
FSCategoryEvent can be Action_Tracking
or User_Engagement
.
init(eventCategory:FSCategoryEvent, eventAction:String)
Parameter | Type | Required | Description |
---|---|---|---|
category | FSCategoryEvent | Yes | Category of the event (Action_Tracking or User_Engagement ). |
action | String | yes | Name of the event. |
label | String | No | Description of the event. |
eventValue | UInt | No | Value of the event, must be an unsigned integer value |
The event action is the name of Event that should appear in the report
//////////////////////////////////////
/////// Create Event Hit /////////////
//////////////////////////////////////
/// Create action tracking category event
let actionEvent:FSEvent = FSEvent(eventCategory: FSCategoryEvent.Action_Tracking, eventAction: "cta_Shop")
actionEvent.label = "cta_Shop_label"
actionEvent.eventValue = 1
// Create visitor
let visitor1 = Flagship.sharedInstance.newVisitor("visitor_1").build()
// Send Event Tracking
visitor1.sendHit(actionEvent)
//////////////////////////////////////
/////// Create Event Hit /////////////
//////////////////////////////////////
/// Create action tracking category event
FSEvent * actionEvent = [[FSEvent alloc] initWithEventCategory:FSCategoryEventAction_Tracking eventAction:@"cta_Shop"];
actionEvent.label = @"cta_Shop_label";
[actionEvent setEventValue:1];
/// Create visitor
FSVisitor * visitor1 = [[[Flagship sharedInstance] newVisitor:@"visitor_1" instanceType:InstanceSHARED_INSTANCE] build];
/// Send Event Tracking
[visitor1 sendEventTrack:actionEvent];
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 name | Description | Context Variable name | Type | Auto-set by SDK | Example | |
---|---|---|---|---|---|---|
FIRST_TIME_INIT | First init of the app | sdk_firstTimeInit | Boolean | Yes | true (false if the init isn’t the first one) | |
LOCALE | Language of the device | sdk_deviceLanguage | String | Yes | fr_FR | |
DEVICE_TYPE | Type of the device (Tablet/Mobile/Watch) | sdk_deviceType | String | Yes | mobile | |
DEVICE_MODEL | Model of the device | sdk_deviceModel | String | Yes | iPhone12,8 | |
LOCATION_CITY | City geolocation | sdk_city | String | No | toulouse | |
LOCATION_REGION | Region geolocation | sdk_region | String | No | occitanie | |
LOCATION_COUNTRY | Country geolocation | sdk_country | String No | France | ||
LOCATION_LAT | Current Latitude | sdk_lat | Double | No | 43.623647 | |
LOCATION_LONG | Current Longitude | sdk_long | Double | No | 1.445397 | |
IP | IP of the device | sdk_ip | String | No | 127.0.0.1 | |
OS_NAME | Name of the OS | sdk_osName | String | Yes | iOS / macOS | |
OS_VERSION_CODE | Version of OS | sdk_osVersionCode | String | Yes | 9.0 | |
OS_VERSION_NAME | Name of OS | sdk_osVersionName | String | Yes | iOS / tvOS | |
MVNO / carrierName | (Mobile virtual network operator)" | sdk_carrierName | String | No | orange | |
DEV_MODE | Is the app in debug mode? | sdk_devMode | Boolean | No | true | |
INTERNET_CONNECTION | What is the internet connection | sdk_internetConnection | String | No | 3g | |
APP_VERSION_NAME | Version name of the app | sdk_versionName | String | No | 1.1.2-beta | |
APP_VERSION_CODE | Version code of the app | sdk_versionCode | Number (int) | No | 40 | |
FLAGSHIP_VERSION | Version of the Flagship SDK | sdk_fsVersion | String | Yes | 1.1.2 | |
INTERFACE_NAME | Name of the interface | sdk_interfaceName | String | No | ProductPage |
Here you can see how a predefined key is used to filter a report in the Flagship interface:
To overwrite the keys, use the
updateContext
method
Updated 3 months ago