|
| 1 | +Creating experiments is easy using Taplytics. You can either use our visual editor or create code-based experiments. You can find documentation on how to do this below. |
| 2 | + |
| 3 | +| Table of Contents | |
| 4 | +| ----------------- | |
| 5 | +| [Feature Flags](#feature-flags)| |
| 6 | +| [Dynamic Variables](#dynamic-variables) | |
| 7 | +| [Code Blocks](#code-blocks) | |
| 8 | +| [Testing Specific Experiments](#testing-specific-experiments) | |
| 9 | +| [Visual Editing](#visual-editing) | |
| 10 | +| [First-view Experiments](#delay-load) | |
| 11 | +| [List Running Experiments](#running-experiments) | |
| 12 | + |
| 13 | +## Feature Flags |
| 14 | + |
| 15 | +Taplytics feature flags operate in synchronous mode. |
| 16 | + |
| 17 | +#### Synchronous |
| 18 | + |
| 19 | +Synchronous feature flags are guaranteed to have the same value for the entire session and will have that value immediately after construction. |
| 20 | + |
| 21 | +Due to the synchronous nature of feature flags, if it is used before the feature flags have been loaded from Taplytics servers (for example on the first launch of your app), it will default to as if the feature flag is not present. In order to prevent this you can ensure that the feature flag is loaded before using the feature flag. This can be done with either the `delayLoad` functionality, the `TaplyticsExperimentsLoadedListener` parameter in your `startTaplytics` call, or the `getRunningExperimentsAndVariations` call: |
| 22 | + |
| 23 | +```kotlin |
| 24 | +if (Taplytics.featureFlagEnabled("featureFlagKey")) { |
| 25 | + //Put feature code here, or launch feature from here |
| 26 | +} |
| 27 | +``` |
| 28 | + |
| 29 | +#### Setting a Default value |
| 30 | + |
| 31 | +In the event that Taplytics was unable to load, Taplytics will by default return FALSE when using ```featureFlagEnabled```. To prevent this behavour and instead default a feature to being ON in cases of no network, pass in the desired default behavior. |
| 32 | + |
| 33 | +```kotlin |
| 34 | +if (Taplytics.featureFlagEnabled("featureFlagKey", true)) { |
| 35 | + //Put feature code here, or launch feature from here |
| 36 | +} |
| 37 | +``` |
| 38 | + |
| 39 | + |
| 40 | +## Running Feature Flags |
| 41 | + |
| 42 | +If you would like to see which feature flags are running on a given device, there exists a `getRunningFeatureFlags(TaplyticsRunningFeatureFlagsListener listener)` function which provides a callback with a map of the current feature flags. An example: |
| 43 | + |
| 44 | +```kotlin |
| 45 | +Taplytics.getRunningFeatureFlags { |
| 46 | + // TODO: Do something with the map. |
| 47 | +} |
| 48 | +``` |
| 49 | + |
| 50 | +## Dynamic Variables |
| 51 | + |
| 52 | +Taplytics variables are values in your app that are controlled by experiments. Changing the values can update the content or functionality of your app. If logging is enabled, logs will show when a variable has been set, updated or when `get()` is called. Variables are reusable between experiments and operate in one of two modes: synchronous or asynchronous. |
| 53 | + |
| 54 | +#### Synchronous |
| 55 | + |
| 56 | +Synchronous variables are guaranteed to have the same value for the entire session and will have that value immediately after construction. |
| 57 | + |
| 58 | +Due to the synchronous nature of the variable, if it is used before the experiments have been loaded, its value will be the default value rather than the value set for that experiment. This could taint the results of the experiment. In order to prevent this you can ensure that the experiments are loaded before using the variable. This can be done with either the `delayLoad` functionality, the `TaplyticsExperimentsLoadedListener` parameter in your `startTaplytics` call, or the `getRunningExperimentsAndVariations` call. |
| 59 | + |
| 60 | +Synchronous variables take two parameters in its constructor: |
| 61 | + |
| 62 | +1. Variable name (String) |
| 63 | +2. Default Value |
| 64 | + |
| 65 | +The type of the variable is defined in the first diamond brackets, and can be a `String`, `Number`, `Boolean` or `JSON`. |
| 66 | + |
| 67 | +For example, using a variable of type `String`: |
| 68 | +```kotlin |
| 69 | +val stringVar = TaplyticsVar("some name", "default value") |
| 70 | +``` |
| 71 | + |
| 72 | +Then when you wish to get the value for the variable, simply call `get()` on the Taplytics variable: |
| 73 | +```kotlin |
| 74 | +val value = stringVar.get() |
| 75 | +``` |
| 76 | + |
| 77 | +#### Asynchronous |
| 78 | + |
| 79 | +Asynchronous variables take care of insuring that the experiments have been loaded before returning a value. This removes any danger of tainting the results of your experiment with bad data. What comes with the insurance of using the correct value is the possibility that the value will not be set immediately. If the variable is constructed *before* the experiments are loaded, you won't have the correct value until the experiments have finished loading. If the experiments fail to load, then you will be given the default value, as specified in the variables constructor. For the best results with asynchronous varaibles make sure that they are constructed after the StartTaplytics call in your app's lifecycle. |
| 80 | + |
| 81 | +Asynchronous variables take three parameters in its constructor: |
| 82 | + |
| 83 | +1. Variable name (String) |
| 84 | +2. Default Value |
| 85 | +3. TaplyticsVarListener |
| 86 | + |
| 87 | +Just as for synchronous variables the type of the variable is defined in the first diamond brackets, and can be a `String`, `Int`, `Boolean` or `JSON`. |
| 88 | + |
| 89 | +For example, using a variable of type `Int`: |
| 90 | + |
| 91 | +```kotlin |
| 92 | +val `var`: TaplyticsVar<Int> = |
| 93 | + TaplyticsVar<Int>("name", 5, TaplyticsVarListener { |
| 94 | + // Do something with the value |
| 95 | + }) |
| 96 | +``` |
| 97 | + |
| 98 | +When the variable's value has been updated, the listener will be called with that updated value. You can specify what you want to do with the variable inside the `variableUpdated` method. |
| 99 | + |
| 100 | +**Note: Default values for dynamic variables cannot be NULL. NULL values may cause default to trigger in all scenarios** |
| 101 | + |
| 102 | +---------- |
| 103 | + |
| 104 | +#### Testing Dynamic Variables |
| 105 | + |
| 106 | +When testing dynamic variables in live update mode you can change the values on the fly via the Taplytics interface and you can switch variations with the shake menu on the device. |
| 107 | + |
| 108 | +**Important Note:** When testing synchronous dynamic variables you *must* call the constructor again to see the new value, as there are no callbacks which occur when the variable is updated with a new value. |
| 109 | + |
| 110 | +This can be achieved by using a experiments updated listener. Here is an example for updating a TextView: |
| 111 | + |
| 112 | + |
| 113 | +``` kotlin |
| 114 | + |
| 115 | +Taplytics.setTaplyticsExperimentsUpdatedListener { |
| 116 | + val stringVar = TaplyticsVar("stringVar", "defaultValue") |
| 117 | + updateText(stringVar.get()) |
| 118 | +} |
| 119 | +``` |
| 120 | +## Code Blocks |
| 121 | + |
| 122 | +Similar to Dynamic Variables, Taplytics has an option for 'Code Blocks'. Code blocks are linked to Experiments through the Taplytics website very much the same way that Dynamic Variables are, and will be executed based on the configuration of the experiment through the Taplytics website. A Code Block is a callback that can be enabled or disabled depending on the variation. If enabled, the code within the callback will be executed. If disabled, the variation will not get the callback. |
| 123 | + |
| 124 | +A Code Block can be used alongside as many other Code Blocks as you would like to determine a combination that yields the best results. Perhaps there are three different Code Blocks on one activity. This means there could be 8 different combinations of Code Blocks being enabled / disabled on that activity if you'd like. |
| 125 | + |
| 126 | +For example: |
| 127 | + |
| 128 | +```kotlin |
| 129 | +Taplytics.runCodeBlock("name") { |
| 130 | + // Put your code here! |
| 131 | +} |
| 132 | +``` |
| 133 | + |
| 134 | +By default, a code block will _not_ run unless enabled on the Taplytics Dashboard. It must be enabled for a Variation before it will run. |
| 135 | + |
| 136 | +## Testing Specific Experiments |
| 137 | + |
| 138 | +To test/QA specific experiment and variation combinations, add a map to the Taplytics start options containing the experiments and variations you wish to test. The keys should be the experiment names, and values of variation names (or baseline). |
| 139 | + |
| 140 | +For example: |
| 141 | + |
| 142 | + |
| 143 | +```kotlin |
| 144 | +val startOptions: HashMap<String, Any> = HashMap() |
| 145 | +val testExperiments = HashMap<Any, Any>() |
| 146 | +testExperiments["Big Experiment"] = "Variation 2" |
| 147 | +startOptions["testExperiments"] = testExperiments |
| 148 | + |
| 149 | +Taplytics.startTaplytics(this, APIKEY, startOptions) |
| 150 | +``` |
| 151 | +## Visual Editing |
| 152 | + |
| 153 | +**NOTE: Not currently fully supported for Android TV / Fire TV** |
| 154 | + |
| 155 | +You don't have to do anything else! You can use the Taplytics dashboard to make all your visual changes. See the docs on visual editing [here](https://taplytics.com/docs/guides/visual-experiments). |
| 156 | + |
| 157 | +#### Dialogs |
| 158 | + |
| 159 | +Taplytics supports editing elements on **dialogFragments** (not dialogs). To do this properly, you must use a fragmentTransaction to add the fragment to the backstack. The tag used here should be the same as the tag used to show the fragment, like so: |
| 160 | + |
| 161 | +```kotlin |
| 162 | +val fragmentTransaction: FragmentTransaction = fragmentManager.beginTransaction() |
| 163 | +fragmentTransaction.show(someDialog) |
| 164 | +fragmentTransaction.addToBackStack("fragment_some_dialog") |
| 165 | +someDialog.show(fragmentTransaction, "fragment_some_dialog") |
| 166 | +``` |
| 167 | + |
| 168 | +Taplytics tracks the appearance/disappearance of the dialog via the backstack manager, which is why it needs to be sent there. The tag is necessary to confirm that the visual edits are being applied to the correct fragment. |
| 169 | + |
| 170 | +This only works with dialogFragments as normal Dialogs do not have any unique identifying tags. |
| 171 | + |
| 172 | +**NOTE:** |
| 173 | + |
| 174 | +dialogFragments exist on an entirely different view hierarchy than traditional view elements. They exist within their own `window` and have an entirely different viewRoot than the rest of your application. This makes changes on dialogs very difficult, and this feature is not 100% guaranteed to work for all dialogs. |
| 175 | + |
| 176 | +--- |
| 177 | + |
| 178 | +## Delay Load |
| 179 | + |
| 180 | +Taplytics has the option to delay the loading of your main activity while Taplytics gets initial ***view*** changes ready. Keep in mind that this initial load will only take some time the very first time, after that, these changes will be saved to disk and will likely not need a delay. |
| 181 | + |
| 182 | +There are two methods to do this, **use both at the start of your ```kotlin onCreate()``` after ```kotlin setContentView()```**: |
| 183 | + |
| 184 | +#### Delay Load With Image |
| 185 | +In this instance, Taplytics takes care of the loading for you. Taplytics creates a splash screen with the provided image. The image will fade automatically after the given time, or when Taplytics has successfully loaded visual changes on the provided activity. |
| 186 | + |
| 187 | +Method: ```Taplytics.delayLoad(Activity activity, Drawable image, int maxTime) ``` |
| 188 | + |
| 189 | +and ```Taplytics.delayLoad(Activity activity, Drawable image, int maxTime)``` |
| 190 | + |
| 191 | + |
| 192 | +**Activity**: the activity (typically main activity) that will be covered in a splash image. |
| 193 | + |
| 194 | +**Image**: A Drawable image that will be the splash screen. |
| 195 | + |
| 196 | +**maxTime**: Regardless of the results of Taplytics, the image will fade after this time. Milliseconds. |
| 197 | + |
| 198 | +**minTime**: Sometimes Taplytics loads things really fast, and this might make the image show only for a short amount of time. To keep this from happening, there is an optional minimum time option. Regardless of Taplytics loading experiments, the ```delayLoad``` won't finish until after this minimum time. Milliseconds. |
| 199 | + |
| 200 | +**Examples**: |
| 201 | + |
| 202 | +```kotlin |
| 203 | +override fun onCreate(Bundle savedInstanceState) { |
| 204 | + super.onCreate(savedInstanceState) |
| 205 | + setContentView(R.layout.main_layout); |
| 206 | + |
| 207 | + Taplytics.delayLoad(this, getResources().getDrawable(R.drawable.image5), 2000); |
| 208 | + ... |
| 209 | +``` |
| 210 | + |
| 211 | +**With a 1 second minimum time** |
| 212 | + |
| 213 | +```kotlin |
| 214 | +Taplytics.delayLoad(this, getResources().getDrawable(R.drawable.image5), 2000, 1000); |
| 215 | +... |
| 216 | +``` |
| 217 | + |
| 218 | +#### Delay Load with Callbacks |
| 219 | +In this instance, Taplytics provides callbacks when the delay load should begin, and when the delay load ends. The callback will also return after the provided timeout time has been reached. This provides you the ability to show a splashscreen that is more than just a simple image. |
| 220 | + |
| 221 | +Method: ```Taplytics.delayLoad(int maxTime, TaplyticsDelayLoadListener listener) ``` |
| 222 | + |
| 223 | +and ```Taplytics.delayLoad(int maxTime, int minTime, TaplyticsDelayLoadListener listener) ``` |
| 224 | + |
| 225 | + |
| 226 | + |
| 227 | +**maxTime**: Regardless of the results of Taplytics, this callback will be triggered if this time is reached. |
| 228 | + |
| 229 | +**minTime**: Sometimes Taplytics loads things really fast, and this might make the behavior of the callback undesirable. To keep this from happening, there is an optional minimum time option. Regardless of Taplytics loading experiments, the ```delayLoad``` won't finish until after this minimum time. |
| 230 | +
|
| 231 | +**Listener**: This listener will provide the necessary callbacks. |
| 232 | +
|
| 233 | +**Examples**: |
| 234 | +
|
| 235 | +```kotlin |
| 236 | +protected fun onCreate(savedInstanceState: Bundle?) { |
| 237 | + super.onCreate(savedInstanceState) |
| 238 | + setContentView(R.layout.main_layout) |
| 239 | + Taplytics.delayLoad(2000, object : TaplyticsDelayLoadListener { |
| 240 | + override fun startDelay() { |
| 241 | + //Start delaying! |
| 242 | + } |
| 243 | +
|
| 244 | + override fun delayComplete() { |
| 245 | + //Loading completed, or the given time has been reached. Insert your code here. |
| 246 | + } |
| 247 | + }) |
| 248 | + ... |
| 249 | +} |
| 250 | +``` |
| 251 | +
|
| 252 | +**With a 1 second minimum time:** |
| 253 | +
|
| 254 | +```kotlin |
| 255 | +Taplytics.delayLoad(2000, 1000, ... |
| 256 | +``` |
| 257 | +
|
| 258 | +--- |
| 259 | +
|
| 260 | +## Running Experiments |
| 261 | +
|
| 262 | +If you would like to see which variations and experiments are running on a given device, there exists a `getRunningExperimentsAndVariations(TaplyticsRunningExperimentsListener listener)` function which provides a callback with a map of the current experiments and their running variation. If logging is enabled, logs will also show this map. |
| 263 | +
|
| 264 | +An example: |
| 265 | +
|
| 266 | +```kotlin |
| 267 | +Taplytics.getRunningExperimentsAndVariations { |
| 268 | + // TODO: Do something with the map. |
| 269 | +} |
| 270 | +``` |
| 271 | +
|
| 272 | +NOTE: This function runs asynchronously, as it waits for the updated properties to load from Taplytics' servers before returning the running experiments. |
| 273 | + |
| 274 | +If you want to see when the experiments have been loaded by Taplytics, you can add a `TaplyticsExperimentLoadedListener` to your `startTaplytics` call. For example |
| 275 | + |
| 276 | +```kotlin |
| 277 | +Taplytics.startTaplytics( |
| 278 | + this, |
| 279 | + "YOUR API KEY", |
| 280 | + null |
| 281 | +) { |
| 282 | + //TODO: Do something now that experiments are loaded |
| 283 | +} |
| 284 | +``` |
| 285 | + |
| 286 | +## Sessions |
| 287 | + |
| 288 | +By default, Taplytics defines a session as when a user is using the app with less than 10 minutes of inactivity. If the app has been backgrounded for 10 minutes, the next time the user opens the app it will be considered a new session. Similarly, if the app is entirely force closed, the next time the app is opened, it will be considered a new session. |
| 289 | + |
| 290 | +### StartNewSession |
| 291 | + |
| 292 | +To manually force a new user session (ex: A user has logged in / out), there exists ```Taplytics.startNewSession``` |
| 293 | + |
| 294 | +If there is an internet connection, a new session will be created, and new experiments/variations will be fetched from Taplytics if they exist. If logging is enabled, logs will show when a new session has been started or if an error has occurred. |
| 295 | + |
| 296 | +It can be used as follows: |
| 297 | + |
| 298 | +```kotlin |
| 299 | +Taplytics.startNewSession(object : TaplyticsNewSessionListener { |
| 300 | + override fun onNewSession() { |
| 301 | + // New session here! Only returns if successful. |
| 302 | + } |
| 303 | + |
| 304 | + override fun onError() { |
| 305 | + // No new session here! Only returns if unsuccessful. |
| 306 | + } |
| 307 | +}) |
| 308 | +``` |
| 309 | + |
| 310 | +### Session Listener |
| 311 | + |
| 312 | +To keep track of when Taplytics defines a new session, use a `TaplyticsNewSessionListener` as follows. |
| 313 | + |
| 314 | +```kotlin |
| 315 | +Taplytics.setTaplyticsNewSessionListener(object : TaplyticsNewSessionListener { |
| 316 | + override fun onNewSession() { |
| 317 | + //We are in a new session |
| 318 | + } |
| 319 | + |
| 320 | + override fun onError() { |
| 321 | + //We are not in a new session |
| 322 | + } |
| 323 | +}) |
| 324 | +``` |
| 325 | + |
| 326 | +**Note that this is NOT called the first time Taplytics loads, only on subsequent sessions** |
0 commit comments