This guide provides detailed information about our Flutter plugin which is built on top of our Android SDK and iOS mobile SDKs. The plugin provides you a way to interact with the native SDKs.
All of our SDKs are open source. Go to our Flutter GitHub repository to see the source code.
Language Support
Dart SDK v2.16.2 and greater, and Flutter v2.5.0 and greater.
Platform support
This plugin currently supports the Android and iOS platforms.
Initialization
Set up Split in your code base with the following two steps:
1. Add the package in your pubspec.yaml file
dependencies:
splitio: 0.1.2+2
2. Instantiate the plugin
/// Initialize Split plugin
import 'package:splitio/split_client.dart';
import 'package:splitio/splitio.dart';
final Splitio _split = Splitio('YOUR_API_KEY', 'KEY');
We recommend keeping only one instance of the Splitio
object at all times (singleton pattern) and reusing it throughout your application.
Include the API key for the environment that you are setting up the SDK for. The API key is available on your Organization Settings page, on the API keys tab. Select the "Client-side" type. This is a special type of API token with limited privileges to be used on mobile clients. Learn more about API keys.
Using the plugin
Basic use
When the SDK is instantiated, it starts background tasks to update an in-memory cache with small amounts of data fetched from Split servers. This process can take up to a few hundred milliseconds, depending on the size of data. If the SDK is asked to evaluate which treatment to show to a customer for a specific split while it's in this intermediate state, it may not have the data necessary to run the evaluation. In this case, the SDK does not fail, rather, it returns the control treatment.
To make sure the SDK is properly loaded before asking it for a treatment, wait until the SDK is ready, as shown below. You can use the onReady
parameter when creating the client to get notified when this happens.
After the observable calls back, you can use the getTreatment
method to return the proper treatment based on the SPLIT_NAME
and the key
variable you passed when instantiating the SDK. Then use an if-else-if block as shown below and insert the code for the different treatments that you defined in the Split web interface. Remember the final else branch in your code to handle the client returning control.
/// Get treatment
_split.client(onReady: (client) async {
final String treatment = await client.getTreatment('SPLIT_NAME');
if (treatment == 'on') {
/// Insert code here to show on treatment
} else if (treatment == 'off') {
/// Insert code here to show off treatment
} else {
/// Insert your control treatment code here
}
});
Shutdown
Call the client.destroy()
method once you've stopped using the client, as this method gracefully shuts down the Split SDK by stopping all background threads, clearing caches, closing connections, and flushing the remaining unpublished impressions.
/// Initialize Split plugin
final Splitio _split = Splitio('YOUR_API_KEY', 'KEY');
final SplitClient _client = _split.client();
/// You should call destroy() on the client once it is no longer needed:
_client.destroy();
After destroy()
is called and finishes, any subsequent invocations to getTreatment
/getTreatments
or manager methods result in control
or empty list, respectively.
Multiple evaluations at once
In some instances, you may want to evaluate treatments for multiple splits at once. Use the client.getTreatments
method to do this. Simply pass a list of the split names you want treatments for and the method returns a map with the results.
const splitNames = ['SPLIT_NAME_1', 'SPLIT_NAME_2'];
var treatments = {};
_split.client(onReady: (client) async {
treatments = await client.getTreatments(splitNames);
});
Attribute syntax
To target based on custom attributes, pass the client's getTreatment
method as an attribute map at runtime.
In the example below, we are rolling out a split to users. The provided attributes plan_type
, registered_date
, permissions
, and deal_size
are passed to the getTreatment
call. These attributes are compared and evaluated against the attributes used in the rollout plan as defined in the Split Web console to decide whether to show the on
or off
treatment to this account. The getTreatment
method supports five types of attributes: strings, numbers, dates, booleans, and sets. The proper data type and syntax for each are:
- Strings: Use type String.
- Numbers: Use type num (int or double).
- Dates: Express the value in milliseconds since epoch.
Note: Milliseconds since epoch is expressed in UTC. If your date or date-time combination is in a different timezone, first convert it to UTC, then transform it to milliseconds since epoch. - Booleans: Use type bool.
- Sets: Use type List or Set.
final attributes = {
// date attributes are handled as `millis since epoch`
'registered_date': DateTime.now().millisecondsSinceEpoch,
// this string will be compared against a list called `plan_type` or against another string
'plan_type': 'growth',
// this number will be compared against a number value called `deal_size`
'deal_size': 10000,
// this array will be compared against a set called `permissions`
'permissions': ['read', 'write']
};
final String treatment =
await _client.getTreatment('SPLIT_NAME', attributes);
if (treatment == 'on') {
// insert on code here
} else if (treatment == 'off') {
// insert off code here
} else {
// insert control code here
}
You can pass your attributes in this way to the getTreatments
method.
Track
Use the track
method to record any actions your customers perform. Each action is known as an event
and corresponds to an event type
. Calling track
through one of our SDKs or via the API is the first step to getting experimentation data into Split and allows you to measure the impact of your splits on your users' actions and metrics. Learn more about using track events in Split.
In the examples below, you can see that the .track()
method can take up to four arguments. The proper data type and syntax for each are:
- EVENT_TYPE: The event type that this event should correspond to. The expected data type is String. Full requirements on this argument are:
- Contains 63 characters or fewer.
- Starts with a letter or number.
- Contains only letters, numbers, hyphen, underscore, or period.
- This is the regular expression we use to validate the value:
[a-zA-Z0-9][-_\.a-zA-Z0-9]{0,62}
- TRAFFIC_TYPE: (Optional) The traffic type of the key in the track call. The expected data type is String. You can only pass values that match the names of traffic types that you have defined in your instance of Split.
- VALUE: (Optional) The value is used to create the metric. The expected data type is double.
- PROPERTIES: (Optional) An object of key value pairs that can be used to filter your metrics. Learn more about event property capture. Split currently supports three types of properties: strings, numbers, and booleans.
The track
method returns a boolean value of true
or false
to indicate whether or not the client was able to successfully queue the event to be sent back to Split's servers on the next event post. The service returns false
if the current queue size is equal to the config set by eventsQueueSize
or if an incorrect input to the track
method is provided.
In the case that a bad input is provided, you can read more about our SDK's expected behavior.
_split.client(onReady: (client) async {
/// Named parameters are optional
client.track('EVENT_TYPE',
trafficType: 'TRAFFIC_TYPE',
value: 120.25,
properties: {'package': 'premium', 'admin': true, 'discount': 50});
});
Configuration
The SDK has a number of knobs for configuring performance. Each knob is tuned to a reasonable default. However, you can override values when instantiating the Splitio
:
final SplitConfiguration configurationOptions = SplitConfiguration(
trafficType: 'user',
enableDebug: true,
persistentAttributesEnabled: true);
final Splitio _split =
Splitio('YOUR_API_KEY', 'KEY', configuration: configurationOptions);
The parameters available for configuration are shown below.
Configuration | Description | Default value |
---|---|---|
featuresRefreshRate | The SDK polls Split servers for changes to feature splits at this rate (in seconds). | 3600 seconds |
segmentsRefreshRate | The SDK polls Split servers for changes to segments at this rate (in seconds). | 1800 seconds |
impressionsRefreshRate | The treatment log captures which customer saw what treatment (on, off, etc.) at what time. This log is periodically flushed back to Split servers. This configuration controls how quickly the cache expires after a write (in seconds). | 1800 seconds |
telemetryRefreshRate | The SDK caches diagnostic data that it periodically sends to Split servers. This configuration controls how frequently this data is sent back to Split servers (in seconds). | 3600 seconds (1 hour) |
eventsQueueSize | When using .track , the number of events to be kept in memory. |
10000 |
eventFlushInterval | When using .track , how often is the events queue flushed to Split's servers. |
1800 seconds |
eventsPerPush | Maximum size of the batch to push events. | 2000 |
trafficType | When using .track , the default traffic type to be used. |
not set |
impressionsQueueSize | Default queue size for impressions. | 30K |
enableDebug | Enabled verbose mode. | false |
streamingEnabled | Boolean flag to enable the streaming service as default synchronization mechanism when in foreground. In the event of an issue with streaming, the SDK will fallback to the polling mechanism. If false, the SDK will poll for changes as usual without attempting to use streaming. | true |
persistentAttributesEnabled | Enables saving attributes on persistent cache which is loaded as part of the SDK_READY_FROM_CACHE flow. All functions that mutate the stored attributes map affect the persistent cache. | false |
impressionListener | Enables impression listener. If true, generated impressions stream in the impressionsStream() method of Splitio. | false |
syncConfig | Use it to filter specific splits to be synced and evaluated by the SDK. If not set, all splits are downloaded. | not set |
Advanced use cases
This section describes advanced use cases and features provided by the SDK.
Instantiate multiple SDK clients
Split supports the ability to release based on multiple traffic types. For example, with traffic types, you can release to users
in one split and accounts
in another. If you are unfamiliar with using multiple traffic types, refer to the Traffic type guide for more information.
Each SDK client is tied to one specific customer ID at a time, so if you need to roll out splits by different keys, instantiate multiple SDK clients, one for each traffic type. For example, you may want to roll out the feature USER_POLL
by users
and the feature ACCOUNT_PERMISSIONING
by accounts
. You can do this with the example below:
final Splitio _split = Splitio('YOUR_API_KEY', 'ACCOUNT_ID');
/// Create a client for the default key, in this case, the account id.
final SplitClient _accountClient = _split.client(onReady: (client) async {
var userPollTreatment = client.getTreatment('USER_POLL');
});
/// To create another client for a user instead, just pass in a
/// User ID to the splitService.initClient() method. (This is only valid after
/// at least one client has been initialized).
final SplitClient _userClient = _split.client(
matchingKey: 'USER_ID',
onReady: (client) async {
var accountPermissioningTreatment =
client.getTreatment('ACCOUNT_PERMISSIONING');
});
/// Track events for accounts
_userClient.track('PAGELOAD', value: 7.86);
/// Track events for users
_accountClient.track('ACCOUNT_CREATED');
Number of SDK instances
While the SDK does not put any limitations on the number of instances that can be created, we strongly recommend keeping the number of clients down to one or two.
Manager
Use these methods on Splitio instance to get a list of the Splits available to the Split client.
/// Retrieves the splits that are currently registered with the SDK.
Future<List<SplitView>> splits()
/// Returns the splits registered with the SDK of this name.
Future<SplitView?> split(String splitName)
/// Returns the names of splits registered with the SDK.
Future<List<String>> splitNames()
Subscribe to events and changes
You can subscribe specify up to four different callbacks when creating a client:
onReady
- The SDK is ready to be used and has the most up-to-date snapshot of your splits.onReadyFromCache
- The SDK is ready to evaluate using locally cached data (which might be stale). If conditions are met, this event is emitted almost immediately since access to the cache is very fast. Otherwise it won't fire.onUpdate
- This event fires whenever a split or segment is changed. Use this if you want to reload your app every time you make a change in the Split Web console.onTimeout
- This event fires when the time set for timeout in the configuration has elapsed and the SDK has not fully loaded.
final Splitio _split = Splitio('YOUR_API_KEY', 'ACCOUNT_ID');
_split.client(onReady: (client) {
/// Client has fetched the most up-to-date definitions.
}, onReadyFromCache: (client) {
/// Fired after the SDK could confirm the presence of the Split data.
/// This event fires really quickly, since there's no actual fetching of information.
/// Keep in mind that data might be stale, this is NOT a replacement of sdkReady.
}, onUpdated: (client) {
/// Fired each time the client state changes, for example,
/// when a Split or a Segment changes.
}, onTimeout: (client) {
/// Fired if the client was not able to be ready.
/// GetTreatment can still be called but the result may be CONTROL.
});
You can also receive Futures (or a Stream, for the Update event) by accessing the following methods in the client.
_client.whenReady().then((client) {
/// Client has fetched the most up-to-date definitions.
});
_client.whenReadyFromCache((client) {
/// Fired after the SDK could confirm the presence of the Split data.
/// This event fires really quickly, since there's no actual fetching of information.
/// Keep in mind that data might be stale, this is NOT a replacement of sdkReady.
});
StreamSubscription<SplitClient> streamSubscription = _client.whenUpdated().listen((client) {
/// Fired each time the client state changes, for example,
/// when a Split or a Segment changes.
});
_client.whenTimeout().then((client) {
/// Fired if the client was not able to be ready.
/// GetTreatment can still be called but the result may be CONTROL.
});
Link with native factory
A native Split Factory instance can be shared with the plugin to save resources when evaluations need to be performed on native platform logic. To do so, do the following:
Android
- If not created already, create a subclass of Android's
Application
, and add its name to the Manifest.
public class CustomApplication extends Application {
}
<application
android:label="my_app"
android:name=".CustomApplication"
android:icon="@mipmap/ic_launcher">
- Add the Split Android SDK dependency to your project's
build.gradle
file.
dependencies {
implementation 'io.split.client:android-client:split_version'
...
}
- Create a property in your subclass of
Application
to hold your factory instance. - Initialize the factory in the
onCreate
callback of yourApplication
subclass.
public class CustomApplication extends Application {
private SplitFactory factory;
@Override
public void onCreate() {
super.onCreate();
try {
factory = SplitFactoryBuilder
.build("YOUR_API_KEY",
new Key("USER_KEY"),
SplitClientConfig.builder()
.build(),
getApplicationContext());
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
- Make the
Application
subclass implement theSplitFactoryProvider
interface, and return the previously created factory in the overriddengetSplitFactory()
method.
public class CustomApplication extends Application implements SplitFactoryProvider {
private SplitFactory factory;
@Override
public void onCreate() {
...
}
@Override
public SplitFactory getSplitFactory() {
return factory;
}
}
iOS
- Add the Split iOS SDK dependency to your app's
Podfile
.
pod 'Split', '~> 2.15.0'
...
- Add a property in your AppDelegate class to hold the factory instance. Make sure to import
Split
.
@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
private var splitFactory: SplitFactory?
...
}
- Initialize the factory just before the
GeneratedPluginRegistrant.register(with: self)
line.
@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
private var splitFactory: SplitFactory?
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
let config = SplitClientConfig()
splitFactory = DefaultSplitFactoryBuilder()
.setConfig(config)
.setApiKey("YOUR_API_KEY")
.setKey(Key(matchingKey: "USER_KEY"))
.build()
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
...
}
- Implement the
SplitFactoryProvider
protocol in yourAppDelegate
and return the previously created factory in the overriddengetFactory()
method.
@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate, SplitFactorProvider {
private var splitFactory: SplitFactory?
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
...
}
func getFactory() -> SplitFactory? {
splitFactory
}
}
Warning
By using this method, all configuration declared when instantiating the Plugin in Flutter are ignored, since the factory is already instantiated and its configuration loaded.
Instantiating the factory natively prevents the plugin from setting up an Impression Listener, so impressions won't be accessible from Flutter. However, Impression Listeners can still be added and used in native code.
Comments
0 comments
Please sign in to leave a comment.