# Remote configuration SDK integration

{% hint style="success" %}
We will greatly appreciate your feedback. Please add **REMOTE CONFIGS** when submitting your request.
{% endhint %}

{% hint style="warning" %}
**Prerequisite:**&#x20;

Currently remote configs are available only for SDK version 2.6.0 (Android & iOS), Unity 3.10.0, Web 3.0 and higher.&#x20;
{% endhint %}

How to configure remote configuration in devtodev interface:&#x20;

{% content-ref url="rc-interface" %}
[rc-interface](https://docs.devtodev.com/integration/integration-of-sdk-v2/remote-configuration/rc-interface)
{% endcontent-ref %}

## What is a remote configuration

**Remote configuration** (**RC** or **remote config**) is a tool that allows you to remotely change the appearance or logic of the app without publishing a new version of the app.

With the help of remote configs you can:

1. Change app behavior for all users or just for a specific audience.&#x20;
2. Conduct A/B tests to compare different configurations on the same audience and find the best performing one.&#x20;

Please note that in order for remote configuration mechanism to work, you will need to prepare all of the possible changes beforehand. This includes the possible changes in app logic and interface.&#x20;

In devtodev, A/B test is considered a specific case of a remote configuration.&#x20;

### Integration plan in short&#x20;

This is a short description of how the RC integration will work.

1. In your app, you will need to declare a set of variables and set **default** values for these variables. We’ll call these variables **Parameters**.
2. Use these parameters in your app according to your realisation of the app logic. Note that parameter value can change and it will affect the app behavior the way it’s determined by your business logic. There is a [special method to get the current parameter value](#dtdremoteconfiglistener.1).
3. Integrate a [listener method](#dtdremoteconfiglistener.1) that will notify you if at least one parameter value has changed. Add a reaction logic to this notification based on our [proposed strategies](#strategies-for-config-application).
4. [Initialize devtodev SDK with a specific method](#remote-config-initialization) to activate the remote configs.

### How remote configs work&#x20;

1. In the devtodev interface you can change parameter values for a specific audience ([Remote Configuration](https://docs.devtodev.com/integration/integration-of-sdk-v2/remote-configuration/rc-interface)) or create an A/B test ([A/B Testing](https://docs.devtodev.com/integration/integration-of-sdk-v2/a-b-testing/working-with-a-b-tests-in-the-devtodev)).
2. During initialization, devtodev SDK makes a request to the server and receives a list of parameters and their new values according to the configuration. If you also conduct an A/B test at the same time, the SDK will receive a set of conditions to enter the test, a test group and parameter values.
3. If the SDK receives a new value for at least one of the parameters in current configuration, it will [notify](#using-the-onchanged-method-with-remote-configuration) you. This change can be triggered by a remote config or when the user enters an A/B test.\
   The SDK will give you [a list of parameter changes](#dtdremoteconfigcollection.1) and triggers for each change source.\
   Using the update notification and list of changes, select [one of the strategies to react.](#strategies-for-config-application)
4. When you receive a notification about parameter changes, you can activate ([apply](#applying-the-config)) the values according to your app logic. You will be able to use the updated parameter values until you receive and apply a new configuration. You can check for changes right after devtodev SDK initialization.&#x20;

{% hint style="warning" %}
Attention!\
If the user enters an A/B test, the SDK will mark their events with an A/B test group regardless of whether you have activated the proposed parameter changes.\
We recommend activating and applying these changes as soon as possible!&#x20;
{% endhint %}

### Parameter value priority&#x20;

The parameter always has a defined **default** value. This value can be changed if you set a new value using the remote configuration.

If a user enters an A/B test, they will receive parameter values according to their test group configuration.&#x20;

{% hint style="warning" %}
Parameter values received during an A/B test rewrite the default values and previously set values from the remote config. These test values **cannot be changed** until you finish the experiment (A/B test value has the highest priority).&#x20;
{% endhint %}

The priority order for parameter value source is the following:

1. **Top priority** – A/B test parameter value.
2. Remote config value from devtodev server.
3. Least priority – Default value defined in the application code.

## Integration&#x20;

### Setting up default parameter values&#x20;

Set the variable (parameter) values in the `DTDRemoteConfig` class using the `DTDRemoteConfig.defaults` property.&#x20;

{% hint style="info" %}
The SDK does not change any values in the `defaults` property.
{% endhint %}

After setting `DTDRemoteConfig.defaults`, you will be able to get parameter values using the `DTDRemoteConfig.config` property.&#x20;

{% hint style="warning" %}
Always use the `config` property to get up-to-date parameter value configurations.
{% endhint %}

### Waiting for an A/B test group <a href="#waiting-for-an-a-b-test-group" id="waiting-for-an-a-b-test-group"></a>

When you start an A/B test in devtodev, the SDK checks the conditions to enter the experiment. If the user is suitable for the experiment, the SDK will wait for a test group from devtodev server to enter the experiment.

{% hint style="info" %}
The default wait time `groupDefinitionWaiting` is 10 seconds.
{% endhint %}

You can reduce or extend the wait time value.

For example:&#x20;

```
 DTDRemoteConfig.groupDefinitionWaiting = 2
```

In this case, the SDK will be waiting for a group from the server no longer than two seconds. After that, the experiment will be cancelled and the test configuration will be impossible to activate. During the following SDK initialization (application re-start) the user will be able to enter the experiment again.&#x20;

{% hint style="warning" %}
You can set the `DTDRemoteConfig.groupDefinitionWaiting` value only **before SDK initialization**!
{% endhint %}

### Remote config initialization <a href="#remote-config-initialization" id="remote-config-initialization"></a>

In order to use the remote configs or A/B tests, use the `DTDAnalytics.initializeWithRemoteConfig` to initialize devtodev SDK.&#x20;

#### Migrating from previous SDK versions  <a href="#migrating-from-previous-sdk-versions" id="migrating-from-previous-sdk-versions"></a>

* If you have previously worked with [devtodev A/B tests](https://docs.devtodev.com/integration/integration-of-sdk-v2/a-b-testing/description-of-a-b-testing-on-the-sdk-side), you will need to change the `DTDAnalytics.initializeWithAbTest` method to `DTDAnalytics.initializeWithRemoteConfig` for SDK initialization. The `initializeWithAbTest` method is not supported in SDK 2.6.0 (Unity 3.10.0) and higher.
* The `DTDRemoteConfigListener` currently implements only one method – `onChanged(update: DTDRemoteConfigUpdate)` with a different signature. You will need to remove the `onReceived(result: DTDRemoteConfigReceiveResult)` and `onPrepareToChange()` methods, since the updated A/B test mechanism does not utilize these methods.&#x20;

Unlike the previous SDK versions, the `onChanged(update: DTDRemoteConfigUpdate)` method will be called only when there is a change in the remote configuration or A/B test.&#x20;

{% hint style="warning" %}
When you migrate to SDK version 2.6.0 (Unity 3.10.0) and higher, during the first SDK initialization the `onChanged(update: DTDRemoteConfigUpdate)` method will be called automatically if the device is participating in an active A/B test.&#x20;

The method will notify you the config with A/B test variables is ready. Since the device is already participating in the test, the latest configuration is already available in `DTDRemoteConfig.config`.&#x20;

**You must call the** `applyConfig` **method to apply and use the test values.**
{% endhint %}

The following SDK launches will not call the `onChanged(update: DTDRemoteConfigUpdate)` method automatically.&#x20;

### Using the onChanged method with remote configuration <a href="#using-the-onchanged-method-with-remote-configuration" id="using-the-onchanged-method-with-remote-configuration"></a>

1. devtodev server sends new or updated parameter configuration to the SDK.
2. The `onChanged()` method is called.&#x20;

### Using the onChanged method with A/B test <a href="#using-the-onchanged-method-with-a-b-test" id="using-the-onchanged-method-with-a-b-test"></a>

1. The SDK found and activated (entered) a suitable experiment.
2. The `onChanged()` method is called.
3. When the experiment is finished and parameter values are different from the experiment configuration, the `onChanged()` method is called.&#x20;

### Applying the config  <a href="#applying-the-config" id="applying-the-config"></a>

To accept and activate the remote configuration, call the `DTDRemoteConfig.applyConfig()` method.&#x20;

When you apply the new configuration, the default, remote config and test group values will intersect according to their priority order. After that, the new configuration values will be available in the `DTDRemoteConfig.config` property.

There are two options to decline a remote config or participation in an A/B test:

1. Call `DTDRemoteConfig.resetConfig()`.\
   After calling this method, the parameter values will be set to **defaults**. You can use the available remote values again with the next SDK initialization.\
   We recommend using this method for testing.
2. Call `DTDRemoteConfig.invalidateActiveConfig()`.\
   After calling this method, the parameter values will be set to **defaults**. It will be impossible to use the remote values until you publish a new remote configuration.

The SDK stores the received configuration until the user deletes the application from the device. Or until you call `resetConfig` or `invalidateActiveConfig` to reset all the active and unapplied configs to **defaults**.

#### Strategies for config application  <a href="#strategies-for-config-application" id="strategies-for-config-application"></a>

The `onChanged(update: DTDRemoteConfigUpdate)` method notifies the developer that the configuration has changed. The `DTDRemoteConfigUpdate` object stores the list of updated keys.

Besides the key-value pairs, you can check how a parameter was updated (remote config or A/B test) using the `DTDRemoteChangeSource`. Knowing the source of change can be useful to select the best strategy to apply the configuration in different cases at the right moment.&#x20;

1. **Activating config during the current session.** In case you need to get the results of parameter configuration change during the same session, call the `DTDRemoteConfig.applyConfig()` method at any convenient time after the `onChanged` was triggered.\
   The `onChanged` is triggered when the devtodev server sends a new or updated config to the device.
2. **A/B testing.** When you are working with A/B tests, we recommend calling the `DTDRemoteConfig.applyConfig()` method as soon as possible because the user will be assigned a test group immediately after the `onChanged` was triggered.
3. **Activating config in a different session.** If you do not have a config that you need to activate during the current session, you can call the `DTDRemoteConfig.applyConfig()` method at any time during the following SDK launches instead of applying config right after `onChanged` is triggered.&#x20;

## External interfaces description <a href="#external-interfaces-description" id="external-interfaces-description"></a>

### DTDRemoteConfig <a href="#dtdremoteconfig.1" id="dtdremoteconfig.1"></a>

The SDK provides threads synchronization when working with this class.&#x20;

| Property                            | Description                                                                                                                                                         |
| ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `groupDefinitionWaiting:Double`     | <p>Wait time for A/B test configuration. </p><p>Default value - 10.0 (measured in seconds).</p>                                                                     |
| `defaults: Map<String, Any>`        | Paramateres and their default values.                                                                                                                               |
| `config: DTDRemoteConfigCollection` | <p>A collection of current parameters and their values for A/B tests.  </p><p>It allows access to the configuration values by using the subscripting syntaxis. </p> |

| Method                     | Description                                                                                                      |
| -------------------------- | ---------------------------------------------------------------------------------------------------------------- |
| `applyConfig()`            | Applies the remote config or A/B test configuration. After the call, the new config is ready for use in the app. |
| `resetConfig()`            | Resets the config values to defaults until the next SDK initialization.                                          |
| `invalidateActiveConfig()` | Resets the config values to defaults until a new configuration is published.                                     |
| `cacheTestExperiment()`    | A debug method for saving a test experiment after restarting the application.                                    |

### DTDRemoteConfigCollection <a href="#dtdremoteconfigcollection.1" id="dtdremoteconfigcollection.1"></a>

Wrapper for collecting remote parameters. Enables access to configuration values by using subscripting syntax.

#### DTDRemoteConfigValue <a href="#dtdremoteconfigvalue.1" id="dtdremoteconfigvalue.1"></a>

Wrapper for working with remote configuration variables (parameters). It presents a method for data source identification, as well as methods for presenting values in the form of various data types.&#x20;

| Type                              | Description                                                                 |
| --------------------------------- | --------------------------------------------------------------------------- |
| `DTDRemoteConfigSource.Undefined` | The variable could not be found in the default or the remote configuration. |
| `DTDRemoteConfigSource.Defaults`  | The variable is set by default.                                             |
| `DTDRemoteConfigSource.Remote`    | The variable is set by remote config.                                       |
| `DTDRemoteConfigSource.AbTest`    | The variable is set by the test group.                                      |

| Property     | Type    | Description                          |
| ------------ | ------- | ------------------------------------ |
| stringValue  | String? | Gets the value as a optional string. |
| floatValue   | Float   | Gets the value as a Float.           |
| doubleValue  | Double  | Gets the value as a Double.          |
| int32Value   | Int32   | Gets the value as a Int32.           |
| int64Value   | Int32   | Gets the value as a Int32.           |
| integerValue | Int     | Gets the value as a Int.             |
| boolValue    | Bool    | Gets the value as a Bool.            |

#### DTDRemoteConfigListener <a href="#dtdremoteconfiglistener.1" id="dtdremoteconfiglistener.1"></a>

Implements the method that reports on remote configuration and A/B test update.

| Method                                     | Description                                                                                 |
| ------------------------------------------ | ------------------------------------------------------------------------------------------- |
| `onChanged(update: DTDRemoteConfigUpdate)` | Notifies the developer that the configuration has changed with a list of updated variables. |

#### DTDRemoteConfigUpdate <a href="#dtdremoteconfigupdate.1" id="dtdremoteconfigupdate.1"></a>

| Fields                                     | Description                      |
| ------------------------------------------ | -------------------------------- |
| `val keys: List<DTDRemoteConfigChangeKey>` | Contains a list of updated keys. |

#### DTDRemoteConfigChangeKey <a href="#dtdremoteconfigchangekey.1" id="dtdremoteconfigchangekey.1"></a>

The source of the key update.

| Fields                                                                                   | Description                              |
| ---------------------------------------------------------------------------------------- | ---------------------------------------- |
| <p><code>val key: String</code></p><p><code>val source: DTDRemoteConfigSource</code></p> | Contains the key name and update source. |

### List of errors <a href="#list-of-errors" id="list-of-errors"></a>

These messages may be useful for debugging.

| Error message                                                                      | Description                                                                                                                         |
| ---------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
| \[A/B-Test Module] The Server refused to conduct the experiment.                   | devtodev server refused to send the experiment configuration.                                                                       |
| \[A/B-Test Module] Offer from devtodev not received within the allotted N seconds. | The backend offer is returned after wait time is over e.g. due to a bad internet connection.                                        |
| \[A/B-Test Module] Offer from devtodev not received within the allotted N seconds. | The SDK was unable to receive an offer from the backend within N seconds e.g, due to a bad internet connection or network problems. |

## Remote configuration examples <a href="#remote-configuration-examples" id="remote-configuration-examples"></a>

### Example 1 <a href="#example-1" id="example-1"></a>

In this exapmle we set the default values for variables (parameters) `title`, `buttonText`, `tutorial` before devtodev SDK initialization.

After the `onChanged` method is triggered, we call `DTDRemoteConfig.applyConfig()` and apply the received parameter values without any conditions. All inside the `onChanged` method.

You can get the parameter values in any other place in the app, in addition to the `onChanged` method.&#x20;

{% tabs fullWidth="true" %}
{% tab title="Android (Kotlin)" %}

```kotlin
// Initialization of defaults
DTDRemoteConfig.defaults = mapOf(
   "title" to "local title data",
   "buttonText" to "local button data",
   "tutorial" to "local tutorial data"
)
// DTDAnalytics Initialization
DTDAnalytics.initializeWithRemoteConfig(
    "App ID", config, context.applicationContext,
    object : DTDRemoteConfigListener {
        // onChanged implementation
        override fun onChanged(update: DTDRemoteConfigUpdate) {
           // Apply config data
            DTDRemoteConfig.applyConfig()
            // Take data from config
            val titleValue = DTDRemoteConfig.config["title"].stringValue
            val buttonTextValue = DTDRemoteConfig.config["buttonText"].stringValue
            val tutorialValue = DTDRemoteConfig.config["tutorial"].stringValue
            Log.d("[remoteConfig]", "title after applyConfig: $titleValue")
            Log.d("[remoteConfig]", "buttonTextValue after applyConfig: $buttonTextValue")
            Log.d("[remoteConfiga]", "tutorialValue after applyConfig: $tutorialValue")
        }
    }
)
```

{% endtab %}

{% tab title="iOS+macOS (Swift)" %}

```swift
@main
class AppDelegate: UIResponder, UIApplicationDelegate, DTDRemoteConfigListener {
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        // Initialization of defaults
        DTDRemoteConfig.defaults = [
            "title": "local title data",
            "buttonText": "local button data",
            "tutorial": "local tutorial data"
        ]
        // DTDAnalytics Initialization
        DTDAnalytics.initializeWithRemoteConfig(applicationKey: "App ID", abConfigListener: self)
        return true
    }

    public func onChanged(update: DTDRemoteConfigUpdate) {
        // Apply config data
        DTDRemoteConfig.applyConfig()
        // Take data from config
        let titleValue = DTDRemoteConfig.config["title"].stringValue
        let buttonTextValue = DTDRemoteConfig.config["buttonText"].stringValue
        let tutorialValue = DTDRemoteConfig.config["tutorial"].stringValue
        print("[remoteConfig] title after applyConfig: \(titleValue)");
        print("[remoteConfig] buttonTextValue after applyConfig: \(buttonTextValue)");
        print("[remoteConfiga] tutorialValue after applyConfig: \(tutorialValue)");
    }
}
```

{% endtab %}

{% tab title="Unity" %}

```csharp
 public class RemoteConfigListenerExample : IDTDRemoteConfigListener
    {
        public void OnChanged(Dictionary<string, DTDRemoteConfigSource> updatedKeys)
        {
            DTDRemoteConfig.ApplyConfig();
            // Take data from config

            var titleValue = DTDRemoteConfig.Config["title"].StringValue();
            var buttonTextValue = DTDRemoteConfig.Config["buttonText"].StringValue();
            var tutorialValue = DTDRemoteConfig.Config["tutorial"].StringValue();
            Debug.Log($"[remoteConfig] title after applyConfig: {titleValue}");
            Debug.Log($"[remoteConfig] buttonTextValue after applyConfig: {buttonTextValue}");
            Debug.Log($"[remoteConfiga] tutorialValue after applyConfig: {tutorialValue}");
        }
    }

    public class RemoteConfigExample
    {
        public void InitializeDevToDev(string appKey, DTDAnalyticsConfiguration analyticsConfiguration)
        {
            DTDRemoteConfig.Defaults = new Dictionary<string, object>
            {
                { "title", "local title data" },
                { "buttonText", "local button data" },
                { "tutorial", "local tutorial data" }
            };
            DTDAnalytics.InitializeWithRemoteConfig(appKey, analyticsConfiguration,
                new RemoteConfigListenerExample());
        }
    }
```

{% endtab %}

{% tab title="Web (TS)" %}

```typescript
import DTDAnalytics, {
  type DTDRemoteConfigListener,
  type DTDRemoteConfigUpdate,
  type DTDRemoteConfigValue,
} from '@dev-2-dev/websdk';

const analytics = new DTDAnalytics();

const remoteConfigListener: DTDRemoteConfigListener = {
  onChanged: (_update: DTDRemoteConfigUpdate) => {
    // Apply config data
    analytics.remoteConfig.applyConfig();
    // Take data from config
    const titleValue = (
      analytics.remoteConfig.config['title'] as DTDRemoteConfigValue
    ).stringValue;
    const buttonTextValue = (
      analytics.remoteConfig.config['buttonText'] as DTDRemoteConfigValue
    ).stringValue;
    const tutorialValue = (
      analytics.remoteConfig.config['tutorial'] as DTDRemoteConfigValue
    ).stringValue;

    console.log('[remoteConfig] title after applyConfig: ', titleValue);
    console.log(
      '[remoteConfig] buttonTextValue after applyConfig: ',
      buttonTextValue
    );
    console.log(
      '[remoteConfig] tutorialValue after applyConfig: ',
      tutorialValue
    );
  },
};

// Initialization of defaults
analytics.remoteConfig.defaults = {
  title: 'local title data',
  buttonText: 'local button data',
  tutorial: 'local tutorial data',
};

analytics.initializeWithRemoteConfig(
  'App ID',
  {
    userId: 'your_user_id',
  },
  remoteConfigListener
);
```

{% endtab %}

{% tab title="Web (JS)" %}

```javascript
import DTDAnalytics from '@dev-2-dev/websdk';

const analytics = new DTDAnalytics();

const remoteConfigListener = {
  onChanged: _update => {
    // Apply config data
    analytics.remoteConfig.applyConfig();
    // Take data from config
    const titleValue = analytics.remoteConfig.config['title'].stringValue;
    const buttonTextValue =
      analytics.remoteConfig.config['buttonText'].stringValue;
    const tutorialValue = analytics.remoteConfig.config['tutorial'].stringValue;

    console.log('[remoteConfig] title after applyConfig: ', titleValue);
    console.log(
      '[remoteConfig] buttonTextValue after applyConfig: ',
      buttonTextValue
    );
    console.log(
      '[remoteConfig] tutorialValue after applyConfig: ',
      tutorialValue
    );
  },
};

// Initialization of defaults
analytics.remoteConfig.defaults = {
  title: 'local title data',
  buttonText: 'local button data',
  tutorial: 'local tutorial data',
};

analytics.initializeWithRemoteConfig(
  'App ID',
  {
    userId: 'your_user_id',
  },
  remoteConfigListener
);
```

{% endtab %}
{% endtabs %}

### Example 2 <a href="#example-2" id="example-2"></a>

In this exapmle we set the default values for variables (parameters) `title`, `buttonText`, `tutorial` before devtodev SDK initialization.

After the `onChanged` method is triggered, we check if there are any keys that were updated for the A/B test.

* If there is such key, the user will immediately enter the A/B test and get the experiment configuration.
* If no key was updated for the A/B test, this means the SDK received only new remote configs without A/B test configuration and you do not need to call `DTDRemoteConfig.applyConfig()` right away.
  * You can apply the remote config at any convenient moment even after the app restarts.

We have added a `DTDRemoteConfig.applyConfig()` call after the SDK initialization. Since the `onChanged` was triggered in the previous app launch, we have the up-to-date remote config values on the device.&#x20;

{% tabs fullWidth="true" %}
{% tab title="Android (Kotlin)" %}

```kotlin
// Initialization of defaults
DTDRemoteConfig.defaults = mapOf(
   "title" to "local title data",
   "buttonText" to "local button data",
   "tutorial" to "local tutorial data"
)
// DTDAnalytics Initialization
DTDAnalytics.initializeWithRemoteConfig(
    "App ID", config, context.applicationContext,
    object : DTDRemoteConfigListener {
        // onChanged implementation
        override fun onChanged(update: DTDRemoteConfigUpdate) {
            // Check if there are a/b test keys
            val abTestKey = update.keys.firstOrNull { it.source == DTDRemoteConfigSource.AbTest }?.key
                if (abTestKey != null) {
                    //Apply config data
                    DTDRemoteConfig.applyConfig()

                    // Take data from config
                    val titleValue = DTDRemoteConfig.config["title"].stringValue
                    val buttonTextValue = DTDRemoteConfig.config["buttonText"].stringValue
                    val tutorialValue = DTDRemoteConfig.config["tutorial"].stringValue
                    Log.d("[remoteConfig]", "title after applyConfig with a/b test: $titleValue")
                    Log.d("[remoteConfig]", "buttonTextValue after applyConfig a/b test: $buttonTextValue")
                    Log.d("[remoteConfig]", "tutorialValue after applyConfig a/b test: $tutorialValue")
                }
            }
        }
    }
)
//Apply config data
DTDRemoteConfig.applyConfig()
// Take data from config
val titleValue = DTDRemoteConfig.config["title"].stringValue
val buttonTextValue = DTDRemoteConfig.config["buttonText"].stringValue
val tutorialValue = DTDRemoteConfig.config["tutorial"].stringValue
Log.d("[remoteConfig]", "title after applyConfig: $titleValue")
Log.d("[remoteConfig]", "buttonTextValue after applyConfig: $buttonTextValue")
Log.d("[remoteConfig]", "tutorialValue after applyConfig: $tutorialValue")

```

{% endtab %}

{% tab title="iOS+macOS (Swift)" %}

```swift
// Initialization of defaults
    DTDRemoteConfig.defaults = [
        "title": "local title data",
        "buttonText": "local button data",
        "tutorial": "local tutorial data"
    ]

    // DTDAnalytics Initialization
    DTDAnalytics.initializeWithRemoteConfig(applicationKey: "App ID", abConfigListener: self)

    // onChanged implementation
    public func onChanged(update: DTDRemoteConfigUpdate) {
        // Check if there are a/b test keys
        if let abTestKey = update.keys.first(where: { $0.source == .abTest })?.key {
            // Apply config data
            DTDRemoteConfig.applyConfig()
            // Take data from config
            let titleValue = DTDRemoteConfig.config["title"].stringValue
            let buttonTextValue = DTDRemoteConfig.config["buttonText"].stringValue
            let tutorialValue = DTDRemoteConfig.config["tutorial"].stringValue
            print("[remoteConfig] title after applyConfig: \(titleValue)");
            print("[remoteConfig] buttonTextValue after applyConfig: \(buttonTextValue)");
            print("[remoteConfiga] tutorialValue after applyConfig: \(tutorialValue)");
        }
    }

    //Apply config data
    DTDRemoteConfig.applyConfig()
    // Take data from config
    let titleValue = DTDRemoteConfig.config["title"].stringValue
    let buttonTextValue = DTDRemoteConfig.config["buttonText"].stringValue
    let tutorialValue = DTDRemoteConfig.config["tutorial"].stringValue
    print("[remoteConfig] title after applyConfig: \(titleValue)");
    print("[remoteConfig] buttonTextValue after applyConfig: \(buttonTextValue)");
    print("[remoteConfiga] tutorialValue after applyConfig: \(tutorialValue)");
```

{% endtab %}

{% tab title="Unity" %}

```csharp
public class RemoteConfigListenerExample : IDTDRemoteConfigListener
{
    public void OnChanged(Dictionary<string, DTDRemoteConfigSource> updatedKeys)
    {
        DTDRemoteConfig.ApplyConfig();
        // Take data from config
        var hasAbTestKey = updatedKeys.Any(x => x.Value == DTDRemoteConfigSource.ABTest);
        if (hasAbTestKey)
        {
            var titleValue = DTDRemoteConfig.Config["title"].StringValue();
            var buttonTextValue = DTDRemoteConfig.Config["buttonText"].StringValue();
            var tutorialValue = DTDRemoteConfig.Config["tutorial"].StringValue();
            Debug.Log($"[remoteConfig] title after applyConfig: {titleValue}");
            Debug.Log($"[remoteConfig] buttonTextValue after applyConfig: {buttonTextValue}");
            Debug.Log($"[remoteConfiga] tutorialValue after applyConfig: {tutorialValue}");
        }
    }
}

public class RemoteConfigExample
{
    public void InitializeDevToDev(string appKey, DTDAnalyticsConfiguration analyticsConfiguration)
    {
        DTDRemoteConfig.Defaults = new Dictionary<string, object>
        {
            { "title", "local title data" },
            { "buttonText", "local button data" },
            { "tutorial", "local tutorial data" }
        };
        DTDAnalytics.InitializeWithRemoteConfig(appKey, analyticsConfiguration,
            new RemoteConfigListenerExample());
    }
}
```

{% endtab %}

{% tab title="Web (TS)" %}

```typescript
import DTDAnalytics, {
  DTDRemoteConfigSource,
  type DTDRemoteConfigListener,
  type DTDRemoteConfigUpdate,
  type DTDRemoteConfigValue,
} from '@dev-2-dev/websdk';

const analytics = new DTDAnalytics();

const remoteConfigListener: DTDRemoteConfigListener = {
  onChanged: (update: DTDRemoteConfigUpdate) => {
    // Check if there are a/b test keys
    const abTestKeys = update.keys.filter(
      key => key.source === DTDRemoteConfigSource.AbTest
    );
    if (abTestKeys.length > 0) {
      // Apply config data
      analytics.remoteConfig.applyConfig();

      // Take data from config
      const titleValue = (
        analytics.remoteConfig.config['title'] as DTDRemoteConfigValue
      ).stringValue;
      const buttonTextValue = (
        analytics.remoteConfig.config['buttonText'] as DTDRemoteConfigValue
      ).stringValue;
      const tutorialValue = (
        analytics.remoteConfig.config['tutorial'] as DTDRemoteConfigValue
      ).stringValue;

      console.log('[remoteConfig] title after applyConfig: ', titleValue);
      console.log(
        '[remoteConfig] buttonTextValue after applyConfig: ',
        buttonTextValue
      );
      console.log(
        '[remoteConfig] tutorialValue after applyConfig: ',
        tutorialValue
      );
    }
  },
};

// Initialization of defaults
analytics.remoteConfig.defaults = {
  title: 'local title data',
  buttonText: 'local button data',
  tutorial: 'local tutorial data',
};

analytics.initializeWithRemoteConfig(
  'App ID',
  {
    userId: 'your_user_id',
  },
  remoteConfigListener
);

analytics.remoteConfig.applyConfig();

// Take data from config
const titleValue = (
  analytics.remoteConfig.config['title'] as DTDRemoteConfigValue
).stringValue;
const buttonTextValue = (
  analytics.remoteConfig.config['buttonText'] as DTDRemoteConfigValue
).stringValue;
const tutorialValue = (
  analytics.remoteConfig.config['tutorial'] as DTDRemoteConfigValue
).stringValue;

console.log('[remoteConfig] title after applyConfig: ', titleValue);
console.log(
  '[remoteConfig] buttonTextValue after applyConfig: ',
  buttonTextValue
);
console.log('[remoteConfig] tutorialValue after applyConfig: ', tutorialValue);
```

{% endtab %}

{% tab title="Web (JS)" %}

```javascript
import DTDAnalytics, { DTDRemoteConfigSource } from '@dev-2-dev/websdk';

const analytics = new DTDAnalytics();

const remoteConfigListener = {
  onChanged: update => {
    // Check if there are a/b test keys
    const abTestKeys = update.keys.filter(
      key => key.source === DTDRemoteConfigSource.AbTest
    );
    if (abTestKeys.length > 0) {
      // Apply config data
      analytics.remoteConfig.applyConfig();

      // Take data from config
      const titleValue = analytics.remoteConfig.config['title'].stringValue;
      const buttonTextValue =
        analytics.remoteConfig.config['buttonText'].stringValue;
      const tutorialValue =
        analytics.remoteConfig.config['tutorial'].stringValue;

      console.log('[remoteConfig] title after applyConfig: ', titleValue);
      console.log(
        '[remoteConfig] buttonTextValue after applyConfig: ',
        buttonTextValue
      );
      console.log(
        '[remoteConfig] tutorialValue after applyConfig: ',
        tutorialValue
      );
    }
  },
};

// Initialization of defaults
analytics.remoteConfig.defaults = {
  title: 'local title data',
  buttonText: 'local button data',
  tutorial: 'local tutorial data',
};

analytics.initializeWithRemoteConfig(
  'App ID',
  {
    userId: 'your_user_id',
  },
  remoteConfigListener
);

analytics.remoteConfig.applyConfig();

// Take data from config
const titleValue = analytics.remoteConfig.config['title'].stringValue;
const buttonTextValue = analytics.remoteConfig.config['buttonText'].stringValue;
const tutorialValue = analytics.remoteConfig.config['tutorial'].stringValue;

console.log('[remoteConfig] title after applyConfig: ', titleValue);
console.log(
  '[remoteConfig] buttonTextValue after applyConfig: ',
  buttonTextValue
);
console.log('[remoteConfig] tutorialValue after applyConfig: ', tutorialValue);
```

{% endtab %}
{% endtabs %}
