Our analytics data show that most of the people make a purchase during their first session. However, only 70% of users open our in-app store during the first session. We hypothesize that if we add a purchase screen to the tutorial then 100% of users will face it and the number of purchases during the first session will increase.
The criteria for test inclusion is starting the tutorial. The DTDAnalytics.tutorial(step: Int)
event with step = -1 is the trigger.
Control group: a 10 step tutorial, no purchase screens
Group А: an 11 step tutorial. At step number 5, we will offer a special offer.
// Timer constants
struct Constants {
static let waitGroupConst = 10.0
}
// Value keys
enum ValueKey: String {
case showStore
}
class AppConfig {
static func loadDefaults() {
let appDefaults: [String: Any] = [
ValueKey.showStore.rawValue: "false"
]
// Set default values
DTDRemoteConfig.defaults = appDefaults
}
static func bool(forKey key: ValueKey) -> Bool {
return DTDRemoteConfig.config[key.rawValue].boolValue
}
}
class AppLogic {
var timer: Timer
// Tutorial open event (e.g. by clicking the ‘start tutorial’ button)
func startTutorial() {
// Send a trigger event that indicates tutorial start
DTDAnalytics.tutorial(step: -1)
}
func nextTutotrialStep(_ currentStep: Int) {
DTDAnalytics.tutorial(step: currentStep)
if AppConfig.bool(forKey: .showStore) && currentStep == 5 {
// Offer purchasing of a special offer
}
}
}
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication,
willFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Set the maximum time of waiting for an A/B test group
DTDRemoteConfig.groupDefinitionWaiting = Constants.waitGroupConst
// Set default values
AppConfig.loadDefaults()
// Initialize the SDK for working with A/B testing
DTDAnalytics.initializeWithAbTest(applicationKey: "appKey",
configuration: config,
abConfigListener: self)
}
}
extension AppLogic: DTDRemoteConfigListener {
// Process the result of waiting for A/B test configuration
func onReceived(result: DTDRemoteConfigReceiveResult) {
// It is not used in current example
}
// Prepare the app UI for changing the remote configuration
func onPrepareToChange() {
// Use the main app thread because you are getting ready for working with the interface
DispatchQueue.main.async { [weak self] in
// Display the download progress indicator
self?.showActivityIndicator()
// Add a timer that will forcibly remove the download progress indicator
self?.timer = Timer.scheduledTimer(withTimeInterval: Constants.waitGroupConst, repeats: false) { [weak self] _ in
self?.hideActivityIndicator()
}
}
}
// Apply the values of the assigned group
func onChanged(result: DTDRemoteConfigChangeResult, error: Error?) {
defer {
// Hide the download progress indicator
DispatchQueue.main.async { [weak self] in
self?.timer.invalidate();
self?.hideActivityIndicator()
}
}
switch result {
case .success:
// Apply new values
DTDRemoteConfig.applyConfig()
case .failure:
// Error processing
if let error = error {
print(error.localizedDescription)
}
@unknown default:
break
}
}
}
// Constants .h + .m
@interface Constants : NSObject
// Timer constants
extern double const waitGroupConst;
// Value keys
extern NSString * const showStore;
@end
@implementation Constants
double const waitGroupConst = 10.0;
NSString * const showStore = @"showStore";
@end
// AppConfig .h + .m
@interface AppConfig: NSObject
+(void) loadDefaults;
+(BOOL) getBoolForKey:(NSString *) key;
@end
@implementation AppConfig
+(void)loadDefaults {
NSDictionary *appDefaults = @{
showStore: @NO
};
// Set default values
DTDRemoteConfig.defaults = appDefaults;
}
+(BOOL) getBoolForKey:(NSString *) key {
return DTDRemoteConfig.config[key].boolValue;
}
@end
// AppLogic .h + .m
@interface AppLogic : UIViewController <DTDRemoteConfigListener>
@property (nonatomic, strong) NSTimer *timer;
@end
@implementation AppLogic
- (void)viewDidLoad {
[super viewDidLoad];
[self startTutorial];
}
// Tutorial open event (e.g. by clicking the ‘start tutorial’ button)
- (void) startTutorial {
// Send a trigger event that indicates tutorial start
[DTDAnalytics tutorialStep: -1];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
[self nextTutotrialStep:5];
});
}
- (void) nextTutotrialStep:(NSInteger) currentStep {
[DTDAnalytics tutorialStep: currentStep];
if ([AppConfig getBoolForKey:showStore] && currentStep == 5) {
// Offer purchasing of a special offer
}
}
// Process the result of waiting for A/B test configuration
- (void)onReceivedResult:(enum DTDRemoteConfigReceiveResult)result {
// It is not used in current example
}
// Prepare the app UI for changing the remote configuration
- (void)onPrepareToChange {
// Use the main app thread because you are getting ready for working with the interface
__weak AppLogic *weakSelf = self;
dispatch_async(dispatch_get_main_queue(), ^{
// Display the progress indicator
[weakSelf showActivityIndicator];
// Add a timer that will forcibly remove the progress indicator
self.timer = [NSTimer scheduledTimerWithTimeInterval:waitGroupConst repeats:false block:^(NSTimer *timer){
[weakSelf hideActivityIndicator];
}];
});
}
// Apply the values of the assigned group
- (void)onChangedResult:(enum DTDRemoteConfigChangeResult)result error:(NSError *)error {
switch (result) {
case DTDRemoteConfigChangeResultSuccess:
// Apply new values
[DTDRemoteConfig applyConfig];
break;
case DTDRemoteConfigChangeResultFailure:
// Error processing
if (error) {
NSLog(@"DTDRemoteConfigError: %@", error.localizedDescription);
}
default:
break;
}
// Hide the progress indicator
__weak AppLogic *weakSelf = self;
dispatch_async(dispatch_get_main_queue(), ^{
[self.timer invalidate];
[weakSelf hideActivityIndicator];
});
}
- (void)showActivityIndicator {
// Display the progress indicator
}
- (void)hideActivityIndicator {
// Hide the progress indicator
}
@end
// AppDelegate .h + .m
@interface AppDelegate ()
@end
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
AppLogic *appLogicController = [[UIStoryboard storyboardWithName:@"Main" bundle:nil] instantiateViewControllerWithIdentifier:@"AppLogic"];
UINavigationController *navController = [[UINavigationController alloc]initWithRootViewController:appLogicController];
self.window.rootViewController = navController;
[self.window makeKeyAndVisible];
// Group timeout, optional
DTDRemoteConfig.groupDefinitionWaiting = waitGroupConst;
// Implementation defaults params
[AppConfig loadDefaults];
[DTDAnalytics applicationKey:appKey abConfigListener:appLogicController];
return YES;
}
@end
class Constants {
// Timer constants
companion object {
const val waitGroupConst = 10.0
const val waitGroupConstInMilliseconds = 10000L
}
}
// Value keys
enum class ValueKey(val value: String) {
ShowStore("showStore")
}
class AppConfig {
companion object {
fun loadDefaults() {
val appDefaults = mapOf<String, Any>(ValueKey.ShowStore.value to "false")
DTDRemoteConfig.defaults = appDefaults
}
fun bool(key: ValueKey): Boolean {
return DTDRemoteConfig.config[key.value].booleanValue
}
}
}
class AppLogic {
// Tutorial open event (e.g. by clicking the ‘start tutorial’ button)
fun startTutorial() {
// Send a trigger event that indicates tutorial start
DTDAnalytics.tutorial(step = -1)
}
fun nextTutorialStep(currentStep: Int) {
DTDAnalytics.tutorial(step = currentStep)
if (AppConfig.bool(ValueKey.ShowStore) && currentStep == 5) {
// Offer purchasing of a special offer
}
}
}
class MainActivity : AppCompatActivity(), DTDRemoteConfigListener {
//activityIndicator control timer
var timer: TimerTask? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState, persistentState)
setContentView(R.layout.activity_main)
// Set the maximum time of waiting for an A/B test group
DTDRemoteConfig.groupDefinitionWaiting = Constants.waitGroupConst
// Set default values
AppConfig.loadDefaults()
// Initialize the SDK for working with A/B testing
DTDAnalytics.initializeWithAbTest(
appKey = "appKey",
context = this,
abConfigListener = this
)
}
// Process the result of waiting for A/B test configuration
override fun onReceived(result: DTDRemoteConfigReceiveResult) {
// It is not used in current example
}
// Prepare the app UI for changing the remote configuration
override fun onPrepareToChange() {
// Use the main app thread because you are getting ready for working with the interface
runOnUiThread {
// Display the download progress indicator
this.showActivityIndicator()
}
// Add a timer that will forcibly remove the download progress indicator
timer = Timer("ActivityIndicator").schedule(Constants.waitGroupConstInMilliseconds) {
runOnUiThread {
this.hideActivityIndicator()
}
}
}
override fun onChanged(result: DTDRemoteConfigChangeResult, ex: Exception?) {
when (result) {
DTDRemoteConfigChangeResult.Success -> {
// Apply new values
DTDRemoteConfig.applyConfig()
}
DTDRemoteConfigChangeResult.Failure -> {
// Error processing
ex?.let { Log.e("TAG", ex.toString()) }
}
}
this.timer?.cancel()
this.timer = null
runOnUiThread {
this.hideActivityIndicator()
}
class Constants {
// Timer constants
final static double waitGroupConst = 10.0;
final static long waitGroupConstInMilliseconds = 10000L;
}
enum ValueKey {
ShowStore("showStore");
private final String stringValue;
ValueKey(String toString) {
stringValue = toString;
}
@Override
public String toString() {
return stringValue;
}
}
class AppConfig {
static void loadDefaults() {
HashMap<String, Object> map = new HashMap<>();
map.put(ValueKey.ShowStore.toString(), "false");
DTDRemoteConfig.INSTANCE.setDefaults(map);
}
static Boolean bool(ValueKey key) {
return DTDRemoteConfig.INSTANCE.getConfig().get(key.toString()).getBooleanValue();
}
}
class AppLogic {
// Tutorial open event (e.g. by clicking the 'start tutorial' button)
void startTutorial() {
// Send a trigger event that indicates tutorial start
DTDAnalytics.INSTANCE.tutorial( -1);
}
void nextTutorialStep(int currentStep) {
DTDAnalytics.INSTANCE.tutorial(currentStep);
if (AppConfig.bool(ValueKey.ShowStore) && currentStep == 5) {
// Offer purchasing of a special offer
}
}
}
class MainActivity extends AppCompatActivity implements DTDRemoteConfigListener {
//activityIndicator control timer
Timer timer = null;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Set the maximum time of waiting for an A/B test group
DTDRemoteConfig.INSTANCE.setGroupDefinitionWaiting(Constants.waitGroupConst);
// Set default values
AppConfig.loadDefaults();
// Initialize the SDK for working with A/B testing
DTDAnalytics.INSTANCE.initializeWithAbTest("appKey", this, this);
}
// Process the result of waiting for A/B test configuration
@Override
public void onReceived(@NonNull DTDRemoteConfigReceiveResult result) {
// It is not used in current example
}
// Prepare the app UI for changing the remote configuration
@Override
public void onPrepareToChange() {
// Use the main app thread because you are getting ready for working with the interface
runOnUiThread(() -> {
// Display the download progress indicator
showActivityIndicator();
});
timer = new Timer().schedule(new TimerTask() {
@Override
public void run() {
runOnUiThread(() -> hideActivityIndicator());
}
}, Constants.waitGroupConstInMilliseconds);
}
@Override
public void onChanged(@NonNull DTDRemoteConfigChangeResult result, @Nullable Exception ex) {
if (result == DTDRemoteConfigChangeResult.Success) {
// Apply new values
DTDRemoteConfig.INSTANCE.applyConfig();
}
if (result == DTDRemoteConfigChangeResult.Failure) {
// Apply new values
DTDRemoteConfig.INSTANCE.applyConfig();
}
if (timer != null) {
timer.cancel();
timer = null;
}
runOnUiThread(() -> hideActivityIndicator());
}
}
// Timer constants
public static class Constants
{
public const float WAIT_GROUP_TIME = 10.0f;
}
// Value keys
public enum ValueKey
{
showStore
}
public class AppConfig
{
public void LoadDefaults()
{
var appDefaults = new Dictionary<string, object>
{
{ValueKey.showStore.ToString(), false}
};
DTDRemoteConfig.Defaults = appDefaults;
}
public bool GetBool(ValueKey key) => DTDRemoteConfig.Config[key.ToString()].BoolValue();
}
public class AppLogic : MonoBehaviour
{
private readonly AppConfig _appConfig = new AppConfig();
private const string APP_KEY = "appKey";
private SimpleUI _simpleUI;
private void Start()
{
DontDestroyOnLoad(this);
_simpleUI = FindObjectOfType<SimpleUI>();
if (_simpleUI == null) throw new NullReferenceException("UIManager not found.");
// Set the maximum time of waiting for an A/B test group
DTDRemoteConfig.GroupDefinitionWaiting = Constants.WAIT_GROUP_TIME;
// Set default values
_appConfig.LoadDefaults();
// Initialize the SDK for working with A/B testing
DTDAnalytics.InitializeWithAbTests(
appKey: APP_KEY,
configListener: this);
}
// Tutorial open event (e.g. by clicking the ‘start tutorial’ button)
// Send a trigger event that indicates tutorial start
public void StartTutorial() => DTDAnalytics.Tutorial(-1);
public void NextTutorialStep(int step)
{
DTDAnalytics.Tutorial(step);
if (_appConfig.GetBool(ValueKey.showStore) && step == 5)
{
// Offer purchasing of a special offer
Store.Instance.ShowSpecialOffer();
}
}
// Process the result of waiting for A/B test configuration
public void OnReceived(DTDRemoteConfigReceiveResult result)
{
// It is not used in current example
}
// Prepare the app UI for changing the remote configuration
public void OnPrepareToChange()
{
// Display the download progress indicator
_simpleUI.ShowLoadingIndicator(Constants.WAIT_GROUP_TIME);
}
// Apply the values of the assigned group
public void OnChanged(DTDRemoteConfigChangeResult result, string exceptionText = null)
{
// Hide the download progress indicator
_simpleUI.HideLoadingIndicator();
switch (result)
{
case DTDRemoteConfigChangeResult.Failure:
// Error processing
Debug.LogError(exceptionText);
break;
case DTDRemoteConfigChangeResult.Success:
// Apply new values
DTDRemoteConfig.ApplyConfig();
break;
}
}
}
// Timer constants
static class Constants
{
public const float WaitGroupConst = 10.0f;
}
// Value keys
static class ValueKeys
{
public const string ShowStore = "showStore";
}
static class AppConfg
{
public static void LoadDafaults()
{
DTDRemoteConfig.Defaults = new Dictionary<string, object>
{
[ValueKeys.ShowStore] = false
};
}
public static bool GetBoolValue(string key)
{
return DTDRemoteConfig.Config[key].BoolValue;
}
}
class Application : IDTDRemoteConfigListener
{
public void Run()
{
// Set the maximum time of waiting for an A/B test group
DTDRemoteConfig.GroupDefinitionWaiting = Constants.WaitGroupConst;
// Set default values
AppConfg.LoadDafaults();
// Initialize the SDK for working with A/B testing
DTDAnalytics.InitializeWithAbTest("appKey", this);
// Your code to show UI
}
// Tutorial open event (e.g. by clicking the ‘start tutorial’ button)
public void StartTutorial()
{
// Send a trigger event that indicates tutorial start
DTDAnalytics.Tutorial(-1);
}
public void NextTutotrialStep(int step)
{
DTDAnalytics.Tutorial(step);
if (AppConfg.GetBoolValue(ValueKeys.ShowStore) && step == 5)
{
// Offer purchasing of a special offer (method do a job in UI thread)
UI.ShowSpecialOffer();
}
}
// Apply the values of the assigned group
public void OnChanged(DTDRemoteConfigChangeResult result, string error)
{
Debug.WriteLine($"[App-ABTests] OnChanged({result}, {error})");
switch (result)
{
case DTDRemoteConfigChangeResult.Failure:
// Error processing
break;
case DTDRemoteConfigChangeResult.Success:
// Apply new values
DTDRemoteConfig.ApplyConfig();
break;
default:
break;
}
// Hide the download progress indicator(method do a job in UI thread)
UI.HideLoadingIndicator();
}
// Prepare the app UI for changing the remote configuration
public void OnPrepareToChange()
{
Debug.WriteLine($"[App-ABTests] OnPrepareToChange()");
// Display the download progress indicator (method do a job in UI thread)
UI.ShowLoadingIndicator();
// Add a timer that will forcibly remove the download progress indicator
TimerUtil.CallWithDelay(Constants.WaitGroupConst, () =>
{
// Hide the download progress indicator(method do a job in UI thread)
UI.HideLoadingIndicator();
});
}
// Process the result of waiting for A/B test configuration
public void OnReceived(DTDRemoteConfigReceiveResult result)
{
// It is not used in current example
Debug.WriteLine($"[App-ABTests] OnReceived({result})");
}
}
// Timer constant
float WAIT_GROUP_TIME = 15.0f;
void SomeLogicClass::Start() {
// Set the maximum time of waiting for an A/B test group
UDTDRemoteConfigBPLibrary::SetGroupDefinitionWaiting(WAIT_GROUP_TIME);
// Set default values
FDTDRemoteConfigDefaults Defaults;
Defaults.BoolDefaults.Add("showStore", false);
UDTDRemoteConfigBPLibrary::SetDefaults(Defaults);
const auto onConfigReceive = new FDTDRemoteConfigReceiveResultDelegate();
onConfigReceive->BindUObject(this, &SomeLogicClass::OnConfigReceive);
const auto onPrepareToChange = new FDTDRemoteConfigPrepareToChangeDelegate();
onPrepareToChange->BindUObject(this, &SomeLogicClass::OnPrepareToChange);
const auto onConfigChange = new FDTDRemoteConfigChangeResultDelegate();
onConfigChange->BindUObject(this, &SomeLogicClass::OnConfigChange);
// Initialize the SDK for working with A/B testing
UDTDAnalyticsBPLibrary::InitializeWithAbTest("AppKey", *onConfigChange, *onPrepareToChange, *onConfigReceive);
}
// Tutorial open event (e.g. by clicking the ‘start tutorial’ button)
void SomeLogicClass::StartTutorial() {
// Send a trigger event that indicates tutorial start
UDTDAnalyticsBPLibrary::Tutorial(-1);
}
void SomeLogicClass::NextTutorialStep(int32 step)
{
UDTDAnalyticsBPLibrary::Tutorial(step);
bool isNeedShowStore = UDTDRemoteConfigBPLibrary::GetRemoteConfigValue("showStore").BoolValue;
if (isNeedShowStore && step == 5)
{
// Offer purchasing of a special offer
SomeStoreUI::ShowSpecialOffer();
}
}
// Process the result of waiting for A/B test configuration
void SomeLogicClass::OnConfigReceive(EDTDRemoteConfigReceiveResult result) {
// It is not used in current example
}
// Prepare the app UI for changing the remote configuration
void SomeLogicClass::OnPrepareToChange() {
// Display the download progress indicator
SomeUI::ShowLoadingIndicator(WAIT_GROUP_TIME);
}
// Apply the values of the assigned group
void SomeLogicClass::OnConfigChange(EDTDRemoteConfigChangeResult result, const FString& error) {
// Hide the download progress indicator
SomeUI::HideLoadingIndicator();
switch (result)
{
case EDTDRemoteConfigChangeResult::Success:
// Apply new values
UDTDRemoteConfigBPLibrary::ApplyConfig();
break;
case EDTDRemoteConfigChangeResult::Failure:
// Error processing
UE_LOG(LogTemp, Warning, TEXT("DTDRemoteConfigError: %s"), *error);
break;
default:
break;
}
}
# Control timer
var timer = Timer.new()
# Timer constant
var waitGroupConst = 10.0
var showStore = "showStore"
func loadDefaults():
var appDefaults = GDDTDRemoteConfigDefaults.new()
# Value key
appDefaults.AddBoolValue(showStore, false)
DTDRemoteConfig.SetDefaults(appDefaults)
func getBool(key: String) -> bool:
return DTDRemoteConfig.GetRemoteConfigValue(key).GetBoolValue()
# Tutorial open event (e.g. by clicking the ‘start tutorial’ button)
func startTutorial():
# Send a trigger event that indicates tutorial start
DTDAnalytics.Tutorial(-1)
func nextTutorialStep(currentStep: int):
DTDAnalytics.Tutorial(currentStep)
if (getBool(showStore) && currentStep == 5):
#Offer purchasing of a special offer
pass
func _ready():
# Set the maximum time of waiting for an A/B test group
DTDRemoteConfig.SetGroupDefinitionWaiting(waitGroupConst)
# Set default values
loadDefaults()
#Initialize the SDK for working with A/B testing
DTDAnalytics.InitializeWithAbTest("appKey",
onRemoteConfigChange,
onRemoteConfigPrepareToChange,
onRemoteConfigReceive)
DTDAnalytics.SetLogLevel(GDDTDLogLevel.Debug)
func onRemoteConfigChange(result: GDDTDRemoteConfigChangeResult.ChangeResult, error: String):
match result:
GDDTDRemoteConfigChangeResult.Success:
# Apply new values
DTDRemoteConfig.ApplyConfig()
GDDTDRemoteConfigChangeResult.Failure:
# Error processing
print(error)
timer.stop()
hideActivityIndicator()
# Prepare the app UI for changing the remote configuration
func onRemoteConfigPrepareToChange():
# Display the download progress indicator
showActivityIndicator()
# Add a timer that will forcibly remove the download progress indicator
timer.connect("timeout", hideActivityIndicator)
timer.one_shot = true
timer.wait_time = waitGroupConstInMilliseconds
add_child(timer)
timer.start()
# Process the result of waiting for A/B test configuration
func onRemoteConfigReceive(result: GDDTDRemoteConfigReceiveResult.ReceiveResult):
#It is not used in current example
pass
Our analytics data show that N users installed the app more than half a year ago but still did not make a purchase. We hypothesize that if we offer a huge discount then we can get additional income.
The criteria for test inclusion is the app install date and the “payer” status.
Control group: current version with usual prices
Group А: it has a discount badge on the main page. After clicking on the badge, a purchase window pops up
// Timer constants
struct Constants {
static let waitConfigConst = 10.0
static let waitGroupConst = 15.0
}
// Value keys
enum ValueKey: String {
case maximumDiscount
}
class AppConfig {
static func loadDefaults() {
let appDefaults: [String: Any] = [
ValueKey.maximumDiscount.rawValue: "false"
]
// Set default values
DTDRemoteConfig.defaults = appDefaults
}
static func bool(forKey key: ValueKey) -> Bool {
return DTDRemoteConfig.config[key.rawValue].boolValue
}
}
class AppLogic {
override func viewDidLoad() {
super.viewDidLoad()
// Display the launch screen
showLaunchScreen()
}
// Launching the main logic of the application
func startAppForReal() {
// Update app UI
updateAppUI()
// Hide launch screen
hideLaunchScreen()
}
func updateAppUI() {
if bool(forKey: .maximumDiscount) {
// Display a discount badge clicking on which invokes a purchase window popup
}
}
}
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication,
willFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Set the maximum time of waiting for the A/B test configuration
DTDRemoteConfig.remoteConfigWaiting = Constants.waitConfigConst
// Set the maximum time of waiting for an A/B test group
DTDRemoteConfig.groupDefinitionWaiting = Constants.waitGroupConst
// Set default values
AppConfig.loadDefaults()
// Initialize the SDK for working with A/B testing
DTDAnalytics.initializeWithAbTest(applicationKey: "appKey",
configuration: config,
abConfigListener: self)
}
}
extension AppLogic: DTDRemoteConfigListener {
// Process the result of waiting for A/B test configuration
func onReceived(result: DTDRemoteConfigReceiveResult) {
// If the attempt fails, launch the main logic of the application
if result == .failure {
DispatchQueue.main.async { [weak self] in
self?.startAppForReal()
}
}
}
// Prepare the app UI for changing the remote configuration
func onPrepareToChange() {
// It is not used in current example
}
// Apply the values of the assigned group
func onChanged(result: DTDRemoteConfigChangeResult, error: Error?) {
defer {
// Launch the main logic of the application
DispatchQueue.main.async { [weak self] in
self?.startAppForReal()
}
}
switch result {
case .success:
// Apply new values
DTDRemoteConfig.applyConfig()
case .failure:
// Error processing
if let error = error {
print(error.localizedDescription)
}
@unknown default:
break
}
}
}
Note
When receiving the configuration, the SDK always calls the onReceived(result: DTDRemoteConfigReceiveResult)
method, and when enrolling in a test - the onPrepareToChange()
and onChanged(result: DTDRemoteConfigChangeResult, error: Error?)
method. However, you can take some additional precocious measures. You can add a timer as in the following example:
func showLaunchScreen() {
// Add a timer that will forcibly launch the main logic of the application
timer = Timer.scheduledTimer(withTimeInterval: waitConfigConst + waitGroupConst,
repeats: false) { [weak self] _ in
self?.startAppForReal()
}
showActivityIndicator()
}
// Constants .h + .m
@interface Constants : NSObject
// Timer constants
extern double const waitConfigConst;
extern double const waitGroupConst;
// Value keys
extern NSString * const maximumDiscount;
@end
@implementation Constants
double const waitConfigConst = 10.0;
double const waitGroupConst = 15.0;
NSString * const maximumDiscount = @"maximumDiscount";
@end
// AppConfig .h + .m
@interface AppConfig: NSObject
+(void) loadDefaults;
+(BOOL) getBoolForKey:(NSString *) key;
@end
@implementation AppConfig
+(void)loadDefaults {
NSDictionary *appDefaults = @{
maximumDiscount: @NO
};
// Set default values
DTDRemoteConfig.defaults = appDefaults;
}
+(BOOL) getBoolForKey:(NSString *) key {
return DTDRemoteConfig.config[key].boolValue;
}
@end
// AppLogic .h + .m
@interface AppLogic : UIViewController <DTDRemoteConfigListener>
@end
@implementation AppLogic
- (void)viewDidLoad {
[super viewDidLoad];
// Display the launch screen
[self showLaunchScreen];
}
// Launching the main logic of the application
- (void) startAppForReal {
// Update app UI
[self updateAppUI];
// Hide launch screen
[self hideLaunchScreen];
}
- (void) updateAppUI {
if ([AppConfig getBoolForKey:maximumDiscount]) {
// Display a discount badge clicking on which invokes a purchase window popup
}
}
// Process the result of waiting for A/B test configuration
- (void)onReceivedResult:(enum DTDRemoteConfigReceiveResult)result {
// If the attempt fails, launch the main logic of the application
if (result == DTDRemoteConfigReceiveResultFailure) {
__weak AppLogic *weakSelf = self;
dispatch_async(dispatch_get_main_queue(), ^{
[weakSelf startAppForReal];
});
}
}
// Prepare the app UI for changing the remote configuration
- (void)onPrepareToChange {
// It is not used in current example
}
// Apply the values of the assigned group
- (void)onChangedResult:(enum DTDRemoteConfigChangeResult)result error:(NSError *)error {
switch (result) {
case DTDRemoteConfigChangeResultSuccess:
// Apply new values
[DTDRemoteConfig applyConfig];
break;
case DTDRemoteConfigChangeResultFailure:
// Error processing
if (error) {
NSLog(@"DTDRemoteConfigError: %@", error.localizedDescription);
}
default:
break;
}
// Launch the main logic of the application
__weak AppLogic *weakSelf = self;
dispatch_async(dispatch_get_main_queue(), ^{
[weakSelf startAppForReal];
});
}
- (void)showLaunchScreen {
// Display the launch screen
}
- (void)hideLaunchScreen {
// Hide launch screen
}
@end
// AppDelegate .h + .m
@interface AppDelegate ()
@end
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
AppLogic *appLogicController = [[UIStoryboard storyboardWithName:@"Main" bundle:nil] instantiateViewControllerWithIdentifier:@"AppLogic"];
UINavigationController *navController = [[UINavigationController alloc]initWithRootViewController:appLogicController];
self.window.rootViewController = navController;
[self.window makeKeyAndVisible];
// Config timeout, optional
DTDRemoteConfig.remoteConfigWaiting = waitConfigConst;
// Group timeout, optional
DTDRemoteConfig.groupDefinitionWaiting = waitGroupConst;
// Implementation defaults params
[AppConfig loadDefaults];
[DTDAnalytics applicationKey:appKey abConfigListener:appLogicController];
return YES;
}
@end
Note
When receiving the configuration, the SDK always calls the onReceivedResult
method, and when enrolling in a test - the onPrepareToChange
and onChangedResult
method. However, you can take some additional precocious measures. You can add a timer as in the following example:
-(void) showLaunchScreen {
// Add a timer that will forcibly launch the main logic of the application
self.timer = [NSTimer scheduledTimerWithTimeInterval:waitConfigConst + waitGroupConst
repeats:false
block:^(NSTimer *timer){
[weakSelf startAppForReal];
}];
showActivityIndicator()
}
class Constants {
// Timer constants
companion object {
const val waitConfigConst = 10.0
const val waitConfigConstInMilliseconds = 10000L
const val waitGroupConst = 15.0
const val waitGroupConstInMilliseconds = 15000L
}
}
// Value keys
enum class ValueKey(val value: String) {
MaximumDiscount("maximumDiscount")
}
class AppConfig {
companion object {
fun loadDefaults() {
val appDefaults = mapOf<String, Any>(ValueKey.MaximumDiscount.value to "false")
// Set default values
DTDRemoteConfig.defaults = appDefaults
}
fun bool(key: ValueKey): Boolean {
return DTDRemoteConfig.config[key.value].booleanValue
}
}
}
class AppLogic {
fun viewDidLoad() {
// Display the launch screen
showLaunchScreen()
}
// Launching the main logic of the application
fun startAppForReal() {
// Update app UI
updateAppUI()
// Hide launch screen
hideLaunchScreen()
}
fun updateAppUI() {
if (AppConfig.bool(ValueKey.MaximumDiscount)) {
// Display a discount badge clicking on which invokes a purchase window popup
}
}
}
class MainActivity : AppCompatActivity(), DTDRemoteConfigListener {
//activityIndicator control timer
var timer: Timer? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState, persistentState)
setContentView(R.layout.activity_main)
// Set the maximum time of waiting for the A/B test configuration
DTDRemoteConfig.remoteConfigWaiting = Constants.waitConfigConst
// Set the maximum time of waiting for an A/B test group
DTDRemoteConfig.groupDefinitionWaiting = Constants.waitGroupConst
// Set default values
AppConfig.loadDefaults()
// Initialize the SDK for working with A/B testing
DTDAnalytics.initializeWithAbTest(
appKey = "appKey",
context = this,
abConfigListener = this
)
}
// Process the result of waiting for A/B test configuration
override fun onReceived(result: DTDRemoteConfigReceiveResult) {
// If the attempt fails, launch the main logic of the application
if (result == DTDRemoteConfigReceiveResult.Failure) {
runOnUiThread {
startAppForReal()
}
}
}
// Prepare the app UI for changing the remote configuration
override fun onPrepareToChange() {
// It is not used in current example
}
override fun onChanged(result: DTDRemoteConfigChangeResult, ex: Exception?) {
when (result) {
DTDRemoteConfigChangeResult.Success -> {
// Apply new values
DTDRemoteConfig.applyConfig()
}
DTDRemoteConfigChangeResult.Failure -> {
// Error processing
ex?.let { Log.e("TAG", ex.toString()) }
}
}
runOnUiThread {
startAppForReal()
}
}
}
Note
When receiving the configuration, the SDK always calls the onReceivedResult
method, and when enrolling in a test - the onPrepareToChange
and onChangedResult
method. However, you can take some additional precocious measures. You can add a timer as in the following example:
fun showLaunchScreen() {
// Add a timer that will forcibly launch the main logic of the application
tiemr = Timer("ActivityIndicator").schedule(
Constants.waitConfigConstInMilliseconds +
Constants.waitGroupConstInMilliseconds
) {
runOnUiThread {
startAppForReal()
}
}
showActivityIndicator()
}
class Constants {
// Timer constants
final static double waitGroupConst = 10.0;
final static long waitGroupConstInMilliseconds = 10000L;
final static double waitConfigConst = 15.0;
final static long waitConfigConstInMilliseconds = 15000L;
}
enum ValueKey {
MaximumDiscount("maximumDiscount");
private final String stringValue;
ValueKey(String toString) {
stringValue = toString;
}
@Override
public String toString() {
return stringValue;
}
}
class AppConfig {
static void loadDefaults() {
HashMap<String, Object> map = new HashMap<>();
map.put(ValueKey.MaximumDiscount.toString(), "false");
DTDRemoteConfig.INSTANCE.setDefaults(map);
}
static Boolean bool(ValueKey key) {
return DTDRemoteConfig.INSTANCE.getConfig().get(key.toString()).getBooleanValue();
}
}
class AppLogic {
static void viewDidLoad() {
// Display the launch screen
showLaunchScreen();
}
// Launching the main logic of the application
static void startAppForReal() {
// Update app UI
updateAppUI();
// Hide launch screen
hideLaunchScreen();
}
static void updateAppUI() {
if (AppConfig.bool(ValueKey.MaximumDiscount)) {
// Display a discount badge clicking on which invokes a purchase window popup
}
}
}
class MainActivity extends AppCompatActivity implements DTDRemoteConfigListener {
//activityIndicator control timer
Timer timer = null;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Set the maximum time of waiting for the A/B test configuration
DTDRemoteConfig.INSTANCE.setRemoteConfigWaiting(Constants.waitConfigConst);
// Set the maximum time of waiting for an A/B test group
DTDRemoteConfig.INSTANCE.setGroupDefinitionWaiting(Constants.waitGroupConst);
// Set default values
AppConfig.loadDefaults();
// Initialize the SDK for working with A/B testing
DTDAnalytics.INSTANCE.initializeWithAbTest("appKey", this, this);
}
// Process the result of waiting for A/B test configuration
@Override
public void onReceived(@NonNull DTDRemoteConfigReceiveResult result) {
// If the attempt fails, launch the main logic of the application
if (result == DTDRemoteConfigReceiveResult.Failure) {
runOnUiThread(AppLogic::startAppForReal);
}
}
// Prepare the app UI for changing the remote configuration
@Override
public void onPrepareToChange() {
// It is not used in current example
}
@Override
public void onChanged(@NonNull DTDRemoteConfigChangeResult result, @Nullable Exception ex) {
if (result == DTDRemoteConfigChangeResult.Success) {
// Apply new values
DTDRemoteConfig.INSTANCE.applyConfig();
}
if (result == DTDRemoteConfigChangeResult.Failure) {
// Error processing
if (ex != null) {
Log.e("TAG", ex.toString());
}
}
if (timer != null) {
timer.cancel();
timer = null;
}
runOnUiThread(AppLogic::startAppForReal);
}
}
Note
When receiving the configuration, the SDK always calls the onReceivedResult
method, and when enrolling in a test - the onPrepareToChange
and onChangedResult
method. However, you can take some additional precocious measures. You can add a timer as in the following example:
void showLaunchScreen() {
// Add a timer that will forcibly launch the main logic of the application
timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
runOnUiThread(AppLogic::startAppForReal);
}
}, Constants.waitConfigConstInMilliseconds +
Constants.waitGroupConstInMilliseconds);
showActivityIndicator();
}
// Timer constants
public static class Constants
{
public static float WAIT_GROUP_TIME = 10.0f;
public static float WAIT_CONFIG_TIME = 15.0f;
}
// Value keys
public enum ValueKey
{
maximumDiscount
}
public class AppConfig
{
public void LoadDefaults()
{
var appDefaults = new Dictionary<string, object>
{
{ValueKey.maximumDiscount.ToString(), false}
};
// Set default values
DTDRemoteConfig.Defaults = appDefaults;
}
public bool GetBool(ValueKey key) => DTDRemoteConfig.Config[key.ToString()].BoolValue();
}
public class AppLogic : MonoBehaviour, IDTDRemoteConfigListener
{
private readonly AppConfig _appConfig = new AppConfig();
private const string APP_KEY = "appKey";
private SimpleUI _simpleUI;
private void Start()
{
DontDestroyOnLoad(this);
_simpleUI = FindObjectOfType<SimpleUI>();
if (_simpleUI == null) throw new NullReferenceException("UIManager not found.");
// Set the maximum time of waiting for an A/B test group
DTDRemoteConfig.GroupDefinitionWaiting = Constants.WAIT_GROUP_TIME;
// Set the maximum time of waiting for the A/B test configuration
DTDRemoteConfig.RemoteConfigWaiting = Constants.WAIT_CONFIG_TIME;
// Set default values
_appConfig.LoadDefaults();
// Initialize the SDK for working with A/B testing
DTDAnalytics.InitializeWithAbTests(
appKey: APP_KEY,
configListener: this);
// Show the loading indicator with timer.
_simpleUI.ShowLoadingIndicator(Constants.WAIT_GROUP_TIME + Constants.WAIT_CONFIG_TIME);
}
public void UpdateAppUI()
{
if (_appConfig.GetBool(ValueKey.maximumDiscount))
{
// Display a discount badge clicking on which invokes a purchase window popup
}
}
// Process the result of waiting for A/B test configuration.
public void OnReceived(DTDRemoteConfigReceiveResult result)
{
// If the attempt fails, hide the loading indicator and stop timer.
if (result == DTDRemoteConfigReceiveResult.Failure)
{
_simpleUI.HideLoadingIndicator();
}
}
// Prepare the app UI for changing the remote configuration
public void OnPrepareToChange()
{
// It is not used in current example
}
// Apply the values of the assigned group
public void OnChanged(DTDRemoteConfigChangeResult result, string exceptionText = null)
{
// Hide the loading indicator and stop timer.
_simpleUI.HideLoadingIndicator();
switch (result)
{
case DTDRemoteConfigChangeResult.Failure:
// Error processing
if (exceptionText != null) Debug.LogError(exceptionText);
break;
case DTDRemoteConfigChangeResult.Success:
// Apply new values
DTDRemoteConfig.ApplyConfig();
break;
}
UpdateAppUI();
}
}
Note
When receiving the configuration, the SDK always calls the OnReceived(DTDRemoteConfigReceiveResult result)
method, and when enrolling in a test - the OnPrepareToChange()
and OnChanged(DTDRemoteConfigChangeResult result, string exceptionText = null)
method. However, you can take some additional precocious measures. You can add a timer as in the following example:
public class SimpleUI : MonoBehaviour
{
[SerializeField] private Canvas loadingIndicator;
private Coroutine _timer;
private IEnumerator HideAfterTimeout(float timeout, Canvas target)
{
yield return new WaitForSeconds(timeout);
target.gameObject.SetActive(false);
}
public void ShowLoadingIndicator(float? timeout = null)
{
loadingIndicator.gameObject.SetActive(true);
if (timeout != null)
_timer = StartCoroutine(HideAfterTimeout(timeout.Value, loadingIndicator));
}
public void HideLoadingIndicator()
{
loadingIndicator.gameObject.SetActive(false);
if (_timer != null) StopCoroutine(_timer);
}
}
using DevToDev.Analytics;
using System.Collections.Generic;
using System.Diagnostics;
// Timer constants
static class Constants
{
public const float WaitConfigConst = 10.0f;
public const float WaitGroupConst = 15.0f;
}
// Value keys
static class ValueKeys
{
public const string MaximumDiscount = "maximumDiscount";
}
static class AppConfg
{
public static void LoadDafaults()
{
// Set default values
DTDRemoteConfig.Defaults = new Dictionary<string, object>
{
[ValueKeys.MaximumDiscount] = false
};
}
public static bool GetBoolValue(string key)
{
return DTDRemoteConfig.Config[key].BoolValue;
}
}
class Application : IDTDRemoteConfigListener
{
public void Run()
{
// Set the maximum time of waiting for an A/B test group
DTDRemoteConfig.RemoteConfigWaiting = Constants.WaitConfigConst;
// Set the maximum time of waiting for the A/B test configuration
DTDRemoteConfig.GroupDefinitionWaiting = Constants.WaitGroupConst;
// Set default values
AppConfg.LoadDafaults();
// Initialize the SDK for working with A/B testing
DTDAnalytics.InitializeWithAbTest("appKey", this);
// Show launch screen (method do a job in UI thread)
UI.ShowLaunchScreen();
}
public void ShowMainScene()
{
// Show main screen (also hide launch screen) (method do a job in UI thread)
UI.ShowMainScreen();
if (AppConfg.GetBoolValue(ValueKeys.MaximumDiscount))
{
// Display a discount badge clicking on which invokes a purchase window popup
// (method do a job in UI thread)
UI.ShowMaximumDiscount();
}
}
// Apply the values of the assigned group
public void OnChanged(DTDRemoteConfigChangeResult result, string error)
{
Debug.WriteLine($"[App-ABTests] OnChanged({result}, {error})");
switch (result)
{
case DTDRemoteConfigChangeResult.Failure:
// Error processing
break;
case DTDRemoteConfigChangeResult.Success:
// Apply new values
DTDRemoteConfig.ApplyConfig();
break;
default:
break;
}
// Show main screen (also hide launch screen)
ShowMainScene();
}
// Prepare the app UI for changing the remote configuration
public void OnPrepareToChange()
{
// It is not used in current example
Debug.WriteLine($"[App-ABTests] OnPrepareToChange()");
}
// Process the result of waiting for A/B test configuration.
public void OnReceived(DTDRemoteConfigReceiveResult result)
{
Debug.WriteLine($"[App-ABTests] OnReceived({result})");
// If the attempt fails, show main screen (also hide launch screen)
if (result == DTDRemoteConfigReceiveResult.Failure)
{
ShowMainScene();
}
}
}
Note
When receiving the configuration, the SDK always calls the OnReceived(DTDRemoteConfigReceiveResult result)
method, and when enrolling in a test - the OnPrepareToChange()
and OnChanged(DTDRemoteConfigChangeResult result, string error)
. However, you can take some additional precocious measures. You can add a timer as in the following example:
public void showLaunchScreen() {
// Add a timer that will forcibly launch the main logic of the application
TimerUtil.CallWithDelay(Constants.WaitConfigConst + Constants.WaitGroupConst, () =>
{
UI.StartAppForReal();
});
UI.ShowActivityIndicator();
}
// Timer constants
static float WAIT_CONFIG_TIME = 15.0f;
static float WAIT_GROUP_TIME = 10.0f;
void SomeLogicClass::Start() {
// Set the maximum time of waiting for an A/B test group
UDTDRemoteConfigBPLibrary::SetRemoteConfigWaiting(WAIT_CONFIG_TIME);
// Set the maximum time of waiting for the A/B test configuration
DTDRemoteConfigBPLibrary::SetGroupDefinitionWaiting(WAIT_GROUP_TIME);
// Set default values
FDTDRemoteConfigDefaults Defaults;
Defaults.BoolDefaults.Add("maximumDiscount", false);
UDTDRemoteConfigBPLibrary::SetDefaults(Defaults);
const auto onConfigReceive = new FDTDRemoteConfigReceiveResultDelegate();
onConfigReceive->BindUObject(this, &SomeLogicClass::OnConfigReceive);
const auto onPrepareToChange = new FDTDRemoteConfigPrepareToChangeDelegate();
onPrepareToChange->BindUObject(this, &SomeLogicClass::OnPrepareToChange);
const auto onConfigChange = new FDTDRemoteConfigChangeResultDelegate();
onConfigChange->BindUObject(this, &SomeLogicClass::OnConfigChange);
// Initialize the SDK for working with A/B testing
UDTDAnalyticsBPLibrary::InitializeWithAbTest("AppKey", *onConfigChange, *onPrepareToChange, *onConfigReceive);
// Show the loading indicator with timer.
SomeUI::showLaunchScreen(WAIT_CONFIG_TIME + WAIT_GROUP_TIME);
}
void SomeLogicClass::UpdateAppUI()
{
if (UDTDRemoteConfigBPLibrary::GetRemoteConfigValue("maximumDiscount").BoolValue)
{
// Display a discount badge clicking on which invokes a purchase window popup
}
}
// Process the result of waiting for A/B test configuration
void SomeLogicClass::OnConfigReceive(EDTDRemoteConfigReceiveResult result) {
// If the attempt fails, hide the loading indicator and stop timer.
if (result == EDTDRemoteConfigReceiveResult::Failure)
{
SomeUI::HideLoadingIndicator();
}
}
// Prepare the app UI for changing the remote configuration
void SomeLogicClass::OnPrepareToChange() {
// It is not used in current example
}
// Apply the values of the assigned group
void SomeLogicClass::OnConfigChange(EDTDRemoteConfigChangeResult result, const FString& error) {
// Hide the loading indicator and stop timer.
SomeUI::HideLoadingIndicator();
switch (result)
{
case EDTDRemoteConfigChangeResult::Success:
// Apply new values
UDTDRemoteConfigBPLibrary::ApplyConfig();
break;
case EDTDRemoteConfigChangeResult::Failure:
// Error processing
UE_LOG(LogTemp, Warning, TEXT("DTDRemoteConfigError: %s"), *error);
break;
default:
break;
}
UpdateAppUI();
}
# Control timer
var timer = Timer.new()
# Timer constants
var waitConfigConst = 10.0
var waitGroupConst = 15.0
var maximumDiscount = "maximumDiscount"
func loadDefaults():
var appDefaults = GDDTDRemoteConfigDefaults.new()
# Set default values
appDefaults.AddBoolValue(maximumDiscount, false)
DTDRemoteConfig.SetDefaults(appDefaults)
func getBool(key: String) -> bool:
return DTDRemoteConfig.GetRemoteConfigValue(key).GetBoolValue()
func viewDidLoad():
#Display the launch screen
showLaunchScreen()
#Launching the main logic of the application
func startAppForReal():
# Update app UI
updateAppUI()
# Hide launch screen
hideLaunchScreen()
func updateAppUI() :
if getBool(maximumDiscount):
# Display a discount badge clicking on which invokes a purchase window popup
pass
func _ready():
# Set the maximum time of waiting for the A/B test configuration
DTDRemoteConfig.SetRemoteConfigWaiting(waitConfigConst)
# Set the maximum time of waiting for an A/B test group
DTDRemoteConfig.SetGroupDefinitionWaiting(waitGroupConst)
# Set default values
loadDefaults()
# Initialize the SDK for working with A/B testing
var config = GDDTDAnalyticsConfiguration.new()
DTDAnalytics.InitializeWithConfigWithAbTest("appKey",
config,
onRemoteConfigChange,
onRemoteConfigPrepareToChange,
onRemoteConfigReceive)
func onRemoteConfigChange(result: GDDTDRemoteConfigChangeResult.ChangeResult, error: String):
match result:
GDDTDRemoteConfigChangeResult.Success:
# Apply new values
DTDRemoteConfig.ApplyConfig()
GDDTDRemoteConfigChangeResult.Failure:
# Error processing
print(error)
startAppForReal()
# Prepare the app UI for changing the remote configuration
func onRemoteConfigPrepareToChange():
# It is not used in current example
pass
# Process the result of waiting for A/B test configuration
func onRemoteConfigReceive(result: GDDTDRemoteConfigReceiveResult.ReceiveResult):
if (result == GDDTDRemoteConfigReceiveResult.Failure):
# If the attempt fails, launch the main logic of the application
startAppForReal()
func showLaunchScreen():
# Add a timer that will forcibly launch the main logic of the application
timer.connect("ActivityIndicator", startAppForReal)
timer.one_shot = true
timer.wait_time = waitConfigConst + waitGroupConst
add_child(timer)
timer.start()
showActivityIndicator()
Our analytics data show that only 60% of our users complete the tutorial. We hypothesize that if we make it easier (group A) or reduce the number of seps (group B), we will increase the percentage of tutorial completion.
New users who have not started the tutorial yet.
Control group: current version with usual difficulty and usual number of steps
Group А: low difficulty, usual number of steps
Group B: usual difficulty, few steps
// Timer constants
struct Constants {
static let waitConfigConst = 10.0
}
// Value keys
enum ValueKey: String {
case difficulty
case stepCount
}
class AppConfig {
static func loadDefaults() {
func loadDefaultValues() {
let appDefaults: [String: Any] = [
ValueKey.difficulty.rawValue: 3,
ValueKey.stepCount.rawValue: 10
]
// Set default values
DTDRemoteConfig.defaults = appDefaults
}
func integer(forKey key: ValueKey) -> Int {
DTDRemoteConfig.config[key.rawValue].integerValue
}
}
class AppLogic {
// Tutorial open event (e.g. by clicking the ‘start tutorial’ button)
func startTutorial() {
// Display the download progress indicator
showActivityIndicator()
// Send a trigger event that indicates tutorial start
DTDAnalytics.tutorial(step: -1)
}
// Initiate tutorial completion
func realStartTutorial() {
// Hide the download progress indicator
hideActivityIndicator()
// Initiate the tutorial by using the remote values (difficulty and stepCount)
runTutorial(stepCount: integer(forKey: .stepCount),
difficulty: integer(forKey: .difficulty))
}
}
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication,
willFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Set the maximum time of waiting for the A/B test configuration
DTDRemoteConfig.remoteConfigWaiting = Constants.waitConfigConst
// Set default values
AppConfig.loadDefaults()
// Initialize the SDK for working with A/B testing
DTDAnalytics.initializeWithAbTest(applicationKey: "appKey",
configuration: config,
abConfigListener: self)
}
}
extension AppLogic: DTDRemoteConfigListener {
// Process the result of waiting for A/B test configuration
func onReceived(result: DTDRemoteConfigReceiveResult) {
// It is not used in current example
}
// Prepare the app UI for changing the remote configuration
func onPrepareToChange() {
// It is not used in current example
}
// Apply the values of the assigned group
func onChanged(result: DTDRemoteConfigChangeResult, error: Error?) {
defer {
// Initiate tutorial completion
DispatchQueue.main.async { [weak self] in
self?.realStartTutorial()
}
}
switch result {
case .success:
// Apply new values
DTDRemoteConfig.applyConfig()
case .failure:
// Error processing
if let error = error {
print(error.localizedDescription)
}
@unknown default:
break
}
}
}
// Constants .h + .m
@interface Constants : NSObject
// Timer constants
extern double const waitConfigConst;
// Value keys
extern NSString * const difficulty;
extern NSString * const stepCount;
@end
@implementation Constants
double const waitConfigConst = 10.0;
NSString * const difficulty = @"difficulty";
NSString * const stepCount = @"stepCount";
@end
// AppConfig .h + .m
@interface AppConfig: NSObject
+(void) loadDefaults;
+(NSInteger) getIntegerForKey:(NSString *) key;
@end
@implementation AppConfig
+(void)loadDefaults {
NSDictionary *appDefaults = @{
difficulty: @3,
stepCount: @10
};
// Set default values
DTDRemoteConfig.defaults = appDefaults;
}
+(NSInteger) getIntegerForKey:(NSString *) key {
return DTDRemoteConfig.config[key].integerValue;
}
@end
// AppLogic .h + .m
@interface AppLogic : UIViewController <DTDRemoteConfigListener>
@end
@implementation AppLogic
- (void)viewDidLoad {
[super viewDidLoad];
// Display the launch screen
[self startTutorial];
}
// Tutorial open event (e.g. by clicking the ‘start tutorial’ button)
- (void) startTutorial {
// Display the download progress indicator
[self showActivityIndicator];
// Send a trigger event that indicates tutorial start
[DTDAnalytics tutorialStep:-1];
}
// Initiate tutorial completion
- (void) realStartTutorial {
// Hide the download progress indicator
[self hideActivityIndicator];
// Initiate the tutorial by using the remote values (difficulty and stepCount)
[self runTutorial: [AppConfig getIntegerForKey:stepCount]
withDifficulty: [AppConfig getIntegerForKey:difficulty]];
}
- (void) runTutorial:(NSInteger) stepCount withDifficulty:(NSInteger) difficulty {
// Start tutorial
}
// Process the result of waiting for A/B test configuration
- (void)onReceivedResult:(enum DTDRemoteConfigReceiveResult)result {
// It is not used in current example
}
// Prepare the app UI for changing the remote configuration
- (void)onPrepareToChange {
// It is not used in current example
}
// Apply the values of the assigned group
- (void)onChangedResult:(enum DTDRemoteConfigChangeResult)result error:(NSError *)error {
switch (result) {
case DTDRemoteConfigChangeResultSuccess:
// Apply new values
[DTDRemoteConfig applyConfig];
break;
case DTDRemoteConfigChangeResultFailure:
// Error processing
if (error) {
NSLog(@"DTDRemoteConfigError: %@", error.localizedDescription);
}
default:
break;
}
// Initiate tutorial completion
__weak AppLogic *weakSelf = self;
dispatch_async(dispatch_get_main_queue(), ^{
[weakSelf realStartTutorial];
});
}
- (void)showActivityIndicator {
// Display the launch screen
}
- (void)hideActivityIndicator {
// Hide launch screen
}
@end
// AppDelegate .h + .m
@interface AppDelegate ()
@end
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
AppLogic *appLogicController = [[UIStoryboard storyboardWithName:@"Main" bundle:nil] instantiateViewControllerWithIdentifier:@"AppLogic"];
UINavigationController *navController = [[UINavigationController alloc]initWithRootViewController:appLogicController];
self.window.rootViewController = navController;
[self.window makeKeyAndVisible];
// Set the maximum time of waiting for the A/B test configuration
DTDRemoteConfig.remoteConfigWaiting = waitConfigConst;
// Implementation defaults params
[AppConfig loadDefaults];
[DTDAnalytics applicationKey:appKey abConfigListener:appLogicController];
return YES;
}
@end
class Constants {
// Timer constants
companion object {
const val waitConfigConst = 10.0
const val waitConfigConstInMilliseconds = 10000L
}
}
enum class ValueKey(val value: String) {
Difficulty("difficulty"),
StepCount("stepCount")
}
class AppConfig {
companion object {
fun loadDefaults() {
val appDefaults = mapOf<String, Any>(
ValueKey.Difficulty.value to 3,
ValueKey.StepCount.value to 10
)
// Set default values
DTDRemoteConfig.defaults = appDefaults
}
fun integer(key: ValueKey): Int {
return DTDRemoteConfig.config[key.value].intValue
}
}
}
class AppLogic {
// Tutorial open event (e.g. by clicking the ‘start tutorial’ button)
fun startTutorial() {
// Display the download progress indicator
showActivityIndicator()
// Send a trigger event that indicates tutorial start
DTDAnalytics.tutorial(step = -1)
}
// Initiate tutorial completion
fun realStartTutorial() {
// Hide the download progress indicator
hideActivityIndicator()
// Initiate the tutorial by using the remote values (difficulty and stepCount)
runTutorial(
stepCount = AppConfig.integer(ValueKey.StepCount),
difficulty = AppConfig.integer(ValueKey.Difficulty)
)
}
}
class MainActivity : AppCompatActivity(), DTDRemoteConfigListener {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState, persistentState)
setContentView(R.layout.activity_main)
// Set the maximum time of waiting for the A/B test configuration
DTDRemoteConfig.remoteConfigWaiting = Constants.waitConfigConst
// Set default values
AppConfig.loadDefaults()
// Initialize the SDK for working with A/B testing
DTDAnalytics.initializeWithAbTest(
appKey = "appKey",
context = this,
abConfigListener = this
)
}
// Process the result of waiting for A/B test configuration
override fun onReceived(result: DTDRemoteConfigReceiveResult) {
// It is not used in current example
}
// Prepare the app UI for changing the remote configuration
override fun onPrepareToChange() {
// It is not used in current example
}
override fun onChanged(result: DTDRemoteConfigChangeResult, ex: Exception?) {
when (result) {
DTDRemoteConfigChangeResult.Success -> {
// Apply new values
DTDRemoteConfig.applyConfig()
}
DTDRemoteConfigChangeResult.Failure -> {
// Error processing
ex?.let { Log.e("TAG", ex.toString()) }
}
}
runOnUiThread {
realStartTutorial()
}
}
}
class Constants {
// Timer constants
final static double waitGroupConst = 10.0;
final static long waitGroupConstInMilliseconds = 10000L;
}
enum ValueKey {
Difficulty("difficulty"),
StepCount("stepCount");
private final String stringValue;
ValueKey(String toString) {
stringValue = toString;
}
@Override
public String toString() {
return stringValue;
}
}
class AppConfig {
static void loadDefaults() {
HashMap<String, Object> map = new HashMap<>();
map.put(ValueKey.Difficulty.toString(), 3);
map.put(ValueKey.StepCount.toString(), 10);
DTDRemoteConfig.INSTANCE.setDefaults(map);
}
static Integer integer(ValueKey key) {
return DTDRemoteConfig.INSTANCE.getConfig().get(key.toString()).getIntValue();
}
}
class AppLogic {
// Tutorial open event (e.g. by clicking the ‘start tutorial’ button)
static void startTutorial() {
// Display the download progress indicator
showActivityIndicator();
// Send a trigger event that indicates tutorial start
DTDAnalytics.INSTANCE.tutorial(-1);
}
// Initiate tutorial completion
static void realStartTutorial() {
// Hide the download progress indicator
hideActivityIndicator();
// Initiate the tutorial by using the remote values (difficulty and stepCount)
runTutorial(
AppConfig.integer(ValueKey.StepCount),
AppConfig.integer(ValueKey.Difficulty)
);
}
}
class MainActivity extends AppCompatActivity implements DTDRemoteConfigListener {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Set the maximum time of waiting for the A/B test configuration
DTDRemoteConfig.INSTANCE.setRemoteConfigWaiting(Constants.waitGroupConst);
// Set default values
AppConfig.loadDefaults();
// Initialize the SDK for working with A/B testing
DTDAnalytics.INSTANCE.initializeWithAbTest("appKey", this, this);
}
// Process the result of waiting for A/B test configuration
@Override
public void onReceived(@NonNull DTDRemoteConfigReceiveResult result) {
// It is not used in current example
}
// Prepare the app UI for changing the remote configuration
@Override
public void onPrepareToChange() {
// It is not used in current example
}
@Override
public void onChanged(@NonNull DTDRemoteConfigChangeResult result, @Nullable Exception ex) {
if (result == DTDRemoteConfigChangeResult.Success) {
// Apply new values
DTDRemoteConfig.INSTANCE.applyConfig();
}
if (result == DTDRemoteConfigChangeResult.Failure) {
// Error processing
if (ex != null) {
Log.e("TAG", ex.toString());
}
}
runOnUiThread(AppLogic::realStartTutorial);
}
}
// Timer constants
public static class Constants
{
public const float WAIT_CONFIG_TIME = 10.0f;
}
// Value keys
public enum ValueKey
{
difficulty,
stepCount
}
public class AppConfig
{
public void LoadDefaults()
{
var appDefaults = new Dictionary<string, object>
{
{ValueKey.difficulty.ToString(), 3},
{ValueKey.stepCount.ToString(), 10}
};
// Set default values
DTDRemoteConfig.Defaults = appDefaults;
}
public int GetInt(ValueKey key) => DTDRemoteConfig.Config[key.ToString()].IntValue();
}
public class AppLogic : MonoBehaviour, IDTDRemoteConfigListener
{
private readonly AppConfig _appConfig = new AppConfig();
private const string APP_KEY = "appKey";
private SimpleUI _simpleUI;
private void Start()
{
DontDestroyOnLoad(this);
_simpleUI = FindObjectOfType<SimpleUI>();
if (_simpleUI == null) throw new NullReferenceException("UIManager not found.");
// Set the maximum time of waiting for the A/B test configuration
DTDRemoteConfig.RemoteConfigWaiting = Constants.WAIT_CONFIG_TIME;
// Set default values
_appConfig.LoadDefaults();
// Initialize the SDK for working with A/B testing
DTDAnalytics.InitializeWithAbTests(
appKey: APP_KEY,
analyticsConfiguration: _analyticsConfiguration,
configListener: this);
}
// Tutorial open event (e.g. by clicking the ‘start tutorial’ button)
public void StartTutorial()
{
// Send a trigger event that indicates tutorial start
DTDAnalytics.Tutorial(-1);
// Display the download progress indicator
_simpleUI.ShowLoadingIndicator(Constants.WAIT_CONFIG_TIME);
}
// Initiate tutorial completion
private void RealStartTutorial()
{
// Hide the download progress indicator
_simpleUI.HideLoadingIndicator();
// Initiate the tutorial by using the remote values (difficulty and stepCount)
RunTutorial(
stepCount: DTDRemoteConfig.Config[ValueKey.stepCount].IntValue(),
difficulty: DTDRemoteConfig.Config[ValueKey.difficulty].IntValue()
);
}
// Process the result of waiting for A/B test configuration
public void OnReceived(DTDRemoteConfigReceiveResult result)
{
// It is not used in current example.
}
// Prepare the app UI for changing the remote configuration
public void OnPrepareToChange()
{
// It is not used in current example
}
// Apply the values of the assigned group
public void OnChanged(DTDRemoteConfigChangeResult result, string exceptionText = null)
{
switch (result)
{
case DTDRemoteConfigChangeResult.Failure:
// Error processing
if (exceptionText != null) Debug.LogError(exceptionText);
break;
case DTDRemoteConfigChangeResult.Success:
// Apply new values
DTDRemoteConfig.ApplyConfig();
break;
}
// Initiate tutorial completion
RealStartTutorial();
}
}
using DevToDev.Analytics;
using System.Collections.Generic;
using System.Diagnostics;
// Timer constants
static class Constants
{
public const float WaitConfigConst = 10.0f;
}
// Value keys
static class ValueKeys
{
public const string Difficulty = "difficulty";
public const string StepCount = "stepCount";
}
static class AppConfg
{
public static void LoadDafaults()
{
// Set default values
DTDRemoteConfig.Defaults = new Dictionary<string, object>
{
[ValueKeys.Difficulty] = 3,
[ValueKeys.StepCount] = 10
};
}
public static int GetIntValue(string key)
{
return DTDRemoteConfig.Config[key].Int32Value;
}
}
class Application : IDTDRemoteConfigListener
{
public void Run()
{
// Set the maximum time of waiting for the A/B test configuration
DTDRemoteConfig.RemoteConfigWaiting = Constants.WaitConfigConst;
// Set default values
AppConfg.LoadDafaults();
// Initialize the SDK for working with A/B testing
DTDAnalytics.InitializeWithAbTest("appKey", this);
// Prepare tutorial scene to show
PrepareTutorialScene();
}
// Tutorial open event (e.g. by clicking the ‘start tutorial’ button)
public void PrepareTutorialScene()
{
// Display the download progress indicator
UI.ShowLoadingIndicator();
// Send a trigger event that indicates tutorial start
DTDAnalytics.Tutorial(-1);
}
// Initiate tutorial completion
public void StartTutorialScene()
{
var difficulty = AppConfg.GetIntValue(ValueKeys.Difficulty);
var stepCount = AppConfg.GetIntValue(ValueKeys.StepCount);
// Hide the download progress indicator (method do a job in UI thread)
UI.HideLoadingIndicator();
// Initiate the tutorial by using the remote values (method do a job in UI thread)
UI.ShowTutorialScreen(difficulty, stepCount);
}
// Apply the values of the assigned group
public void OnChanged(DTDRemoteConfigChangeResult result, string error)
{
Debug.WriteLine($"[App-ABTests] OnChanged({result}, {error})");
switch (result)
{
case DTDRemoteConfigChangeResult.Failure:
// Error processing
break;
case DTDRemoteConfigChangeResult.Success:
// Apply new values
DTDRemoteConfig.ApplyConfig();
break;
default:
break;
}
// Initiate tutorial completion
StartTutorialScene();
}
// Prepare the app UI for changing the remote configuration
public void OnPrepareToChange()
{
Debug.WriteLine($"[App-ABTests] OnPrepareToChange()");
// It is not used in current example
}
// Process the result of waiting for A/B test configuration
public void OnReceived(DTDRemoteConfigReceiveResult result)
{
Debug.WriteLine($"[App-ABTests] OnReceived({result})");
// It is not used in current example
}
}
// Timer constant
const float WAIT_CONFIG_TIME = 10.0f;
void SomeLogicClass::Start() {
// Set the maximum time of waiting for the A/B test configuration
DTDRemoteConfigBPLibrary::SetRemoteConfigWaiting(WAIT_CONFIG_TIME);
// Set default values
FDTDRemoteConfigDefaults Defaults;
Defaults.IntegerDefaults.Add("difficulty", 3);
Defaults.IntegerDefaults.Add("stepCount", 10);
UDTDRemoteConfigBPLibrary::SetDefaults(Defaults);
const auto onConfigReceive = new FDTDRemoteConfigReceiveResultDelegate();
onConfigReceive->BindUObject(this, &SomeLogicClass::OnConfigReceive);
const auto onPrepareToChange = new FDTDRemoteConfigPrepareToChangeDelegate();
onPrepareToChange->BindUObject(this, &SomeLogicClass::OnPrepareToChange);
const auto onConfigChange = new FDTDRemoteConfigChangeResultDelegate();
onConfigChange->BindUObject(this, &SomeLogicClass::OnConfigChange);
// Initialize the SDK for working with A/B testing
UDTDAnalyticsBPLibrary::InitializeWithAbTest("AppKey", *onConfigChange, *onPrepareToChange, *onConfigReceive);
}
// Tutorial open event (e.g. by clicking the ‘start tutorial’ button)
void SomeLogicClass::StartTutorial()
{
// Send a trigger event that indicates tutorial start
UDTDAnalyticsBPLibrary::Tutorial(-1);
// Display the download progress indicator
SomeUI::ShowLoadingIndicator(WAIT_CONFIG_TIME);
}
// Initiate tutorial completion
void SomeLogicClass::RealStartTutorial()
{
// Hide the download progress indicator
SomeUI::HideLoadingIndicator();
// Initiate the tutorial by using the remote values (difficulty and stepCount)
RunTutorial(UDTDRemoteConfigBPLibrary::GetRemoteConfigValue("difficulty").IntegerValue,
UDTDRemoteConfigBPLibrary::GetRemoteConfigValue("stepCount").IntegerValue);
}
// Process the result of waiting for A/B test configuration
void SomeLogicClass::OnConfigReceive(EDTDRemoteConfigReceiveResult result) {
// It is not used in current example.
}
// Prepare the app UI for changing the remote configuration
void SomeLogicClass::OnPrepareToChange() {
// It is not used in current example
}
// Apply the values of the assigned group
void SomeLogicClass::OnConfigChange(EDTDRemoteConfigChangeResult result, const FString& error) {
switch (result)
{
case EDTDRemoteConfigChangeResult::Success:
// Apply new values
UDTDRemoteConfigBPLibrary::ApplyConfig();
break;
case EDTDRemoteConfigChangeResult::Failure:
// Error processing
UE_LOG(LogTemp, Warning, TEXT("DTDRemoteConfigError: %s"), *error);
break;
default:
break;
}
// Initiate tutorial completion
RealStartTutorial();
}
# Timer constant
var waitConfigConst = 10.0
var difficulty = "difficulty"
var stepCount = "stepCount"
func loadDefaults():
var appDefaults = GDDTDRemoteConfigDefaults.new()
# Set default values
appDefaults.AddIntegerValue(difficulty, 3)
appDefaults.AddIntegerValue(stepCount, 10)
DTDRemoteConfig.SetDefaults(appDefaults)
func getInteger(key: String) -> int:
return DTDRemoteConfig.GetRemoteConfigValue(key).GetIntValue()
# Tutorial open event (e.g. by clicking the ‘start tutorial’ button)
func startTutorial():
# Display the download progress indicator
showActivityIndicator()
#Send a trigger event that indicates tutorial start
DTDAnalytics.Tutorial(-1)
# Initiate tutorial completion
func realStartTutorial():
# Hide the download progress indicator
hideActivityIndicator()
#Initiate the tutorial by using the remote values (difficulty and stepCount)
var stepCount = getInteger(stepCount)
var difficulty = getInteger(stepCount)
func _ready():
# Set the maximum time of waiting for an A/B test group
DTDRemoteConfig.SetRemoteConfigWaiting(waitConfigConst)
# Set default values
loadDefaults()
# Initialize the SDK for working with A/B testing
DTDAnalytics.InitializeWithAbTest(appKey",
onRemoteConfigChange,
onRemoteConfigPrepareToChange,
onRemoteConfigReceive)
func onRemoteConfigChange(result: GDDTDRemoteConfigChangeResult.ChangeResult, error: String):
match result:
GDDTDRemoteConfigChangeResult.Success:
# Apply new values
DTDRemoteConfig.ApplyConfig()
GDDTDRemoteConfigChangeResult.Failure:
# Error processing
print(error)
realStartTutorial()
# Prepare the app UI for changing the remote configuration
func onRemoteConfigPrepareToChange():
# It is not used in current example
pass
# Process the result of waiting for A/B test configuration
func onRemoteConfigReceive(result: GDDTDRemoteConfigReceiveResult.ReceiveResult):
# It is not used in current example
pass