Only this pageAll pages
Powered by GitBook
Couldn't generate the PDF for 179 pages, generation stopped at 100.
Extend with 50 more pages.
1 of 100

devtodev documentation

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Integration

SDK Integration

Web

Expert Tips

Setting up Events

Integration of SDK 2.0+

Introduction

devtodev documentation helps you to gain in-depth knowledge of the product and use all opportunities offered by the platform.

Thanks for choosing devtodev analytics!

devtodev provides SDKs that give you the ability to measure user behavior and player journeys using basic and custom events.

We have SDKs for the most popular OS and engines:

Web SDK Releases

Changelog for WEB SDK

Version 2.2 (28 Mar 2025)

Added A/B test functionality (Beta).

Version 2.1 (06 Dec 2024)

, which carry information about the user's personal data, are excluded from the SDK.

Registration

  1. Visit our website www.devtodev.com and click Get started.

  1. Fill out the registration form.

  1. You will receive an email to confirm your registration.

Product updates: 2025

Here are some of the new features you might have missed.

Custom event descriptions in Funnels

Released: 28/11/2025

Quickly find out the meaning behind an event. In Conversion funnels report, hover over an event or a parameter, and a description will appear. If you do not have any event descriptions yet, you can add them manually or generate them using AI in Tuning -> Custom event configurations.


Adding a space

Space is an information field where you will work. Later on, you will add your application to the space.

  1. Click Create Space.

  1. To create a space, you need to fill in:

Getting Started

Here are some simple steps to start using our service:

Push notifications

Available platforms

Set up push campaigns in devtodev

Reserved user properties

Integrate SDK to your project

  • Integrate basic devtodev events

  • Integrate custom events

  • Check your integration

  • Add anti-cheat methods

  • Mark Test devices

  • Add App Store/Google Play account

  • Sign up for devtodev
    Create your space
    Add a project to the space
    Push Notifications
    Cover

    Android

    Cover

    iOS

    Cover

    Windows

    Cover

    Unity

    Cover

    Unreal Engine

    New report: Purchased items

    Released: 01/10/2025

    We've updated our Payment structure report and added a Purchased items tab. Check this report to quickly identify poorly performing items and change your monetization accordingly.

    If available, this report uses automatically tracked refund data (see setup requirements).

    Learn more about Payments structure report


    Custom events and Funnels: detailed Tutorial analysis

    Released: 01/10/2025

    Before you could check tutorial events only with a Status parameter: started, finished or skipped. We've added a Step parameter so you can select and analyse the exact steps of your tutorial in Custom events and Funnels reports. This allows for a more detailed FTUE analysis.

    Learn more about Custom events and Funnels reports


    Basic Metrics: updated Total value overview

    Released: 29/09/2025

    By default, you can see the Total values for selected events above the chart. Previously this field was available only for table views. We've added a Show total button so you can hide this information block.

    Total values are available in Basic Metrics for projects and Space.

    Learn more about Basic Metrics report


    Remote configuration (Beta)

    Released: 16/09/2025

    With the help of remote configuration you can:

    1. Change app behavior for all users or just for a specific audience.

    2. Conduct A/B tests to compare different configurations on the same audience and find the best-performing one.

    With the introduction of remote configuration we've also simplified the A/B test integration.

    Learn more about remote configuration


    New integration: automatic payments from Aghanim

    Released: 16/09/2025

    We've collaborated with Aghanim and added a new way to track payments automatically. After everything is set up, Aghanim will send Real Payment events to devtodev via API and we'll match them to users.

    Learn more about Aghanim setup


    Custom event descriptions in reports

    Released: 01/09/2025

    Quickly find out the meaning behind an event. In Custom events report, hover over an event or a parameter, and a description will appear. If you do not have any event descriptions yet, you can add them manually or generate them using AI in Tuning -> Custom event configurations.

    Learn more about Custom event configurations


    Automatic refunds tracking

    Released: 07/08/2025

    An addition to our Automatic payments tracking – now you can also get refunds data automatically with a simple setup in devtodev settings. This integration allows you to receive data from App Store and Google Play.

    Learn more about automatic refunds tracking


    New cohort export integrations: Customer.io, Pushwoosh, Braze

    Released: 17/07/2025

    We've added more integrations for our Cohort export feature. Create segments in devtodev and engage with users using personalised communication.

    Learn how to set up Cohort export in devtodev


    SDK integration with the help of AI

    Released: 17/06/2025

    If you are using AI coding agents, you can try our new AI-assisted integration process. The AI will analyse your app code, suggest the necessary events and help you integrate devtodev SDK quickly.

    Currently this option is available for Unity projects but we are working on extending the list of platforms.

    Feel free to send us feedback regarding this feature via Contact us form or reach out to our Customer Success team directly within the platform. Please add AI INTEGRATION when submitting your request.

    Learn more about AI-assisted integration


    Labels on Dashboards

    Released: 16/05/2025

    Previously, labels were available only in Basic Metrics reports; now you can see them on the dashboard widgets. The system creates labels for new releases automatically and you can create your own labels in the Tuning section.

    Learn more about Labels


    New integration: send user cohorts to OneSignal

    Released: 16/05/2025

    New addition to our Cohort Export feature – OneSignal. Create a segment in devtodev and use it to send personalised messages via different channels from OneSignal.

    Learn how to set up OneSignal in devtodev


    AI-generated descriptions for Custom events

    Released: 10/04/2025

    You can now add descriptions to Custom events. To help you with this process, we've added an option to quickly generate these descriptions using AI. You can always edit the generated descriptions manually.

    We've also updated the Custom event configurations page so you can easily check and configure any event and its parameters.

    Learn more about Custom event configurations


    A/B testing for Web SDK

    Released: 28/03/2025

    Our SDK for Web now supports A/B testing. We've also added a changelog page for Web SDK releases.

    Learn more about A/B testing integration


    Automatic payment tracking

    Released: 26/03/2025

    Devtodev already tracks Subscriptions automatically. We've added automatic tracking for In-App Purchases. Now you can receive data about transactions from App Store and Google Play without integrating the Real Payment event.

    This allows you to speed up analytics integration and get valid data directly from the store.

    Learn more about automatic payment tracking

    Learn more about Custom event configurations

    Name

  • Timezone

  • Logo (optional)

  • The timezone is important because it defines the time when one day ends and another one begins.

    1. Fill in your billing information.

    That's it! You've created your space, and now you can add applications to it.

    We offer a 30-day free trial for all new users (only for the first space created using this email).

    Note that the trial period begins at the moment you create a Space.

    Android

    iOS

    Unity

    Web

    macOS

    Windows

    Unreal Engine

    Godot Engine

    Cover
    Cover
    Cover
    Cover
    Cover
    Cover
    Cover
    Cover

    Adding an app to the space

    Add Application

    Once you've created the space, you'll see the Add Application button in the devtodev interface. Click on it to add an application to the space.

    1. First, you need to select a platform.

    Push Notifications

    Setting up Events

    Integration of SDK 1.0+ (deprecated)

    SDK Integration

    1. The next step is optional. You can test the integration in test mode. It assumes that up to 100 different users can use the app, and their data will be excluded from statistics. When you switch the test mode off, all the users who had sent the data will be marked as testers in your project.

    1. During the next step, you can add an account to collect data from the application store or skip this step and add this information later in project Settings.

    1. The final step is to fill in the name of your app and select its genre and type. Select app type: game or app. If you choose “app” as the type, gaming events will not be tracked and displayed in the interface, even if they are integrated. Also, game-related elements will be hidden in the interface.

    You can change the project type at any time in the Settings → General settings section.

    Please note that you can select more than one genre.

    Congratulations, you have added the application to the space!

    Next steps

    Now you can see the standard devtodev interface and all the reports. Of course, the reports are empty until you start sending data to devtodev.

    If you have added an account to collect data from the market, it will take 1 day to build the report.

    If your integration uses SDK (in most cases), the next step is to integrate SDK into your app. Please read our expert tips on integration, select the necessary SDK, integrate it, and start using devtodev's full functionality!

    And please don't hesitate to ask us questions. You can find the Contact us button in the top right of the devtodev interface.

    Check your integration

    Test devices

    Add a device to test your integration:

    Test Devices

    Apps targeted at children

    When developing and publishing apps targeted at children under 13 years old, you need to ensure special conditions for data processing.

    Check out how to enable compliance mode for , , and .

    How to check event logs in devtodev interface

    You can check the incoming events in the User card () or in the (Settings -> SDK -> Integration -> Event Log).

    To check events faster, mark the User card as a Tester. This way the log in the User card will not be cached and the events will appear much quicker.

    Configure Revenue rates and transaction checks

    You can change the default revenue rates for your project in Settings -> SDK -> .

    If necessary, you can also disable some of the and set up transaction value limits.

    How to enable SDK debug logs

    Change LogLevel value to DTDLogLevel.Debug in the Initialization configuration.

    Check out examples for different platforms in the section.

    Data collected by devtodev automatically

    Some of the events and properties are sent to devtodev automatically by the SDK.

    Information about a device/user:

    • Event timestamp

    • Timezone

    • Tracking status (iOS, Android) – the state of the flag for permission of ad tracking.

    • App version (must be specified by developer for WEB projects).

    Automatically sent events:

    • Session start – beginning of application activity with the date.

    • Activity period – duration of application activity.

    • The source of app install (only from Google Play), sent once.

    Data received by the server from queries’ metadata

    • Install date – date of the first launch of an application with integrated devtodev SDK.

    • Last seen date – date of the last incoming query.

    • IP-address – anonymized IP-address.

    • Country – defined by IP.

    Web SDK Integration

    The latest up-to-date version of the Web SDK: 2.2

    Please see the changelog if you are using an outdated version.

    Integration

    Please do the following to integrate your web application with devtodev:

    1. to the Space using the wizard for adding applications.

    2. To integrate SDK, add the following line to the tag of your page:

    Starting with version 2.1, you must specify the current version number of the SDK in the URI:

    <script type="text/javascript"

    src="https://cdn.devtodev.com/sdk/web/devtodevsdk-version.js">

    </script>

    1. Initialize the SDK.

    Initialization

    In order for SDK for WEB to start working, it is necessary to perform initialization right after the page is loaded and you have a basic user identifier at your disposal.

    • You can find the App ID in the settings of the respective app in devtodev (Settings → SDK → Integration → Credentials).

    • config – is an object that is used for specifying additional properties during initialization.

    Since there’s no option to get any consistent identifier in web browsers, we recommend using as a User ID either a social network ID with your app or an ID that your server assigns to a user. It’s best to assign a User ID and specify it in the config object during the SDK initialization instead of using a method after the initialization.

    If you have a game app, we recommend specifying the current player’s lever either in the config or at the earliest possible moment after the initialization via the method.

    Config

    Example:

    Track sessions

    Session Measurement and Tracking in Mobile and Web Applications

    How devtodev SDK tracks sessions

    Session measurement is an important metric for product analysis as it allows us to determine how frequently and for how long users interact with our website or application. However, it is important to note that session tracking methods on mobile and web applications have their own peculiarities.

    When a user starts a session in the application, the SDK recognizes that the application is active, indicating that it has gained focus (when the app is brought into the foreground). If the last recorded activity was more than 10 minutes ago, a Session Start event is sent.

    Application activity refers to the period of time when the application is in focus, meaning the application or web page is open and the device screen is active. The focus is lost if the application goes into the background or if another website is opened in the current tab.

    We measure the duration of application activity using a technical event called User Engagement (UE). It starts counting the time as soon as the application receives focus and sends the activity counter data to the server.

    If, for any reason, the information about the duration of the activity could not be sent, it will be sent the next time the application is initialized and has internet access. However, the activity will only be included in devtodev reports if it has been less than 7 days since the session, as events from a previous period more than 7 days ago are ignored.

    Thus, we have information about "Session start" and the duration of activity, but there is no specific "Session end" event.

    All events performed by the user are marked with the session start date on which they occurred (sessionid field in SQL tables).

    Platform specifics

    Mobile applications

    It is difficult to determine the beginning and end of a session because users often switch between screens of different applications. If an application on a mobile device receives focus and the last active time (in focus) was more than 10 minutes ago, a new session will start and a Session Start event will be sent to devtodev.

    For example, the user opens the application, spends a minute in it, and then puts the application in the background, a Session Start event will be sent to devtodev in the first second. After a minute, when the application goes into the background or is closed, an event with information about the duration of activity (UE) will be sent to the server as the focus is lost.

    Web projects

    It is not possible to detect when the user closes the page. Therefore, the UE event (duration of activity) is sent to the server every 2 minutes. To minimize the loss of information about the session duration to no more than two minutes in case of session termination, the SDK additionally saves the duration every 5 seconds and will send the information about the last duration upon the next activity. If there is no next session, the information about the last two minutes may be lost.

    Let's consider an example where a user opens a webpage, spends 1.5 minutes on it, then opens another page on the site and spends another 1.5 minutes there.

    A Session Start event will be sent in the first second, and every 5 seconds, information about the activity will be saved. After 2 minutes from the start of the session, a UE event with 2 minutes of activity will be sent to the server, and after the third minute, the activity of 1 minute will be recorded in the Local Storage. Information about this activity will be sent during the next user session.

    Windows

    The SDK cannot control app activity for Windows Standalone projects hence this responsibility is passed on to the developer. During the SDK initialization, the activity is triggered automatically, and later the activity status will not change automatically.

    For tracking app activity, the developer can use the DTDAnalytics.StartActivity and DTDAnalytics.StopActivity methods.

    It is recommended that you use the DTDAnalytics.StopActivity method to stop the activity when the app goes into the background or being closed. If the window is re-opened from the taskbar it is recommended to renew the activity by using the DTDAnalytics.StartActivity method.

    Session metrics in reports

    In , Engagement -> , and , we encounter the following metrics:

    • Session duration – average session time of one user. Calculated as (Total Sessions Length / Number of sessions) averaged by users.

    • Number of sessions – average number of sessions per user. Calculated as the Number of sessions divided by the Number of users.

    • Total daily time spent – average total time per day spent in the user application. Calculated as Total Sessions Length divided by the number of Active Users.

    Session metrics in SQL

    SQL Wizard

    In the SQL wizard, there is a parameter called session.Duration, which is tracked by the UE event. The session.Duration parameter represents the duration of the activity, i.e., the time the application is in focus, and it is not equal to the session duration.

    sessions.Count is the number of Session Start events received from the user.

    SQL Editor

    The table has two types of eventtype field in SQL:

    • ss: represents the Session Start event received from the user.

    • ue: represents User Engagement – the time that the application was in focus (active), providing information about time parameters and activity duration.

    From this data, you can calculate the average session length by dividing the sum of activity lengths from all rows for the desired period by the sum of all session starts for the same period. We recommend using extended time periods to obtain a more reliable result.

    AI-assisted integration (Beta)

    Use AI assistance to speed up the integration process.

    Requirements:

    1. Project built with Unity.

    2. The project does not have devtodev integration yet. Projects with poorily integrated events can also participate.

    3. AI coding agent that have access to the complete application code.

    If you have any questions regarding the integration, let us know by using the form or reach out to our Customer Success team directly within the platform. We will also greatly appreciate your feedback. Please add AI INTEGRATION when submitting your request.

    We created this manual for AI code editor but you can try using other AI coding agents that can access all of application code files.

    Preparation and prompt

    Get Unity Integration Guide File

    and add it to your Unity project folder. We recommend creating a folder called docs and placing the guide inside it. This ensures the AI coding agent can easily access and use it during analysis.

    Open your AI-agent, and select your Unity project folder. The agent will begin indexing all project files, including the markdown guide. Wait a few moments until indexing is complete.

    If you have any troubles with access to Integration Guide Files, let us know by using the form or reach out to our Customer Success team directly within the platform.

    Create prompt

    Create a clear and effective prompt for the AI-agent. The prompt should:

    • Assign the AI a role (like "You are a product engineer").

    • Specify your goal: integrate DevToDev events properly.

    • Reference the guide using @devtodev_unity_integration_guide.md.

    Copy and paste the promt text into the AI-agent chat window.

    .

    The prompt accuracy is estimated at around 90%, which means that some devtodev method calls may contain errors. Therefore, it’s recommended that developers review the integration code.

    Add references to integration guide

    Attach the guide file inside the prompt. You can do this in two ways:

    1. Find all the guide mentions in the prompt and delete that placeholder text. Then, drag the file from the docs folder into the chat window. The AI-agent will automatically recognize and link the file.

    2. Alternatively, type @ and start typing "devtodev" — the agent will suggest the guide file. Usually it is at the top of the list.

    Now click Send and let the AI coding agent generate the integration.

    It will:

    • Analyze your codebase

    • Understand game logic

    • Suggest where and how to insert DevToDev analytics events.

    Events generation

    Check and review the list of events generated by the AI-agent. You can edit the list by removing the unnecessary or duplicate events. You can also ask the AI to revise its suggestions.

    After you have finished reviewing and modifying the suggested events file, you can proceed to the next step of integration. The AI-agent may then generate a new class — usually something like AnalyticsManager — to send events to Devtodev.

    Further integration

    The coding agent might show some errors if Unity has not recompiled yet. Switch to the Unity Editor and wait for it to finish compiling. Once done, the errors in AI-agent will disappear.

    The AI must not change your original game logic.

    It may add analytics calls (like DTDAnalytics.CustomEvent(...)), but should never modify player behavior, game flow, or state handling.

    If something looks wrong — ask the AI-agent to revise it.

    After generating the integration, the AI-agent will also produce a documentation file (like ANALYTICS_SETUP.md).

    It describes:

    • Where events were added

    • How to use the generated AnalyticsManager class

    • What next steps are required to finish setup.

    Follow that instruction carefully to complete your devtodev analytics integration properly.

    Remote configuration

    Remote configuration SDK integrationWorking with remote configuration in devtodev

    Payments & Anti-cheat

    Payment event integration and using anti-cheat methods

    Here you'll find the principles of processing data about real payments, tips for the integration of the Payment event and anti-cheat methods used in devtodev.

    Payment event integration

    Gross metrics are one of the key indicators of the app’s performance. Therefore, it is important to approach the integration of the very seriously.

    There are four parameters that are sent in the Payment event:

    Autocapture

    Autocapture is a set of features in devtodev that allows you to collect data automatically, without manually sending events from the client side. It is designed to simplify integration, reduce engineering effort, and ensure data consistency across platforms.

    Currently, Autocapture supports purchase tracking, refund tracking, and AI-assisted integration. Web auto-tracking is in development.

    How it works

    Each Autocapture feature collects data from validated sources such as the App Store, Google Play, or SDK behavior. In most cases, you only need to enable the feature in the devtodev settings and initialize the SDK accordingly. No additional coding is required.

    Test Devices

    In devtodev you can mark a device as a test device. You can do so in the section or in Settings -> SDK -> .

    The system will exclude from the reports all incoming events from the test devices (except Real-time dashboard). You can check the evets in Settings -> SDK -> Integration -> or in the Users & Segments section.

    When you mark the User card as a Tester, the log in the User card will not be cached and the events will appear much quicker.

    If the app is in test mode, all devices with this app are added automatically to the list of test devices (up to 100 users). You can switch to Production mode manually in Settings -> .

    Users & Segments

    Data Export

    Here you will find how you can export your data from devtodev.

    Raw data export is available for .

    Export via API

    Use devtodev API to export raw data.

    3rd Party Sources

    How to set up
    • Automatic payment tracking

    • Automatic refund tracking

    • AI-assisted integration for Unity

    Compatibility and limitations

    • Autocapture is available only for native payment systems (Google Play and App Store).

    • Unity Android refund tracking is not supported due to store receipt limitations.

    • Do not mix manual and automatic event tracking for the same transaction types.

    • Web tracking will be available via the devtodev Web SDK (currently in development).

    devtodev SDK version

  • Push token – in the case of an additional initialization by the developer and with a user’s permission.

  • Device manufacturer (iOS, Android)

  • Device model (iOS, Android)

  • Language – device’s locale data.

  • OS type

  • OS version

  • Rooted/Jailbreaked OS flag

  • User agent string

  • Different device IDs depending on the platform. Disabled when COPPA Control is enabled.

  • Android
    iOS
    MacOS
    Unity
    Users & Segments section
    Event Log
    Payments
    transation checks
    Integration
    Sessions - total number of sessions (opening or unfolding the application) for the given time period.
  • Sessions by user – average number of sessions made by one user during the period.

  • Average session length – calculated from the data obtained from session starts and user activity time during those sessions. It is defined as the sum of the length of all sessions divided by the number of sessions within a given period.

  • Windows
    Unity
    Basic Metrics
    Sessions
    other reports
    sessions
    Attribution Trackers
    App Marketplace Data
    Ad revenue
    Cohort export

    Server API

    List of all devtodev APIs

    Data API 2.0Subscription APIPush APIRaw ExportLabels API

    Below you can find APIs for 3rd party services. You can use them in case devtodev does not provide an integration to a specific 3rd party service.

    Check the list of available Ad Revenue services.

    Ad revenue API

    Check the list of available Attribution trackers.

    Custom postback API

    A/B testing

    Raw Data Export via devtodev interface

    Build a report and download it as a .csv file right in the devtodev interface.

    Export to Cloud Storage

    Configure data export to a data storage.

    Basic, Business and Enterprise price plans
    Raw Export
    Tuning
    Data Export to Cloud Storage (BigQuery / Amazon S3)

    The level of logging the SDK activity. The "No" value is used by default. For troubleshooting during integration, it is recommended to set it to "Debug", and either switch it "No" or use it only for error handling "Error" in the release version.

    applicationVersion

    String

    The app version. Cannot be empty.

    Parameter

    Type

    Description

    userId

    string

    Unique user identifier. For example, user’s ID in a social network, or a unique account name used for user identification on your server. If at the time of initialization this identifier is not yet available, specify the identifier later using

    the setUserId method.

    currentLevel

    integer

    The player level at the moment of devtodev SDK initialization. Must be greater than 0. It’s optional but we recommend using it for improving data accuracy.

    trackingAvailability

    boolean

    The property allows or disallows devtodev tracking of the user. By default, it is set to true. SDK stores the previously assigned value. Pass false if the user opted out of tracking in line with GDPR.

    logLevel

    Add the application
    setUserId
    setCurrentLevel

    string

    <script type="text/javascript" src="https://cdn.devtodev.com/sdk/web/devtodevsdk-2.2.js">
    </script>
    window.devtodev.initialize("App ID", config);
    var config = {};
    config.userId = "Unique user identifier";
    config.currentLevel = 2;
    config.trackingAvailability = true;
    config.logLevel = "Error";
    config.applicationVersion = "1";
    window.devtodev.initialize("App ID", config);

    Transaction identifier

  • Item name

  • Item price in payment currency

  • Payment currency identifier.

  • Let’s look at each of the parameters and things to keep in mind when specifying their values while integrating devtodev SDK.

    Transaction identifier

    This is one of the transaction parameters where invalid values occur most often.

    Here are the requirements for this parameter:

    1. The transaction identifier is a string value of max 64 symbols. In case this limit is exceeded the value will be shortened to 64 symbols.

    2. The identifier must be unique. Data about the transaction with already registered identifier will be discarded by the system and will not be included in statistics.

    3. We recommend using the identifier that has been assigned to the transaction by the payment system as the transaction identifier.

    4. In case your app is designed for Apple (iPhone, iPad, iPhone+iPad, or Mac) or Android (Google Play) platforms, the use of the transaction identifier assigned by the app store is mandatory!

      1. Transaction identifiers that come from apps on these platforms are checked by devtodev for their compliance with the format used by these markets. This allows us to discard the most obvious cheat transactions.

      2. It is also important to know that users who made these transactions are marked as cheaters and all their subsequent transactions are not included in statistics (you can disable this verification process in the ).

    Item name

    The item name is a string value that should not exceed 255 symbols. One of the most common mistakes when specifying the value of this parameter is specifying the localized name of the item in multi-language apps. This leads to the appearance of many records that describe the same item in different reports (for example, Virtual goods & purchases).

    One way to avoid this situation is to specify the name of the item bundle as its name.

    Item price and currency identifier

    The item price parameter contains the sum that a user paid for the item in a payment currency. The price is specified as a floating-point number.

    The currency identifier parameter must specify the currency as a three-letter code according to ISO 4217 standard (examples: USD, EUR, JPY, CNY).

    When the Payment event reaches devtodev servers, before transaction data is saved, the sum is automatically converted to USD at the actual currency rate at that moment.

    In case the currency identifier is not specified or the identifier is invalid, the transaction is considered invalid and is not counted in statistics.

    If after converting to USD the sum exceeds $1500, the transaction is considered invalid and is not counted in statistics as well (this verification can be disabled in the Settings). When the transaction is made with an in-game currency of social network, you first need to convert this currency to any real-world currency.

    It is also important to remember that the sum of the purchase sent in the Payment event shows the actual sum that the user paid (this data is used to built Gross metrics).

    In order to see your net income (Revenue metrics), you need to specify the revenue rates to calculate net profit within your total revenue. The rate can be specified as single or individual for each country. This increases the accuracy in case the part of the sum is spent on taxes and fees that are individual for each country.

    Payment validation

    Unfortunately, in some situations filling in the parameters of the Payment event is not enough for getting valid data in reports, since there can be cheat transactions. There are several ways to deal with this problem, but all of them are based either on preliminary verification of the transaction or detection of suspicious user actions.

    To prevent cheat transactions from getting into the report, you need to check the transaction in advance and omit sending the Payment event If the transaction turns out to be invalid.

    Or you can mark the user/device as a cheater and exclude their further data from all reports. It is possible to combine both methods for greater reliability.

    The process of detecting cheaters based on their behavior within apps depends on the specificity of a particular app. If you have implemented such an algorithm, you can mark suspicious users as cheaters to avoid getting data on their payments in reports. To do that, you just need to execute an SDK method or mark users via API.

    One of the conditions for increasing the reliability of transaction verification is implementing it outside of the client app. You can create the system of verification and place it on your own servers or use our out-of-the-box solution – devtodev anti-cheat system.

    devtodev anti-cheat allows to check the validity of transactions from the following app stores:

    • Apple App Store

    • Google Play Store

    • Microsoft Store (UWP).

    Recommended sequence of actions when working with transactions

    1. Get response about a completed transaction from the payment system.

    2. Either send data about the received transaction for verification by calling devtodev anti-cheat methods or use your own tools for transaction verification.

    3. If the transaction has successfully passed verification, perform the Payment event. If the transaction has not passed verification, do not perform the Payment event.

    We do not recommend to use the result of devtodev anti-cheat verification as a condition for giving or not giving in-game currency or item purchased by user.

    Payment event
    Anticheat methods
    In the Users & Segment section you can find a specific user using filters. For example, search a specific Advertisig ID or IDFA.

    Open the User Card and mark the User as a Tester.

    Test Devices

    You can check and configure your list of test devices in Settings -> SDK -> Test Devices.

    Users & Segments
    Test Devices
    Event Log
    General Settings
    Contact Us
    Cursor
    Download the Unity Integration Guide File
    Contact Us
    You can use our prompt as an example

    What to track

    You can find full description of devtodev events in this article:

    Basic Events & Custom Events

    Where to start

    Tutorial is a very important part of any project because the first session lays the foundation for future retention and monetization indicators.

    devtodev allows to analyze how users complete the tutorial, find bottlenecks, and measure the time it takes for users to complete the tutorial. These questions can be answered with the help of the Tutorial analysis report.

    If users in your project become more experienced and raise their level

    Many game projects have levels, which means that as users become more experienced, they gradually increase their level. In this case levels have a linear structure: the level N is followed by the level N+1.

    When players move to the next level, you need to use the . Reports such as Economy balance and Player levels are built by levels and are based on this event.

    Also, if your project has in-game currency, you can send information about the current amount of in-game currency players have using the LevelUp event. This data allows to evaluate the average amount of in-game currency that players have on a particular level.

    For example, this is the . It shows how users are distributed among levels, the percentage of users who remain on a particular level, the revenue of a particular level, etc.

    If it is possible to pass / fail a level in your game

    There are many projects (for example, Match-3 games), where players attempt to pass a level. Their attempts may be either successful or unsuccessful. In addition, during a certain attempt some numerical indicators can change: the number of stars, resources, in-game currency.

    To analyze these attempts, we've created a basic . With Progression event you can send information about how players pass a particular game location, whether their attempt was successful, and how numerical indicators change.

    Based on the Progression event, we build the , where all indicators are calculated by game locations, for successful and unsuccessful attempts.

    The sequence of locations passing is not important in this case: after the location N, players can go to any location M.

    If there is a virtual currency in your project

    Many games, especially f2p, have in-game currency. Players can accumulate currency, or buy it for real money. They can then spend it on virtual goods. To work with virtual currency, devtodev has developed the following events:

    • – to send information about purchases made by players. Please note that these are only purchases made with virtual currency, while information about purchases for real money is sent with the .

    • – to show information about movements of virtual currency. For example, if a player earns currency or receives it for some actions, you can use Currency Accrual to see this information.

    All the game economy reports are based on these events.

    With the help of the Currency Balances by Level tab in the (this one also requires a ), you can see how users spend, earn and accumulate currency on each game level.

    The Top Purchases tab in the allows you to analyze the structure of the consumer basket and identify the most popular items among different categories of players.

    Custom Events

    There may be situations when your project requires events that cannot be tracked by devtodev basic events. Such events can still be sent and analyzed in our system as . It is possible to specify parameter values of custom events.

    Here are some examples of user actions that can be sent as custom events: opening an in-game store, clicking on items, buying items. Based on these events, you can then build a funnel and see the conversion on each step.

    Some limits for custom events:

    1. The number of different event types sent from one project should not exceed 300.

    2. The event name must not exceed 72 characters.

    3. One event can contain up to 20 parameters, each of them with unique names of up to 32 symbols.

    4. Parameters can be string or numeric:

    Here is some expert advice to avoid problems with limits on custom events:

    • There is no need to send user IDs in parameters (they are collected by default).

    • Do not send time in the timestamp format (it is also collected by default).

    If you want to check transactions for validity

    To exclude from statistics transactions made by cheaters, you can use devtodev . By using this method, you will be able to check payments for validity before sending them to devtodev.

    The verification process is the following:

    1. Get response about a completed transaction from the payment system.

    2. Either send data about the received transaction for verification by calling devtodev anti-cheat methods or use your own tools for transaction verification.

    3. If the transaction has successfully passed verification, perform the Payment event. If the transaction has not passed verification, do not perform the Payment event.

    We do not recommend to use devtodev Anticheat method as the only tool to validate transactions.

    UE4

    This generation of SDK is deprecated and is no longer supported. Information about the current version can be found here.

    Please do the following to integrate your application with devtodev:

    1. Add the application to the Space using the wizard for adding application.

    2. Go to your project directory and place the plugin content here: ProjectName/Plugins

    3. Restart Unreal Editor and open the plugin menu (Window > Plugins). In the "Analytics" plugin group of your project select DevToDev (and "Blueprint Analytics Framework" in case if you use blueprints). You will be offered to restart Unreal Editor again.

    4. Finally, add the following strings into the DefaultEngine.ini configuration file (the file is in "Config" folder of your project):

    Project Settings

    To get an access to the DevToDev settings, go to Project Settings > DevToDev

    Get the keys (they can be found in the application settings: Settings -> SDK -> Integration) and insert the keys in this window. Then choose the Enable Push Notifications option in case if you want to send push notifications through devtodev service.

    Initialization

    All the events are available in the Analytics block of your Blueprint.

    To initialize SDK in a blueprint, first call the "Start Session" event from the Analytics Blueprint Library.

    or from the following code

    Automatic refund tracking

    Devtodev allows you to collect refund data automatically for purchases made through the Google Play and App Store platforms. Refunds are recorded as transactions with negative amounts and matched to original purchases whenever possible.

    The automatic refund tracking system helps you monitor actual revenue more accurately and analyze refund patterns without manual event configuration.

    Automatic refund tracking is available only for the Google Play and App Store platforms, and only for purchases made using their native payment systems. If you are using a third-party payment system, you need to send refunds manually using the Real Payment event with a negative amount.

    If you're using Unity and sending Real Payment events manually, refunds may not be processed correctly. This is because the purchase token is partially truncated in the SDK, which prevents it from being matched with store refunds.

    We strongly recommend enabling to ensure correct refund matching on Unity for Android.

    How it works

    Refunds are collected via store APIs or postbacks and processed on the server side. For each refund:

    • devtodev attempts to match it to an original transaction by transaction ID.

    • The refund is saved as a transaction with a negative amount.

    • The event is dated using the refund processing date (not the original payment date).

    Note: Refund tracking works automatically — no changes in the SDK are required.

    Setup

    We recommend using automatic refund tracking together with to ensure accurate matching and avoid inconsistencies in reports.

    To enable refund tracking:

    • Set up integration with the App Store or Google Play.

    • Go to: Settings → Payments integration → IA refunds tracking.

    • Click the ✎ icon next to IA refunds tracking and fill in the fields related to the platform.

    • Refund tracking starts working automatically on supported platforms after you enable it in the devtodev interface.

    For details on how to set up integration with app stores, see:

    Mac OS

    This generation of SDK is deprecated and is no longer supported. Information about the current version can be found here.

    Please do the following to integrate your application with devtodev:

    1. Add the application to the Space using the wizard for adding application.

    2. and add it to 'Linked Frameworks and Libraries' list in the general settings of the project.

    3. Add init method into didFinishLaunchingWithOptions method of your AppDelegate.m

      App ID and Secret key can be found in the application settings (Open "Settings" → "SDK" → "Integration").

    If the application you integrate SDK in is a part of a cross-platform project, then the user data initialization is required.

    Since the analytics of cross-platform projects is based on a unique user (unlike the usual projects where it is based on device identifiers), you have to:

    • Set the unique cross-platform user identifier (it will be used for a cross-platform project data collection).

    • Actualize the user data. Mostly it is about game applications where the player has a game level as a characteristic. For such projects, you need to set the current player level.

    If your application allows user to re-login (changing the user during the working session of application), then the setUserID and setCurrentLevel methods should be called just after the authorization. You don't need to call the SDK initialization one more time.

    We recommend you set the user identifier before SDK initialization, otherwise, the user identifier from the previous session will be used since the SDK initialization moment till the setUserID method call.

    If your cross-platform application is supposed to be used without cross-platform authorization, don't use the setUserID method or use the empty string ("") as the user identifier. SDK will assign the unique identifier to a user. This identifier will be used until the real cross-platform identifier assigns to the user.

    Debug mode

    To enable the debug mode and make SDK notifications displayed in the console, use this method:

    Automatic payment tracking

    Devtodev allows you to collect payment and subscription information automatically for the Google Play and App Store platforms. All you need to do is enable the corresponding module during the SDK initialization and configure store setting in the devtodev interface.

    The automatic payment tracking system will help you avoid integration errors with data submitted using the Real Payment and Subscription events. This system will also help you avoid sending fraudulent transaction data, as each transaction is additionally verified on the Google Play and App Store platforms before being recorded in the database.

    Important: Automatic payment tracking is only available for the Google Play and App Store platforms, and only if you are using their native payment systems. If you are using a third-party payment system, you will need to integrate the Real Payment Basic event.

    Please note that you should not simultaneously use an automatic payment tracking system and send payment data using the integrated and events, as this may result in duplicate payment data. Devtodev will do its best to prevent including such duplicates in the statistics; however, for some integration cases, this is impossible (e.g. when using Google Play Purchase Token instead of Transaction ID in Real Payment event data). Therefore, when you switch to automatic payment tracking, we recommend that you remove the integration of and events, except in the case of tracking transactions with third-party payment systems.

    While automatic payment tracking helps avoid fraudulent transaction data, this mechanism does not automatically mark users with such payments as cheaters. In case you would like to mark users as cheaters, use a or .

    Windows 8.1 and 10

    This generation of SDK is deprecated and is no longer supported. Information about the current version can be found here.

    You have to enable Internet (enabled by default in Windows 10) and Location (if needed) in the Capabilities tab of Package.appxmanifest for correct work of the SDK.

    1. To start working with the SDK, add the DevToDev.winmd and DevToDev.Background.winmd to the project references.

    2. Initialize the library at Application Launching event.

    App ID and Secret key can be found in the application settings (Open "Settings" → "SDK" → "Integration").

    Example:

    If the application you integrate SDK in is a part of cross-platform project, then the user data initialization is required.

    Since the analytics of a cross-platform projects is based on a unique user (unlike the usual projects where it is based on device identifiers), you have to:

    • Set the unique cross-platform user identifier (it will be used for a cross-platform project data collection).

    • Actualize the user data. Mostly it is about game applications where the player has a game level as a characteristic. For such projects you need to set the current player level.

    We recommend you set the user identifier before SDK initialization, otherwise, the user identifier from the previous session will be used since the SDK initialization moment till the UserID field is set.

    If your cross-platform application supposes to be used without cross-platform authorization, don't use the UserID field or use the empty string ("") as the user identifier. SDK will assign the unique identifier to the user. This identifier will be used until the real cross-platform identifier assigns to the user.

    If your application allows user to re-login (changing the user during the working session of application), then the UserID field and SetCurrentLevel method should be called just after the authorization. You don't need to call the SDK initialization one more time.

    Debug mode

    To enable the debug mode and make SDK notifications displayed in the console use this method:

    Attribution Trackers

    3rd party attribution sources integration

    devtodev has integration with the following attribution trackers:

    • AppsFlyer

    • Adjust

    • Branch.io

    If the tracker you are using is not in the list, you can send data via

    Windows

    1. NuGet Installation

    Package Manager UI

    App Store

    1

    2

    Kochava

    Integration

    1. Login to your Kochava account and select the application you want to configure.

    Unity

    General Information

    In order to work with push notifications, you need to integrate the messaging module. You can do it by using one of the two methods: with the help of the Unity Package Manager (recommended) or by manually importing the unitypackage.

    Adjust

    Integration

    Official Adjust documentation for enabling devtodev partner module can be found .

    1. Go to the

    Branch.io

    Integration

    1. Sign in into Branch.io

    2. Go to Data import & Export in the left menu.

    Contact us to request access. Use form or reach out to our Customer Success team directly within the platform. We will also greatly appreciate your feedback. Please add REMOTE CONFIGS when submitting your request.

    Contact us to request access. Use form or reach out to our Customer Success team directly within the platform. We will also greatly appreciate your feedback. Please add REMOTE CONFIGS when submitting your request.

    Settings

    No SDK-side changes are required.

    automatic payment tracking
    automatic payment tracking
    App Store
    Google Play
    Real Payment
    Subscription
    Real payment
    Subscription
    dedicated method
    flag the User card
    App Store
    Google Play
    Aghanim
    Kochava
    Tenjin
    Tune (MAT)
    Singular
    Custom postback API.
    Contact Us
    Contact Us
  • The maximum length of string parameter values is 255 characters.

  • The number of unique string parameter values cannot exceed 50000. When it exceeds 50000, the system will block the parameter.

  • LevelUp event
    Player levels report
    Progression event
    Locations report
    Virtual Currency Payment
    Payment event
    Currency Accrual
    Economy Balance report
    LevelUp event
    Virtual goods & Purchases report
    custom events
    Anticheat method
    Download the latest version of devtodev SDK from the GitHub repository.
    /**
    * <param name="appKey">App ID</param>
    * <param name="appSecret">Application secret key</param>
    */
    DevToDev.SDK.Initialize(string appKey, string appSecret);
    DevToDev.SDK.Initialize("3f2504e0-4f89-11d3-9a0c-0305e82c3301", "a8f5f167f44f4964e6c998dee827110c");
    DevToDev.SDK.UserID = "activeUserId"; //cross-platform user identifier (64 symbols max.)
    
    /**
    * <param name="appKey">App ID</param>
    * <param name="appSecret">Application secret key</param>
    */
    DevToDev.SDK.Initialize(string appKey, string appSecret);
    
    /**
    * <param name="level">Current level</param>
    */
    DevToDev.SDK.SetCurrentLevel(int level);
    //to enable logging
    DevToDev.SDK.LogEnabled = true;
    
    //to disable loging
    DevToDev.SDK.LogEnabled = false;
    Find the DevToDev.Analytics.Uwp package using the package manager search engine and click Install. The latest version of the package is recommended.

    2. SDK Initialization

    Initialize the library using the following code:

    • You can find the App ID in the settings of the respective app in devtodev (Settings → SDK → Integration → Credentials).

    • config - is a DTDAnalyticsConfiguration object instance that is used for specifying additional properties during initialization.

    DTDAnalyticsConfiguration

    Parameter

    Type

    Description

    currentLevel

    Integer

    The player level at the moment of devtodev SDK initialization. It’s optional but we recommend using it for improving data accuracy.

    userId

    String

    A custom user ID assigned by the developer. In the case of default calculation by device IDs, the identifier can be used for searching users in devtodev. In case the project uses calculation by user IDs, the parameter is mandatory because it becomes the principal calculation ID in devtodev.

    trackingAvailability

    DTDTrackingStatus (enum)

    The property allows or disallows devtodev tracking of the user. By default, it is set to DTDTrackingStatus.Enable. SDK stores the previously assigned value. Pass DTDTrackingStatus.Disable if the user opted out of tracking in line with GDPR.

    logLevel

    Example:

    1. NuGet Installation

    Package Manager UI

    Find the DevToDev.Analytics package using the package manager search engine and click Install. The latest version of the package is recommended.

    2. SDK Initialization

    Initialize the library using the following code:

    • You can find the App ID in the settings of the respective app in devtodev (Settings → SDK → Integration → Credentials).

    • config - is a DTDAnalyticsConfiguration object instance that is used for specifying additional properties during initialization.

    DTDAnalyticsConfiguration

    Example:

    3. SDK Activity

    The SDK can’t control app activity hence this responsibility is passed on to the developer. During the SDK initialization, the activity is triggered automatically, and later the activity status will not change automatically. For tracking app activity, the developer can use the DTDAnalytics.StartActivity and DTDAnalytics.StopActivity methods. It is recommended that you use the DTDAnalytics.StopActivity method to stop the activity when the app goes into the background or being closed. If the window is re-opened from the taskbar it is recommended to renew the activity by using the DTDAnalytics.StartActivity method.

    Settings on devtodev side

    Settings on the App Store Connect side

    To get detailed information about the transaction, devtodev requires access to the App Store Server API. To grant this access, you will need to generate an In-App Purchase API key.

    Generating the key:

    1. Authorize on App Store Connect.

    2. Navigate to the Users and Access section.

    3. In the Integrations tab (1), select In-App Purchase from the menu on the left (2), and click (+) to add the key (3).

    4. Specify the name of the key, for example: "devtodev API Key", and click Generate.

    5. To grant the necessary access to the devtodev service, it is necessary to pass the following information:

      • Issuer ID

      • Key ID

      • The generated .p8 file of the In-App Purchase key

    Settings on devtodev side

    1. Go to Settings → Payments integration → IA refunds tracking.

    2. Fill in the integration form with the data obtained earlier from the App Store:

      • App Bundle ID

      • Issuer ID

      • Key ID

      • Upload the .p8 file of the In-App Purchase key.

    3. When the integration is complete, the status will change to Active.

    Settings on the App Store Connect side
    Go to the partner configuration page and click the Add a Configuration button.
    1. Find devtodev in Media Partners list, select it and click the Go button.

    1. Click the Edit button on the Install event.

    1. Open the application that you are configuring in devtodev. Open Settings -> 3rd party sources -> Attribution tracking, turn on the toggle switch on Kochava panel and copy devtodev API Key from the panel.

    1. Insert copied API key into Kochava postback settings. Copy all other fields from the following image and click the Save button.

    Important: Select Delivery method -> All

    1. Make sure that postback has appeared on the Partner Configuration page.

    CPI data integration

    devtodev allows our customers to query CPI data from Kochava via API in order to get more accurate install costs.

    Check the Enable receiving CPI info by Kochava API box and fill in the form to switch on CPI data collection.

    Documentation on these fields and how to find them can be found here.

    Integration by using the Unity Package Manager

    If you integrated the devtodev package manually, then you need to delete the Assets/DevToDev and Plugins/DevToDev folders.

    1. In the Package Manager (Window → Package Manager), click + in the top left corner and select Add package from git URL.

    2. Copy the repository URL https://github.com/devtodev-analytics/package_Messaging.git to the input box and click Add.

    3. Wait for the Unity Package Manager to download the package and all the necessary dependencies.

    4. If you use Android, allow dependencies in Assets → External Dependency Manager → Android Resolver → Resolve.

    You can pick a specific SDK version by adding # and a version number at the end of the URL, for example: https://github.com/devtodev-analytics/package_Messaging.git#v3.3.2

    Integration by importing unitypackage

    1. Download the latest version of devtodev package from the repository: https://github.com/devtodev-analytics/Unity-sdk-3.0/releases/latest

    2. Import DTDAnalytics.unitypackage to your project

    3. Import DTDMessaging.unitypackage to your project

    4. If you use Android, allow dependencies in Assets → External Dependency Manager → Android Resolver → Resolve.

    Platform specific integration

    Android
    iOS
    Windows (UWP/WSA)

    Open Partner Setup:

    1. Click Add Partners:

    1. Search devtodev and click blue plus button:

    2. Go to devtodev Settings -> 3rd party sources -> Attribution tracking page of a preferred app and copy your API Key:

    1. Enter your API Key in the field for corresponding platform and click Save:

    2. Switch on the attribution in devtodev interface (Settings -> 3rd party sources -> Attribution tracking):

    Improve matching

    To improve the synchronization of the user data obtained by devtodev with the install data from Adjust, we recommend that you follow the guideline below.

    The following recommendation is especially important for iOS since Advertising ID (IDFA) is no longer always available, and Vendor ID (IDFV) is not always sent by Adjust. The best solution here is to send Adjust’s user ID data to devtodev.

    You need to make some changes to the devtodev SDK integration. Send a user profile custom property named ad_tracker_id and assign it a string value of {adid}, which can be obtained from Adjust.

    If you have multiple mobile apps for which you would like to set up this integration, you will need to follow these steps for each one of your mobile apps individually.

    You can find this field later as MMP_ID in the or mmpid in the in SQL.

    CPI data integration

    devtodev allows our customers to query CPI data from Adjust via API in order to get more accurate install costs. Check the Enable receiving CPI info by Adjust KPI Service box and fill in the form to switch on CPI data collection.

    Documentation on these fields and how to find them can be found here.

    here
    Adjust Dashboard

    Go to Data Feeds in the left menu.

  • Go to Webhooks in top tabs.

    1. Click Add new webhook button

    2. Go to devtodev Settings -> 3rd party sources -> Attribution tracking and copy postback URL, then enable integration by clicking the On switch.

    1. In Branch.io, paste the callback to webhook Send field and click Save.

    1. Integration is complete.

    CPI data integration

    devtodev allows our customers to query CPI data from Branch.io via API in order to get more accurate install costs.

    Check the Enable receiving CPI info by Query API box and fill in the form to switch on CPI data collection.

    Documentation on these fields and how to find them can be found here.

    Download the latest version of devtodev SDK from the GitHub repository.
    Download the latest version of the devtodev.framework from the GitHub repository

    2024

    Here are some of the new features you might have missed.

    Create Alerts in Basic Metrics reports

    Released: 21/11/2024

    It is now possible to create an alert for a number of metrics straight from the report. Click on the Create alert button in the three dots menu and you will be promted to the Alerts wizard with the preselected metric.

    Learn more about the Basic Metrics report


    Custom Events: updated Total value overview

    Released: 21/11/2024

    By default you can see the Total values for selected events above the report. We've added a Show total button so now you can hide this information block. We've also moved all the report customization settings (Metrics settings and Conditional Formatting) to the left for easier navigation.


    Widgets: last update time

    Released: 21/11/2024

    You can now check if the data in the widget is up to date and update it without the need to refresh the whole dashboard. Click on three dots menu and select Refresh data.


    Report sharing update

    Released: 17/10/2024

    We have added a Select All option so you can easily share your report or dashboard with everyone. If you want to share multiple reports, there is now an option to batch select and share them in the Saved reports or Saved Dashboards section.


    Dashboard control management update

    Released: 13/08/2024

    Unified Control for Report Filters and SQL Variables: Before this update you could only add a Period type control for widgets based on non-SQL reports (Basic metrics / Custom Events / Funnel reports). Now it is possible to create and apply common filters such as Country, Language, Channel, Campaign and Paying status.

    Simplified default variable value change: You can change the default value directly in the Manage controls panel, without the need to navigate to the original widget.


    Add Player Levels report to custom dashboards

    Released: 13/08/2024

    The Player Levels report has some unique ready-to-use metrics such as Gross revenue and number or players remaining at a specific level. You can now add this report as a widget to a custom dashboard and get the full picture for your game analysis.


    Export widgets to CSV

    Released: 22/07/2024

    Save the report results from the dashboard without opening the original report using Export to CSV. The result will be saved as table. This can be useful for further data processing or sharing the report to colleagues without access to devtodev.


    Add Conversion to payments report to custom dashboards

    Released: 21/06/2024

    You can now save Conversion to N payment, Period until payments, Top converting goods charts to a custom dashboard as widgets. This information will help you analyse the payments in a more convenient way.


    Like operator in Custom Events, Funnels and User Flow

    Released: 21/06/2024

    The Like opertor allows you to create a mask for the string parameter values.

    For example, you have a parameter called Item and it has values: offer1, offer2, offer3, offer4. Use the Like operator and type offer, this will select all four of these values in the resulting report.


    Add Locations report to custom dashboards

    Released: 21/06/2024

    The Locations report contains ready-made metrics that allow you to see how users pass the different locations. You can now save this report to a dashboard with other metrics to see the whole picture.


    Change metrics' names in Custom events report

    Released: 14/05/2024

    This update gives you an ability to change the name of any metric in Custom events. You can use more convenient titles and adjust the values with a Round setting. There is also an option to change the Units for a single number widget. The applied metrics' settings will also be saved when you share the report or add it to a dashboard.


    Add Cumulative ARPU report to custom dashboards

    Released: 14/05/2024

    Results from Devtodev’s Cumulative ARPU report can now be saved to a custom dashboard as a widget. This information helps you track crucial Cumulative ARPU metric values for your project.


    Acquisition: trafic source filter

    Released: 30/04/2024

    Previously data from Custom Postback API or SDK Install referrer was not available in the Acquisition section. Now, we have added a Source filter in the Detailed stats report, where you can select the needed trafic sources.


    Table widget settings

    Released: 30/04/2024

    on your dashboard. Improve readability by adjusting the column width automatically (Autofit column width) or by hand. We have also added a Wrap text option for heading and table rows to allow for longer titles.


    Extended period of segment expiration

    Released: 10/04/2024

    With this update, we have extended the expiration date for unused segments to 60 days. If you do not apply the segment to any report, it will expire. However, you will have 30 days to recover this segment before it’s completely deleted.


    Axis scale settings

    Released: 21/03/2024

    Users now are able to customize axis boundaries on their charts to more accurately represent data trends. In cases where the charts are not optimally positioned (or you are not happy with their placement, or want to change the position of the chart), simply clicking on the Axis Scale option allows you to define the minimum and maximum values for the axis.


    Managing content created by deleted users

    Released: 20/03/2024

    When a user deletes their account, or in case the user is removed from the space, their content remains in devtodev. Now, the team can manage the ownership of such content. For example, you can take ownership of the report made by the deleted user, or you can delete it from the space.


    Transfer space ownership to another user

    Released: 20/03/2024

    When the space owner is changing, they need to transfer their ownership. With this update, the owner of the space can select a new owner in User settings and delegate their owner access.


    Cohort Timespent in Basic Metrics

    Released: 12/03/2024

    We’ve added a new metric to our Basic Metrics report – the Cohort Timespent. Previously, you may have known this metric as “Cohort Playtime,” which was created specifically to evaluate user engagement in the Sessions report. And now this metric is also available in Basic Metrics for both game projects and applications.


    devtodev SDK for Godot Engine

    Released: 27/02/2024

    Now you can create games and apps from scratch on all popular engines, including Godot, all while utilizing detailed data analytics tools. Devtodev platform will support you in understanding player behavior, optimization, and increasing user engagement throughout the entire lifetime of the product.

    The new Godot SDK is now available for iOS, macOS, and Android platforms, providing you with the tools you need to take your games and apps to the next level.


    Copy dynamic segments

    Released: 28/01/2024

    An existing dynamic segment can be duplicated with just one click. Simply copy the segment and adjust its settings to save time when creating a segment with similar conditions.


    More Alert options

    Released: 09/01/2024

    You can receive notifications if the number of basic or custom events suddenly changes or differs from a specific day. These alerts will inform you about any positive or negative fluctuations.

    Also, two new conditions have become available: a comparison with the previous day and a comparison with the same day last week. This allows the alert to activate when yesterday's event count is different from the count on the chosen day.

    macOS

    Manual installation

    1. Download the latest version of devtodev SDK from the repository

    2. Add DTDAnalytics.xcframework to the project (with Do Not Embed specified)

    3. Add frameworks:

    • AppTrackingTransparency.framework

    • AdSupport.framework

    4. Add initialization todidFinishLaunchingWithOptions method:

    • An App ID can be found in the settings of the respective app in devtodev (Settings → SDK → Integration → Credentials).

    • config - an object instance of DTDAnalyticsConfiguration, which is used for specifying additional properties during the initialization.

    DTDAnalyticsConfiguration

    Example:

    Integration features

    For Objective-C

    1. Create Bridging-Header. To do this, you need to add any swift file to the project (don’t delete it later) and choose ‘Create Bridging Header’ in the offered dialog box.

    2. Make sure that the ‘Build Settings’ for ‘Defines Module’ value evaluates to ‘YES’.

    3. While importing, use: #import <DTDAnalytics/DTDAnalytics-Swift.h>

    For SwiftUI

    For SDK to function properly, it needs to be integrated at the earliest moment of the app launch. It is recommended that you use the following method of main entry point initialization:

    Apps targeted at children

    When developing and publishing apps targeted at children under 13 years old, you need to ensure special conditions for data processing. Any mobile app aimed at children or intended for users in a region with strict regulations on child online protection, must comply with current laws.

    Please study the following requirements:

    • USA:

    • EU:

    If your app has to comply with the legal requirements (COPPA), use the following recommendations:

    1. Implement the coppaControlEnable method. The method disables collection of ad IDs and vendor IDs (IDFA, IDFV).

    2. To comply with

      1. Remove AppTrackingTransparency.framework and all the links pointing to it.

    Call the coppaControlEnable method before SDK initialization. If the method was not called, the SDK will work as before.

    Aghanim

    This integration is configured only in Aghanim.

    By connecting Aghanim with devtodev, you can track player-generated events from the Game Hub, ensuring precise tracking of user actions across your entire game environment.

    Step 1: Configure the Integration in Aghanim

    1. Go to the Aghanim Dashboard → Aghanim Connect → .

    2. Click the Install button to enable the integration.

    Step 2: Add devtodev attributes to player.verify webhook response

    To ensure devtodev correctly identifies users and attributes their actions on the Game Hub, include the devtodev-specific attributes in the webhook response:

    Key
    Type
    Description
    Required?

    The DevtodevDeviceId schema

    The device ID object must contain at least one of the following identifiers:

    Key
    Type
    Description

    Example webhook response

    Once the integration is set up, Aghanim will automatically send events to devtodev, allowing you to track user purchases from the game hub.

    iOS

    This generation of SDK is deprecated and is no longer supported. Information about the current version can be found here.

    Please perform the following actions to integrate your application with devtodev system:

    • add the application to the Space using the wizard for adding application

    • or install via CocoaPods

    • integrate SDK into your application. The integration may be whether partial or including all the possibilities.

    CocoaPods

    is the easiest way to add devtodev into your iOS project.

    1. Firstly, install CocoaPods using

    2. Create a file in your Xcode project called Podfile and add the following:

    3. Run

      in your Xcode project directory. CocoaPods should download and install the devtodev library, and create a new Xcode workspace. Open this workspace in Xcode.

    Manual installation

    1. Download the latest version of devtodev SDK from the repository.

    2. Include devtodev.framework dependency:

    3. Link against the embedded framework:

      Add devtodev.framework to the Linked Frameworks and Libraries section.

    We recommend you set the user identifier before SDK initialization, otherwise the user identifier from the previous session will be used since the SDK initialization moment till the setUserID method call.

    If your cross-platform application is supposed to be used without cross-platform authorization, don't use the setUserID method or use the empty string ("") as the user identifier. SDK will assign the unique identifier to the user. This identifier will be used until the real cross-platform identifier is assigned to the user.

    If your application allows user to re-login (changing the user during the working session of application), then the setUserID and setCurrentLevel methods should be called just after the authorization. You don't need to call the SDK initialization one more time.

    Debug mode

    To enable the debug mode and make SDK notifications displayed in the console use this method:

    AppsFlyer

    Integration

    1. Copy devtodev API key from “3rd Party Attribution. AppsFlyer” panel on " Settings -> 3rd party sources -> Attribution tracking" page in devtodev system. Don't forget to turn on the service.

    1. Sign in with .

    2. Open the and start manage integration.

    1. Select your app, paste the devtodev API key you copied before, select "All media sources, including organic", activate partner and click "Save integration". Integration completed.

    For iOS applications we recommend turning off the Advanced Privacy (for iOS 14.5+ and later) option.

    Improve matching

    To improve the synchronization of the user data obtained by devtodev with the install data from AppsFlyer, we recommend that you follow the guideline below. The following recommendation is especially important for iOS since Advertising ID (IDFA) is no longer always available, and Vendor ID (IDFV) is not always sent by AppsFlyer. The best solution here is to send AppsFlyer’s user ID data to devtodev.

    You need to make some changes to the devtodev SDK integration. You need to send a string parameter named "ad_tracker_id" to the while assigning to its value an AppsFlyer ID, which can be .

    If you have multiple mobile apps for which you would like to set up this integration, you will need to follow these steps for each one of your mobile apps individually. You can find this field later as MMP_ID in the or

    CPI data integration

    devtodev allows our customers to query CPI data from AppsFlyer via Pull API.

    AppsFlyer has API daily rate limits, so adding CPI integration results in a single daily request per app.

    Integration can be enabled by checking the appropriate box. You will need an , which can be found on the Profile -> Security Center -> . For Apple platform App ID is Application ID without the 'id' prefix. For Android platform App ID is Android Package name. Note: App IDs are case sensitive.

    Web

    This generation of SDK is deprecated and is no longer supported. Information about the current version can be found here.

    Please do the following to integrate your web application with devtodev:

    1. Add the application to the Space using the wizard for adding application.

    2. To integrate SDK, add the following line to the tag of your page:

    Initialization

    In order for SDK for WEB to start working, it is necessary to perform initialization right after the page is loaded and you have a basic user identifier at your disposal.

    In case User ID is changed after SDK was initiated, the method should be called repeatedly with indication of a new User ID. For example, when user signs into another account in a launched messenger application.

    If a user has no unique identifier, e.g. if it is possible to use your application / site without authorization), but you need to get stats for such users, don't set userId during the initialization or set an empty string ("") or null as value of userId. SDK will assign the unique identifier to the user. This identifier will be used until the real identifier assigns to the user.

    Unique API key can be found in the application settings: "Settings" → "SDK" → "Integration".

    Cross-platform application Initialization

    In cross-platform applications, the additional user identifier can be used. It is a user cross-platform ID which is unique for all of the platforms. And if a cross-platform ID differs from the ID that is main for the platform, you need to set the cross-platform ID. The cross-platform ID combines the user data for a cross-platform project.

    We recommend you apply this method before the SDK initialization, otherwise, the user identifier from the previous session will be used since the SDK initialization moment till the setCrossplatformUserId method call.

    If it is difficult to do, set a cross-platform ID as soon as it is available in the application after SDK initialization.

    If your application allows user to re-login (changing the user during the working session of application), then the setCrossplatformUserId method should be called just after the authorization. You don't need to call the SDK initialization one more time.

    Additional initialization

    For the most precise data collection, we strongly recommend specifying some information about the user right after SDK is initiated.

    User data initialization

    In the first place, this additional initialization is required for gaming applications where the player has a game level as a characteristic.

    Application data initialization

    It is not obligatory, but if you want to have the ability to build reports with regard to the version of your application, use this method before initialization.

    Debug mode

    To enable the debug mode and make SDK notifications displayed in the console, use this method:

    UE4

    Integration of push notification on UE4

    This generation of SDK is deprecated and is no longer supported. Information about the current version can be found here.

    General information

    To enable Push Notifications you will have to perform the following actions:

    • Add the application to your space in devtodev system

    • Android. Get API key from Google APIs Console. It is nessesary to activate Google Cloud Messaging for Android before key generation. Detailed information on how to receive an API key you can find in native Android devtodev SDK documentation

    • iOS. Generate Developer or Production Certificate for the application and get Private key file (.p12) on its basis. Detailed information on how to receive a Private key file you can find in native iOS devtodev SDK documentation

    Project settings

    Set Push Notification Enabled in blueprint.

    Creating a new push notification in devtodev interface

    1. Open PUSH NOTIFICATIONS section and click on "Add new campaign" button

    2. Fill in campaign name, select an app for delivery*

    3. Choose user group to send a message. You can choose existing segment or create a new one

    4. Enter notification details

    You can create a campaign only after at least one push token comes from devtodev SDK integrated to your application. Otherwise the app will not be displayed in the list.

    Windows (UWP)

    Push Notifications (UWP)

    The DevToDev.Messaging package necessary for notifications is available in the NuGet package manager.

    Android

    This generation of SDK is deprecated and is no longer supported. Information about the .

    SDK is available as a library in AAR (recommended) and JAR. The library is available in Maven Central repository and on repository.

    Step 1. If you use Gradle for the applications build, add mavenCentral() into gradle.build file of your application and specify the following relationship in dependencies block:

    In case you don't use Gradle, you can and add the library into the project.

    Step 2. Initialize the library in the first Activity method onCreate() in the following way:

    App ID and Secret key can be found in the application settings: "Settings" → "SDK" → "Integration".

    var config = new DTDAnalyticsConfiguration();
    config.LogLevel = DTDLogLevel.Error;
    DTDAnalytics.Initialize("App ID", config);
    var config = new DevToDev.Analytics.DTDAnalyticsConfiguration();
    config.LogLevel = DTDLogLevel.No;
    config.CurrentLevel = 2;
    config.UserId = "CustomUserId";
    config.TrackingAvailability = DTDTrackingStatus.Enable;
    DevToDev.Analytics.DTDAnalytics.Initialize("App ID", config);
    [Analytics]
    ProviderModuleName=DevToDev
    FAnalytics::Get().GetDefaultConfiguredProvider()->StartSession();
      #import "AppDelegate.h"
      #import <devtodev/DevToDev.h>
      @interface AppDelegate ()
      @end
      @implementation AppDelegate
      - (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
              [DevToDev initWithKey:@"appKey" andSecretKey:@"secretKey"];
      }
      @end
    /**
    * Method allows to initialize the user. It applies when SDK initialization or user relogin.
    * @param String activeUserId - unique cross-platform user identifier (max. 64 symbols)
    */
    [DevToDev setUserID:@"activeUserId"];
    
    /**
    * Method sets the current user level. Using this method allows to actualize the SDK user data
    * in game cross-platform applications.
    * @param NSUInteger level - number of current game level of the user
    */
    [DevToDev setCurrentLevel:level];
    
    /**
    * devtodev App Id and Secret key can be found in the devtodev application
    * settings page ("Settings" → "SDK" → "Integration")
    */
    [DevToDev initWithKey:applicationId andSecretKey:secretKey];
    /**
    * @param BOOL isActive
    */
    [DevToDev setActiveLog: (BOOL) isActive];
    Check out this article for more details
    Check out this article for more details
    Check out this article for more details
    Learn more about dashboard controls
    Learn more about the Player Levels report
    Learn more about dashboard widgets
    Learn more about the Conversion to payments report
    Learn more about Custom events and Funnels reports
    Learn more about the User Flow report
    Learn more about the Location report
    Learn more about the Custom events report
    Learn more about the Cumulative ARPU report
    Learn more about the Detailed stats report
    Customize the tables
    Check out this article for more details
    Learn more about Segments
    Check out this article for more details
    Learn more about User management
    Learn more about ownership transfer
    Check out this article about Cohort Timespent
    Learn how to integrate Godot SDK
    Learn more about Segments
    Learn more about Alerts
    mmpid
    in the
    in SQL.
    AppsFlyer
    devtodev Tech Partner integration page
    user profile custom property
    obtained from AppsFlyer SDK
    User card
    API token V.2.0
    API tokens page
    users table
    This example is for game application made with Unity, however, the process is similar for other applications types

    attributes.devtodev_apikey

    string

    The API key of the project in devtodev. Obtain the API key by going to Settings → SDK → Integration in the application menu.

    Yes

    attributes.devtodev_device_id

    DevtodevDeviceId

    The device ID object.

    Yes

    devtodevId

    number

    The devtodev ID is the primary numeric identifier for the device/user account in the devtodev database. The devtodevId can be obtained from the devtodev SDK.

    userId

    string

    A custom user ID assigned by the developer. Usually, a user ID on the developer's server. The ID must be specified during devtodev SDK initialization, or specified using the SetUserID method.

    advertisingId

    string

    The Advertising ID or IDFA of the user device.

    Devtodev
    player.verify
    Real Payments
    Integration samples of devtodev SDK for WEB are available on GitHub.

    The devtodev analytics library contains an implementation of FirebaseMessagingService for working with push notifications. If you want to use your own or a third-party push notification service instead of implementing “devtodev push notification”, then you need to disable the built-in service in the manifest file.

    Example:

    If you want to use our SDK to work with push notifications, see this doc.

    Step 3. Add the following lines at the bottom of proguard.config

    Additional initialization

    If the application you integrate SDK in is a part of a cross-platform project, then the user data initialization is required.

    Since the analytics of cross-platform projects is based on a unique user (unlike the usual projects where it is based on device identifiers), you have to:

    • Set the unique cross-platform user identifier (it will be used for cross-platform project data collection).

    • Actualize the user data. Mostly it is about game applications where the player has a game level as a characteristic. For such projects you need to set the current player level.

    We recommend you set the user identifier before SDK initialization, otherwise, the user identifier from the previous session will be used since the SDK initialization moment till the setUserID method call.

    If your cross-platform application is supposed to be used without cross-platform authorization, don't use the setUserID method or use the empty string ("") as the user identifier. SDK will assign the unique identifier to the user. This identifier will be used until the real cross-platform identifier assigns to the user.

    If your application allows user to re-login (changing the user during the working session of application), then the setUserID and setCurrentLevel methods should be called just after the authorization. You don't need to call the SDK initialization one more time.

    Debug mode

    To enable the debug mode and make SDK notifications displayed in the console use this method:

    current version can be found here
    GitHub
    download it here
    {
      "player_id": "2D2R-OP3C",
      "name": "Beebee-Ate",
      "level": 42,
      "attributes": {
        "devtodev_apikey": "ak-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
        "devtodev_device_id": {
          "devtodevId": 123456,
          "userId": "2D2R-OP3C",
          "advertisingId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
        },
      }
    }
    <script type="text/javascript" src="https://cdn.devtodev.com/sdk/web/v1/devtodevsdk.js">
    </script>
    /**
    * @param {string} apiKey - devtodev API key, unique API key can be found in the application
    * settings ("Settings" → "SDK" → "Integration")
    * @param {string} userId - Unique user identifier.
    * For example, user’s ID in a social network, or a unique account name used
    * for user identification on your server.
    * @param {string} previousUserId - Previous unique user identifier. Optional.
    * It is used in case of change of the user identifier.
    */
    
    devtodev.init(apiKey, userId, previousUserId);
    /**
    * Initializes the user with the specified cross-platform identifier
    * @param {string} сrossplatformUserId - unique cross-platform user ID used
    * for user identification on your server.
    */
    
    devtodev.setCrossplatformUserId(сrossplatformUserId);
    /**
    * Initializes the current user level. Required if level feature used in the app.
    * @param {number} currentUserLevel- Сurrent game level of the player.
    */
    
    devtodev.setCurrentLevel(currentUserLevel);
    /**
    * @param {Object} appData - App data object.
    * @param {string} appData.appVersion - Current app version. Required.
    * @param {number} appData.codeVersion - Current code version. Optional.
    */
    
    devtodev.setAppData(appData);
    /**
    * Activates console log
    * @param {boolean} status
    */
    
    devtodev.setDebugLog(status);
    <service
    android:name="com.devtodev.push.logic.DTDFcmMessagingService"
    android:enabled="false">
    </service>
    dependencies {
        implementation 'com.devtodev:android:1.14.10'
        implementation 'com.android.installreferrer:installreferrer:2.2'
        implementation 'com.google.android.gms:play-services-base:17.6.0'
        implementation 'com.google.firebase:firebase-core:19.0.0'
        implementation 'androidx.preference:preference:1.1.1' //or higher, required for SDK version 1.14.8 and higher
    }
    public class MyActivity extends Activity {
          @Override
          public void onCreate(Bundle savedInstanceState) {
              super.onCreate(savedInstanceState);
              // Initialization devtodev SDK
              DevToDev.init(this, APP_ID, SECRET_KEY);
          }
    }
    -keep class com.devtodev.** { *; }
    -dontwarn com.devtodev.**
    /**
    * Method allows to initialize the user. It applies when SDK initialization or user relogin.
    * @param String activeUserId - unique cross-platform user identifier (max. 64 symbols)
    */
    DevToDev.setUserId(activeUserId);
    
    /**
    * Method sets the current user level. Using this method allows to actualize the SDK user data
    * in game cross-platform applications.
    * @param int level - number of current game level of the user
    */
    DevToDev.setCurrentLevel(currentLevel);
    
    /**
    * devtodev SDK initialization
    * @param String appId - devtodev App Id
    * @param String secretKey - Secret key
    * devtodev App Id and Secret key can be found in the devtodev application
    * settings page ("Settings" → "SDK" → "Integration")
    */
    DevToDev.init(getBaseContext(), appId, secretKey);
    /**
    * @param logLevel
    */
    DevToDev.setLogLevel(LogLevel logLevel);

    The level of logging the SDK activity. The DTDLogLevel.No value is used by default. For troubleshooting during integration, it is recommended to set it to DTDLogLevel.Debug, and either switch it off DTDLogLevel.No or use it only for error handling DTDLogLevel.Error in the release version.

    ApplicationVersion

    String

    The app version during the devtodev SDK initialization. It is recommended that you set the app version before the initialization to make the collection of app version statistics more precise.

    DTDLogLevel (enum)

    The level of logging the SDK activity. The DTDLogLevel.no value is used by default. For troubleshooting during integration it is recommended to set it to DTDLogLevel.Debug, and either switch it off DTDLogLevel.No. Use DTDLogLevel.No in the release version.

    Parameter

    Type

    Description

    currentLevel

    Integer

    The player level at the moment of devtodev SDK initialization. It’s optional but we recommend using it for improving data accuracy.

    userId

    String

    A custom user ID assigned by the developer. In the case of default calculation by device IDs, the identifier can be used for searching users in devtodev. In case the project uses calculation by user IDs, the parameter is mandatory because it becomes the principal calculation ID in devtodev.

    trackingAvailability

    DTDTrackingStatus (enum)

    The property allows or disallows devtodev tracking of the user. By default, it is set to DTDTrackingStatus.Enable. SDK stores the previously assigned value. Pass DTDTrackingStatus.Disable if the user opted out of tracking in line with GDPR.

    logLevel

    DTDLogLevel (enum)

    Bundle identifier of the application (App Bundle ID)

    refunds step 2 ios
    refunds step 3 ios

    The level of logging the SDK activity. The DTDLogLevel.no value is used by default. For troubleshooting during integration it is recommended to set it to DTDLogLevel.debug, and either switch it off DTDLogLevel.no. Use DTDLogLevel.no in the release version.

    Remove AdSupport.framework all the links pointing to it.

    Parameter

    Type

    Description

    currentLevel

    int

    The player level at the moment of devtodev SDK initialization. It is recommended (but optional) to use to improve data precision.

    userId

    string

    A custom user identifier provided by the developer. If you utilize the default calculation by the device ID, this identifier can be used for finding a user in devtodev.

    In case your project utilizes the calculation by the user identifier, you must set this parameter because it becomes the main user identifier in devtodev.

    trackingAvailability

    DTDTrackingStatus (enum)

    The property allows or disallows devtodev tracking of the user. By default, it is set to DTDTrackingStatus.enable. SDK stores the previously assigned value. Pass DTDTrackingStatus.disable if the user opted out of tracking in line with GDPR.

    logLevel

    Children’s Online Privacy Protection Act (COPPA)
    General Data Protection Regulation (GDPR) Article 8
    Apple’s guidelines

    DTDLogLevel (enum)

    For the correct SDK functioning add the following frameworks:

    • Security.framework (Optional)

    • UIKit.framework (Optional)

    • UserNotifications (Optional)

    • StoreKit.framework

    • AdSupport.framework

  • Add init method into didFinishLaunchingWithOptions method of your AppDelegate.m

  • If the application you integrate SDK in is a part of a cross-platform project, then the user data initialization is required. Since the analytics of cross-platform projects is based on an unique user (unlike the usual projects where it is based on device identifiers), you have to:

    • Set the unique cross-platform user identifier (it will be used for cross-platform project data collection).

    • Actualize the user data. Mostly it is about game applications where the player has a game level as a characteristic. For such projects, you need to set the current player level.

  • download the latest version of devtodev SDK
    CocoaPods
    GitHub

    Submit the data to the application settings in devtodev system

  • Integrate devtodev SDK to the application (see the "SDK integration" division to learn more about integrating and initializing devtodev SDK)

  • Set Push Notification Enabled in blueprint.

  • Create a campaign for sending push notifications in "Push" section

  • Schedule the delivery

  • That's it!

  • 1. NuGet Installation

    Package Manager UI

    Find the DevToDev.Messaging package using the package manager search engine and click Install. The latest version of the package is recommended.

    2. Credentials

    To integrate WNS, you need to get the Package SID and Application Secret Key from the Microsoft Partner Center and specify them in the devtodev push notification settings (Project -> Settings-> Push notifications):

    3. Setup

    For the correct package functioning, add handlers invoke to the Windows.UI.Xaml.Application class implementation.

    Besides, in the UI editor of the Package.appxmanifest file do the following:

    1. Add Background Tasks to the Declarations tab and mark it as System Event. After that enter DevToDev.Background.ToastNotificationBackgroundTask to the Entry Point field.

    2. Add Background Tasks to the Declarations tab and mark it as Push Notification. After that enter DevToDev.Background.RawNotificationBackgroundTask to the Entry Point field.

    4. Initialization

    For the DevToDev.Messaging package functioning you need to have the main DevToDev.Analytics package installed. Initialize the SDK before initializing messages. You can read about it in more detail in the SDK initialization section.

    After the SDK has been initialized you can move to initializing messages. To do this, call the method:

    5. Events

    It is possible to listen to events from the DevToDev.Messaging package:

    1. Push Token - a string that allows to identify the client on a remote server for sending him customized notifications. For listening the unique Push Token ID issue event, it is necessary to be subscribed to the event:

    2. To track the errors related to the unique Push Token ID issue, it is necessary to be subscribed to the event:

    Where error is a string value containing information about the error.

    3. To handle incoming message data, it is necessary to be subscribed to the following event:

    Where messageData belongs to the IDictionary<string, string> type and contains data sent from the server together with the message.

    4. To handle notification activation events, it is necessary to be subscribed to the event:

    Where messageAction is DevToDev.Messaging.DTDMessageAction class instance:

    6. Disabling

    Call the method to turn notifications off:

    User card
    users table

    Godot Engine

    Godot SDK integration

    The DevToDev SDK extends the Godot engine in a modular way and supports the following platforms: MacOS, iOS, Android.

    Requirements

    • Godot engine 4.0+ ()

    • .

    • build system.

    • SDK module source code ()

    The SDK module installation

    The SDK module is available in . Download the Source code of latest release and copy d2d_analytics folder to the modules(/godot/modules/) folder of Godot engine source code.

    To work in the Godot editor, compile the engine source code and the analytics module:

    SDK Initialization

    For initialization, add the following code at the start of your application:

    You can find the AppID in the settings of the respective app in devtodev (Settings → SDK → Integration → ).

    config – an object instance of GDDTDAnalyticsConfiguration, which is used for specifying additional properties during the initialization

    Example:

    Project export

    Export for MacOS

    Open the Export Template Manager to download and install templates:

    Open a terminal, go to the root directory of the engine source code. Compile a custom template for MacOS (see for MacOS), select Debug or Release build and processor architecture. To support both architectures in a single Universal 2 binary, use lipo:

    The next step is to prepare a custom template as a macos.zip archive. Don't forget to add the DTDAnalytics native library, copy libDTDAnalytics.dylib to macos_template.app/Contents/MacOS/.

    Open the Export menu and specify the path to the prepared custom template:

    Export for iOS

    Open the Export Template Manager to download and install templates:

    Open a terminal, go to the root directory of the engine source code. And compile a custom template for iOS (see for iOS). To work with the iOS simulator, compile the sources with the ios_simulator=yes flag. To support both architectures in a single Universal 2 binary, use lipo:

    The next step is to prepare a custom template as an ios.zip archive. Don't forget to add the DTDAnalytics native library, copy DTDAnalytics.xcframework to ios_xcode/.

    In XCode project:

    1. Add DTDAnalytics.xcframework to the project (with Do Not Embed specified)

    2. Create Bridging-Header. To do this, add any swift file to the project (don't delete it later) and select 'Create Bridging Header' in the dialogue box that appears.

    3. Add frameworks:

    Export for Android

    Click to install android templates:

    After installing the template, an android folder will appear in your project.

    Move the d2d_analytics/native/androidAnalytics.aar to the android/plugins/ folder in your project. You will also need to create an Analytics.gdap file in android/plugins/ with the following content:

    Next, Analytics should appear in the Plugins section, check it out:

    The next step is to compile the Godot engine for Android (see ), choose debug or release build, and select the processor architecture.

    After successful compilation execute the following commands:

    In the next step in godot-4.0-stable/bin you will see godot-lib.template_debug.aar or

    godot-lib.template_relaese.aar.

    You need to copy and replace this file to the previously installed template in your project at the path:

    • appName/android/build/libs/debug - for debugging

    • appName/android/build/libs/release - for release

    After these steps, you are ready to export your Android app.

    Make sure that Use Gradle Build (in the Gradle Build section), Analytics (in the Plugins section) and the previously compiled Architecture (in the Architectures section) are selected.

    Adobe Air

    This generation of SDK is deprecated and is no longer supported.

    Please do the following to integrate your application with devtodev:

    1. Add the application to the Space using the wizard for adding application. Attention! If your Adobe Air can be used for compilations for different platforms, you need to add the applications in devtodev for each platform. As a result, the statistics will be gained for each platform separately.

    2. Add com.devtodev.sdk.ane library to your application

    3. For Android add the following permissions to the MyApplication.xml file:

    To automatically gather the referrals data on Android, add the following strings into tag

    For other platforms no changes are needed.

    5. Add the following imports to your source

    6. Add following source into initialize event in MyApplication.mxml file:

    The appKey and appSecret values are unique for each app on each platform and can be found in the settings of appropriate app ("Settings" → "SDK" → "Integration").

    For example: file MyApplication.mxml:

    file MyApplication.xml:

    Additional initialization

    If the application you integrate SDK in is a part of cross-platform project, then the user data initialization is required.

    Since the analytics of cross-platform projects is based on a unique user (unlike the usual projects where it is based on device identifiers), you have to:

    • Set the unique cross-platform user identifier (it will be used for cross-platform project data collection).

    • Actualize the user data. Mostly it is about game applications where the player has a game level as a characteristic. For such projects, you need to set the current player level.

    We recommend you set the user identifier before SDK initialization, otherwise, the user identifier from the previous session will be used since the SDK initialization moment till the setUserId method call.

    If your cross-platform application is supposed to be used without cross-platform authorization, don't use the setUserId method or use the empty string ("") as the user identifier. SDK will assign the unique identifier to the user. This identifier will be used until the real cross-platform identifier assigns to the user.

    If your application allows user to re-login (changing the user during the working session of application), then the setUserID and setCurrentLevel methods should be called just after the authorization. You don't need to call the SDK initialization one more time.

    Debug mode

    To enable the debug mode and make SDK notifications displayed in the console, use this method:

    2023

    Here are some of the new features you might have missed.

    Add Retention report to custom dashboards

    Released: 20/12/2023

    Results from Devtodev’s Retention report can now be added to a custom dashboard as a widget. This information helps you track crucial retention metric values for your project. The update eliminates the need for SQL in building the report and adding it to the dashboard.

    Here are some examples of how you can use this functionality:

    var config = new DTDAnalyticsConfiguration();
    config.LogLevel = DTDLogLevel.Error;
    DTDAnalytics.Initialize("App ID", config);
    var config = new DevToDev.Analytics.DTDAnalyticsConfiguration();
    config.LogLevel = DTDLogLevel.Error;
    config.CurrentLevel = 2;
    config.UserId = "CustomUserId";
    config.TrackingAvailability = DTDTrackingStatus.Enable;
    config.ApplicationVersion = "1.2.34";
    DevToDev.Analytics.DTDAnalytics.Initialize("App ID", config);
    let config = DTDAnalyticsConfiguration()
    config.logLevel = .error
    DTDAnalytics.initialize(applicationKey: "App ID", configuration: config)
    DTDAnalyticsConfiguration *config;
    config.logLevel = DTDLogLevelError;
    [DTDAnalytics applicationKey:@"App ID" configuration:config];
    let config = DTDAnalyticsConfiguration()
    config.currentLevel = 1
    config.userId = "CustomUserID"
    config.trackingAvailability = .enable
    config.logLevel = .no
    DTDAnalytics.initialize(applicationKey: "App ID", configuration: config)
    DTDAnalyticsConfiguration *config;
    config.currentLevel = @1;
    config.userId = @"CustomUserID";
    config.trackingAvailability = DTDTrackingStatusEnable;
    config.logLevel = DTDLogLevelNo;
    [DTDAnalytics applicationKey:@"App ID" configuration:config];
    @main
    struct TestSwiftUIApp: App {
        init() {
            let config = DTDAnalyticsConfiguration()
            config.logLevel = .debug
            DTDAnalytics.initialize(applicationKey: "App ID", configuration: config)
        }
    
        var body: some Scene {
            WindowGroup {
                ContentView()
            }
        }
    }
    DTDAnalytics.coppaControlEnable()
    DTDAnalytics.initialize(applicationKey: "App ID", configuration: config)
    [DTDAnalytics coppaControlEnable];
    [DTDAnalytics applicationKey:@"App ID" configuration:config];
    /**
    * devtodev App Id and Secret key can be found in the devtodev application
    * settings page (“My apps” → App Name → “Settings” → “Integration”)
    */
    [DevToDev initWithKey:applicationId andSecretKey:secretKey];
    gem install cocoapods
    pod 'devtodev'
    pod install
    /**
    * Method allows to initialize the user. It applies when SDK initialization or user relogin.
    * @param NSString activeUserId - unique cross-platform user identifier (max. 64 symbols)
    */
    [DevToDev setUserId:@"activeUserId"];
    
    /**
    * Method sets the current user level. Using this method allows to actualize 
    * the SDK user data in game cross-platform applications.
    * @param NSUInteger level - number of current game level of the user
    */
    [DevToDev setCurrentLevel:level];
    
    /**
    * devtodev App Id and Secret key can be found in the devtodev application
    * settings page ("Settings" → "SDK" → "Integration")
    */
    [DevToDev initWithKey:applicationId andSecretKey:secretKey];
    /**
    * @param BOOL isActive
    */
    [DevToDev setActiveLog: (BOOL) isActive];
    sealed partial class App : Application
    {
      protected override void OnLaunched(LaunchActivatedEventArgs e)
      {
        // Your code...
        DTDMessaging.HandleActivatedEvent(e);
      }
      
      protected override void OnActivated(IActivatedEventArgs args)
      {
          // Your code...
          DTDMessaging.HandleActivatedEvent(args);
      }
    }
    DTDMessaging.SetMessagingEnabling(true);
    DTDMessaging.OnTokenReceived += token => { /* Your code... */ };
    DTDMessaging.OnTokenFailed += error => { /* Your code... */ };
    DTDMessaging.OnMessageReceived += messageData => { /* Your code... */ };
    DTDMessaging.OnMessageActivated += messageAction => { /* Your code with token. */ };
    /// <summary>
    /// The class contains information about notification's action.
    /// </summary>
    public sealed class DTDMessageAction
    {
        /// <summary>
        /// Action type.
        /// Can be: Open, Url, Share, DeepLink.
        /// </summary>
        public DTDMessageActionType ActionType { get; }
    
        /// <summary>
        /// Action string.
        /// </summary>
        public string ActionString { get; }
    
        /// <summary>
        /// Activated button ID.
        /// </summary>
        public string ButtonId { get; }
    
        /// <summary>
        /// Activated button's text.
        /// </summary>
        public string ButtonText { get; }
    
        /// <summary>
        /// Notification data.
        /// </summary>
        public IReadOnlyDictionary<string, string> MessageData { get; }
      }
    DTDMessaging.SetMessagingEnabling(false);
    Add a widget with data on the retention of users who signed up for a specific subscription plan.
  • Display the retention of users who signed up for your project and then performed a desired action (e.g., created a project, started a workout, or invited a friend).

  • Compare the retention of users who engaged with your new features with those who did not (e.g., completed/skipped onboarding).

  • Add several widgets to compare the retention of users who used different methods of doing something (e.g., project creation, battle modes, price plans, payment methods, etc.).

  • Learn more about Retention reports


    IS NOT operator and new parameter options in reports

    Released: 05/12/2023

    Devtodev’s Custom event, Custom funnels, and User flow reports got a new operator and parameter value that will increase their flexibility. Use the 'IS NOT' operator and 'null' parameter value to exclude certain parameters and to work with events that do not return any value.

    Check out this article for more details


    Manage User properties in one click

    Released: 21/11/2023

    You can disable any property that you have previously added and now consider unnecessary or faulty. For example, if you initially collected the 'user type' property (such as beginner or pro), but have since decided to focus on the 'user status' property (guest/registered), you can disable the 'user type' property to maintain a cleaner and more organized report-building process.

    Learn how to manage User Properties


    1x1 widget in Custom events

    Released: 20/11/2023

    With this update a familiar 1x1 widget from SQL and Basic Metrics becomes available in the Custom events report. Select Report type -> Number and a single number for your most important KPI is ready for your dashboard.

    Here are some examples of what you can check: number of in-app shop openings, average battle time in a game, conversion from workout start to finish.

    Learn more about Custom events report


    App type selection

    Released: 30/10/2023

    When creating a new devtodev project, you can now select the type of project: a game or an application. This choice will affect the customization of the devtodev interface. Depending on the type, specialized reports will be available.

    You can always change the type of the project later in Settings -> General settings.

    Learn more about adding new projects


    Convenient 1x1 widget from Basic Metrics report

    Released: 19/09/2023

    Devtodev users are familiar with the 1x1 widget — a small module on your dashboard containing a single number designed to keep you informed about important metrics. With the latest update from Devtodev, you no longer need to use SQL to create widgets with basic metrics. You can simply open the Basic Metrics report, select a metric, click View -> Number, and then add this number as a widget to your dashboard. This will not only save you valuable time but also enable you to use a limited number of SQL widgets to display other metrics.

    Check out this article for more details


    1x1 Widget upgrade

    Released: 29/08/2023

    When it comes to data visualization, Devtodev offers plenty of powerful tools, and SQL widgets is one of them. To give you access to even more data points on your dashboards, we’ve introduced an update that offers you the ability to display two metrics on a single 1x1 widget. Simply write a single query and get two numbers: it is easy, convenient and requires less time than creating separate queries for two individual widgets.

    Check out this article for more details


    Simplified Funnel views

    Released: 16/08/2023

    Devtodev’s clients frequently create funnels to analyze various aspects of their apps and games. However, these funnels are often longer than a single screen, and the users find themselves having to scroll excessively, which can be quite inconvenient.

    To alleviate this issue, we have recently introduced the 'Step' option to the Conversion funnel report. It is designed to save our clients time and effort while streamlining the funnel exploration process.

    Check out this article for more details


    Space Overview update

    Released: 25/07/2023

    The overview provides essential metrics for all projects in Space, allowing users to conventionally track the metrics of the projects. Additionally, it offers the option to access the prepared reports in the selected project to analyze metric rises or falls. The overview aims to briefly introduce each project's key metrics and keep users informed about devtodev's news.

    Learn more about Space overview


    Customize Tutorial steps

    Released: 18/07/2023

    This update empowers you to enhance collaboration among team members by giving each step in the Tutorial analysis report a meaningful and easy-to-understand name of your choice.

    By tapping on the step in the report, you can assign a name that describes the action and facilitate seamless collaboration among all team members working on the app.

    Check out this article for more details


    Basic metrics report: percentage share

    Released: 27/06/2023

    In our latest update we added distribution by parameter (Show % of total) across all the Basic metrics report tables. This feature proves particularly valuable when you need to determine the percentage of users with a specific attribute in relation to the total number of users in a particular group. It’s extremely convenient because you don’t need to waste time on creating separate SQL queries anymore!

    Check out this article for more details


    Enhanced Retention report

    Released: 20/06/2023

    devtodev’s Retention reports has got several new features that will enable you to narrow down the audiences and calculate this metric more effectively.

    With this update, we’ve introduced two additional options for two calculation methods (by calendar days and by 24-hour interval):

    • Flexible calculation start: you can now choose a specific event or several events that define a cohort for retention calculation, allowing for more tailored analysis..

    • Custom calculation end: you have the freedom to select a particular event that signifies a user’s return to the product, enabling more precise measurement.

    Check out this article for more details


    Updated Funnels: more events and Churned users segment

    Released: 25/05/2023

    This update got you two new features in the Funnel report (Reports -> Conversion funnel): an alternative event that you can use when building a funnel, and an option for creating a segment of users who failed to complete all the funnel steps.

    Check out this article for more details


    Updated Virtual Goods & Purchases report

    Released: 02/05/2023

    This time we added several great features to the report’s Virtual goods and purchases section. They allow for a much deeper analysis of in-game currency, purchases, and real money spends.

    What we did:

    • Added display of item groups. The groups may come in handy in case you have too many items.

    • Added breakdown by levels.

    • Introduced a number of purchases divided by the DAU metric value (“% of active”, as in the Custom events report).

    • Improved mean value. After the update, we calculate it as the number of items purchased at a level divided by the number of users.

    Check out this article for more details


    Data labels in reports

    Released: 17/04/2023

    You can now activate Data labels on charts in Basic Metrics, Custom events or SQL reports. Use them to evaluate the dynamics and values in one glimpse.

    Learn more about reports


    Export Dashboards to PDF

    Released: 04/04/2023

    Sharing dashboards has never been so easy and convenient! devtodev already has a wide range of tools that make your work more smooth and unhindered, however, the export to pdf tool takes the process to the next level.

    It’s an extremely convenient option for both senders and recipients. The senders can make sure that the recipients get only the data that they want to share with them in the exact form that they intended to show them. The recipients can get access to data without going through the whole process of signing up to the devtodev platform.

    Check out this article for more details


    Funnels: limit by session, rename steps

    Released: 07/03/2023

    Analyzing FTUE using devtodev funnels is as easy as it can be! To make the process more convenient, we are introducing ‘limitation by session’ — a new additional option that will help you with analyzing any particular user session. Now building a funnel is as easy as creating any devtodev out-of-the-box reports — simply click and analyze every aspect of it.

    If you are curious about the number of users who achieve the ultimate goal during the first or the Nth session (first two, three, etc.), you can easily calculate them, build a segment and then analyze thoroughly. Simply click on ‘Conversion time limit’, set a sequential number of the session and build a funnel.

    Now you can also rename funnel steps!

    Check out this article for more details


    SDK Update: full complience with COPPA

    Released: 16/02/2023

    If you have a child-directed app or game, you may worry that the data you send to devtodev is anonymised and protected enough. It’s not a problem anymore with our updated SDK because it operates in full compliance with COPPA — the Children's Online Privacy Protection Act.

    This updated version of devtodev SDK does not collect, process or store ad IDs of children. It simply creates anonymised user identification numbers that can be used by the platform for tracking underaged users without influencing the decisions they make.

    If you already use devtodev SDK, all you need to do is to enable the COPPA-compliance opinion before SDK initialization and delete the dependencies necessary for processing ad and vendor IDs. This will not interfere with your working process because almost all devtodev reports will stay available and you will be able to use them as you did before.

    Read more about it in our documentation: iOS, Android, and Unity


    New metric in A/B tests: CARPU

    Released: 14/02/2023

    We’ve added 1, 7, 14 and 30-day Cumulative ARPU to the A/B tests. You can use these metrics to improve the quality of the analysis and prove that the implemented changes did not influence other key metrics of the app.

    Learn more about A/B testing


    User Flow: more session analysis options

    Released: 17/01/2023

    Drill down into your user flow data by setting a limit on the number of user sessions! This option comes in handy in case you want to take a closer look at what your users managed to achieve during, let’s say, their first session, and compare it with your expectations.

    There are three options in this report - “No”, “Limit by first session”, and “Limit by number of sessions”. If you want to see events that users completed during their first session, choose the second option. If you want to see data for several sessions, choose the third option. In this case, the calculation is as follows: we take the specified number of sessions performed during the selected period, then we take the dates of the first and the last sessions, and after that we analyze the events performed during this period.

    Check out this article for more details

  • AppTrackingTransparency.framework

  • AdSupport.framework

  • Parameter

    Type

    Description

    CurrentLevel

    Integer

    The player level at the moment of devtodev SDK initialization. It’s optional but we recommend using it for improving data accuracy.

    UserId

    String

    A custom user ID assigned by the developer. In the case of default calculation by device IDs, the identifier can be used for searching users in devtodev. In case the project uses calculation by user IDs, the parameter is mandatory because it becomes the principal calculation ID in devtodev.

    TrackingAvailability

    GDDTDTrackingStatus (enum)

    The property allows or disallows devtodev tracking of the user. By default, it is set to GDDTDTrackingStatus.Enable. SDK stores the previously assigned value. Pass GDDTDTrackingStatus.Disable if the user opted out of tracking in line with GDPR.

    LogLevel

    GDDTDLogLevel (enum)

    The level of logging the SDK activity. The GDDTDLogLevel.No value is used by default. For troubleshooting during integration it is recommended to set it to GDDTDLogLevel.Debug, and either switch it off GDDTDLogLevel.No. Use GDDTDLogLevel.No in the release version.

    Source code
    Python 3.6+
    SCons 3.0+
    GitHub repository
    GitHub repository
    Find out more about compiling for MacOS
    Credentials
    Building export templates
    Compiling
    Android compilation
    Download the latest version of devtodev SDK from the GitHub repository.
    scons platform=macos arch=x86_64
    scons platform=macos arch=arm64
    lipo -create bin/godot.macos.editor.x86_64 bin/godot.macos.editor.arm64 -output bin/godot.macos.editor.universal
    
    cp -r misc/dist/macos_tools.app ./Godot.app
    mkdir -p Godot.app/Contents/MacOS
    cp bin/godot.macos.editor.universal Godot.app/Contents/MacOS/Godot
    cp modules/d2d_analytics/native/macos/libDTDAnalytics.dylib Godot.app/Contents/MacOS/
    chmod +x Godot.app/Contents/MacOS/Godot
    codesign --force --timestamp --options=runtime --entitlements misc/dist/macos/editor.entitlements -s - Godot.app
    var config = GDDTDAnalyticsConfiguration.new()
    config.logLevel = GDDTDLogLevel.Debug
    DTDAnalytics.InitializeWithConfig("AppID", config)
    var config = GDDTDAnalyticsConfiguration.new() 
    config.logLevel = GDDTDLogLevel.No 
    config.trackingStatus = GDDTDTrackingStatus.Enable 
    config.currentLevel = 1 
    config.userId = "unique_userId" 
    DTDAnalytics.InitializeWithConfig("AppID", config)
    scons platform=macos target=template_debug arch=x86_64
    scons platform=macos target=template_debug arch=arm64
    lipo -create bin/godot.macos.template_debug.x86_64 bin/godot.macos.template_debug.arm64 -output bin/godot.macos.template_debug.universal
    cp -r misc/dist/macos_template.app .
    mkdir -p macos_template.app/Contents/MacOS
    cp bin/godot.macos.template_debug.universal macos_template.app/Contents/MacOS/godot_macos_debug.universal
    cp modules/d2d_analytics/native/macos/libDTDAnalytics.dylib macos_template.app/Contents/MacOS/
    chmod +x macos_template.app/Contents/MacOS/godot_*
    chmod +x macos_template.app/Contents/MacOS/libDTDAnalytics.dylib
    zip -q -9 -r macos.zip macos_template.app
    scons p=ios target=template_debug
    scons p=ios target=template_release
    scons p=ios target=template_debug ios_simulator=yes arch=x86_64
    scons p=ios target=template_debug ios_simulator=yes arch=arm64
    
    cp -r misc/dist/ios_xcode .
    cp bin/libgodot.ios.template_debug.arm64.a ios_xcode/libgodot.ios.debug.xcframework/ios-arm64/libgodot.a
    lipo -create bin/libgodot.ios.template_debug.arm64.simulator.a bin/libgodot.ios.template_debug.x86_64.simulator.a -output ios_xcode/libgodot.ios.debug.xcframework/ios-arm64_x86_64-simulator/libgodot.a
    
    cp bin/libgodot.ios.template_release.arm64.a ios_xcode/libgodot.ios.release.xcframework/ios-arm64/libgodot.a
    lipo -create bin/libgodot.ios.template_debug.arm64.simulator.a bin/libgodot.ios.template_debug.x86_64.simulator.a -output ios_xcode/libgodot.ios.release.xcframework/ios-arm64_x86_64-simulator/libgodot.a
    cp -r modules/d2d_analytics/native/ios/DTDAnalytics.xcframework ios_xcode/
    cd "ios_xcode"
    zip -q -r ios.zip ./*
    cd ..
    cp "ios_xcode/ios.zip" ./
    [config]
    
    name="Analytics"
    binary_type="local"
    binary="Analytics.aar"
    
    [dependencies]
    
    remote=[
        "com.google.code.gson:gson:2.8.9", 
        "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.2", 
        "com.google.android.gms:play-services-ads-identifier:18.0.1",
        "com.devtodev:android-google:1.0.0"
        // Optional (recommended)
        "com.android.installreferrer:installreferrer:2.2"
        ]
    
    custom_maven_repos=["https://repo.maven.apache.org/maven2/"]
    // Example. For debug on arm64v8 architecture use:
    scons platform=android target=template_debug arch=arm64v8
    //for release:
    scons platform=android target=template_release arch=arm64v8
    // Example. For debug on arm64v8 architecture use:
    scons platform=android target=template_debug arch=arm64v8
    //for release:
    scons platform=android target=template_release arch=arm64v8
      <uses-permission android:name="android.permission.INTERNET"/>
      <!-- Necessary(Required for sending analytics data to our server) -->
      <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
      <!-- Additional (Required for devices' MAC addresses collection) -->
      <uses-permission android:name="android.permission.READ_PHONE_STATE" />
      <!-- Additional (Required for cellular operator data collection) -->
      <receiver android:name="com.devtodev.InstallReceiver" android:enabled="true" android:exported="true">
      <intent-filter>
           <action android:name="com.android.vending.INSTALL_REFERRER" />
      </intent-filter>
      </receiver>
      import com.devtodev.sdk.core.DevToDev;
      import com.devtodev.sdk.core.data.consts.AccrualType;
      import com.devtodev.sdk.core.data.consts.Gender;
      import com.devtodev.sdk.core.data.consts.SocialNetwork;
      import com.devtodev.sdk.core.data.consts.TutorialState;
      import com.devtodev.sdk.cheat.data.consts.VerifyStatus;
      import com.devtodev.sdk.cheat.data.consts.TimeStatus;
      import com.devtodev.sdk.core.data.metrics.aggregated.events.CustomEventParams;
      DevToDev.init(AppId:String, SecretKey:String);
    <?xml version="1.0" encoding="utf-8"?>
    <s:Application initialize="application1_activateHandler(event)" 
                   deactivate="application1_deactivateHandler(event)" 
                   xmlns:fx="http://ns.adobe.com/mxml/2009" 
                   xmlns:s="library://ns.adobe.com/flex/spark" applicationDPI="160">
        <fx:Script>
            <![CDATA[
                     import com.devtodev.sdk.core.DevToDev;
                     import com.devtodev.sdk.core.data.consts.AccrualType;
                     import com.devtodev.sdk.core.data.consts.Gender;
                     import com.devtodev.sdk.core.data.consts.SocialNetwork;
                     import com.devtodev.sdk.core.data.consts.TutorialState;
                     import com.devtodev.sdk.cheat.data.consts.VerifyStatus;
                     import com.devtodev.sdk.cheat.data.consts.TimeStatus;
                     import com.devtodev.sdk.core.data.metrics.aggregated.events.CustomEventParams;
    
                     protected function application1_activateHandler(event:Event):void {
                          DevToDev.init(AppId, SecretKey);
                          DevToDev.startSession();
                     }
    
                     protected function application1_deactivateHandler(event:Event):void {
                          DevToDev.endSession();        
                     }
            ]]>
        </fx:Script>
    
        <fx:Declarations>
        </fx:Declarations>
    
        <s:VGroup>
        </s:VGroup>
    
    </s:Application>
    <?xml version="1.0" encoding="utf-8" standalone="no"?>
    <application xmlns="http://ns.adobe.com/air/application/17.0">
       <id>com.my.application</id>
       <filename>myapplication</filename>
       <name>myapplication</name>
       <versionNumber>1.0.0</versionNumber>
       <initialWindow>
          <autoOrients>true</autoOrients>
          <fullScreen>false</fullScreen>
          <visible>true</visible>
          <softKeyboardBehavior>none</softKeyboardBehavior>
       </initialWindow>
       <android>
          <colorDepth>16bit</colorDepth>
          <manifestAdditions><![CDATA[
             <manifest android:installLocation="auto">
                <uses-permission android:name="android.permission.INTERNET"/>
                <uses-permission android:name="android.permission.WAKE_LOCK"/>
                <uses-permission android:name="android.permission.READ_PHONE_STATE"/>
                <application>
                     <receiver android:name="com.devtodev.InstallReceiver" android:enabled="true"
                     android:exported="true">
                       <intent-filter>
                            <action android:name="com.android.vending.INSTALL_REFERRER" />
                       </intent-filter>
                     </receiver>
                </application>
             </manifest>
          ]]></manifestAdditions>
       </android>
       <iPhone>
          <InfoAdditions><![CDATA[
             <key>UIDeviceFamily</key>
             <array>
                <string>1</string>
                <string>2</string>
             </array>
          ]]></InfoAdditions>
          <requestedDisplayResolution>high</requestedDisplayResolution>
       </iPhone>
    
       <extensions>
          <extensionID>com.devtodev.SDK</extensionID>
       </extensions>
    </application>
    /**
    * Method allows to initialize the user. It applies when SDK initialization or user relogin.
    * @param activeUserId - unique cross-platform user identifier (max. 64 symbols)
    */
    DevToDev.setUserId(activeUserId:String);
    
    /**
    * Method sets the current user level. Using this method allows to actualize the SDK user data
    * in game cross-platform applications.
    * @param level - number of current game level of the user
    */
    DevToDev.setCurrentLevel(level:int);
    
    /**
    * devtodev App Id and Secret key can be found in the devtodev application
    * settings page ("Settings" → "SDK" → "Integration")
    * @param appKey - application key
    * @param appSecret - application secret
    */
    DevToDev.init(appKey:String, appSecret:String);
    /**
    * @param logLevel (set logLevel=1 to enable log, 0 to disable)
    */
    DevToDev.setLogLevel(logLevel:int);
    DevToDev.Analytics.Uwp 2.4.0nuget

    Basic Events & Custom Events

    To begin working with analytics, you need to start sending events (information about user activity in the project) to the analytics system.

    devtodev provides basic events that drive the majority of the reports.

    Sessions

    Users start with opening the app, and this process we call "starting a session". For your convenience, in the majority of devtodev SDKs this event is integrated automatically.

    There is no separate event in devtodev that describes a session by its duration and start or end date.

    We use two events that we can use to examine the duration of the sessions. The first event captures the start date of a new session and the second captures the duration of each application activity (time when application is in focus). In our experience, this is the most reliable solution at the moment.

    At the moment when application receives focus (this may be the moment the SDK is initialized / the application is opened / the device wakes up from the sleep mode with the focus on the application), we begin to assume that the application becomes active and begin to count the duration of the activity.

    If the application loses focus (minimizing the application / device is going into the sleep mode / switching to another application / exiting the application), we consider that the activity is completed and we send its duration to the devtodev server. This is an application activity event.

    We consider the start of a new session to be the moment when the application receives focus (see the description above), but at the same time we take into account that more than 10 minutes have passed since the last activity of the application. Otherwise, we count that the previously launched session continues. More detailed information on tracking sessions can be found on the page.

    To calculate the average session length per day, the sum of all durations of application activity is divided by the number of session starts.

    Reports based on a Session event :

    Real Payment

    If users can use real money to make purchases in your project, you need to integrate the Payment event. When devtodev receives data about sessions and payments in your app, we can calculate all financial metrics (gross, revenue, average check) and metrics of the project’s financial efficiency (ARPU, ARPPU, paying share, LTV).

    Integration of data about sessions and payments takes up to 20% of the time spent on integration, but in the future it will give answers to 80% of questions about user behavior in the project.

    Reports based on a Payment event :

    Subscriptions

    If your application has subscriptions, you can integrate this event to analyze the financial data as well as the structure of your subscribers. Devtodev integrations allow you to receive information about subscription purchases even if the user doesn’t open the app (auto-renewable subscriptions).

    Reports based on Subscription events:

    Onboarding (tutorial)

    Tutorial is a very important part of any project because the first session lays the foundation for future retention and monetization indicators.

    Using this event you can evaluate the effectiveness of the tutorial steps system, analyze how users complete the tutorial, find bottlenecks, and measure the time it takes for users to complete the tutorial. The event should be sent at the end of each tutorial step indicating the number of completed steps as a parameter.

    Here are some predefined values for the Tutorial Step event parameter:

    • 0 - the user skipped the tutorial

    • -1 - the user started the tutorial

    • -2 - the user finished the tutorial

    Reports based on a Tutorial Step event:

    Custom Events

    devtodev is a universal analytics system, and our structure of basic events is designed for game projects specifically. However, there may be situations when your project requires events that are not provided by the devtodev basic events. Such events can still be sent and analyzed in our system - as custom events.

    Each custom event can have up to 20 parameters (see ) to identify more detailed info about users' behavior.

    Here are some examples of users' actions that can be sent as custom events: when users open an in-game store, click on items, buy items. Based on these events, you can build a funnel and see the conversion on each step.

    Some limits for custom events:

    1. The number of different event types sent from one project shouldn't exceed 300 (see ).

    2. The event name must not exceed 72 characters.

    3. One event can contain up to 20 parameters, which must have unique names of up to 32 symbols.

    4. Parameters can be string and numeric: - the maximum length of parameter values is 255 characters; - the number of string parameter values cannot exceed 50 000; when it exceeds 50 000, the further sending of information about the parameter and possibilities to work with it will be blocked.

    Here is some expert advice to avoid problems with limits in custom events:

    • There is no need to send user IDs in parameters (they are collected by default).

    • Do not send time in the timestamp format (it is also collected by default).

    Reports based on Custom events:

    LevelUp

    This event is for games only. It is worthwhile to integrate this event into a game type project, as specified in the . In projects with the “app” type, game events will not be tracked and displayed in the interface, even if they are integrated. You can verify and change the project type in Settings → .

    You can analyze the distribution of the players over the levels. Many game projects have levels, which means that as users become more experienced, they gradually increase their level. In this case levels have a linear structure: the level N is followed by the level N+1. If your project has in-game currency, using the LevelUp event you can send information about the current amount of in-game currency players have. This data allows evaluating the average amount of in-game currency that players have on a particular level.

    The event should be sent right after the player reaches the next level.

    Reports based on a LevelUp event:

    CurrencyAccrual

    This event is for games only. It is worthwhile to integrate this event into a game type project, as specified in the . In projects with the “app” type, game events will not be tracked and displayed in the interface, even if they are integrated. You can verify and change the project type in Settings → .

    This specific event is a part of the LevelUp event and does not require a separate dispatch. Send this event to track the average amount of in-game currency earned or purchased during a level after each time an in-game account is replenished.

    Reports based on a Currency Accrual event:

    Progression Event

    This event is for games only. It is worthwhile to integrate this event into a game type project, as specified in the . In projects with the “app” type, game events will not be tracked and displayed in the interface, even if they are integrated. You can verify and change the project type in Settings → .

    There are many projects (for example, Match-3 games), where players make an attempt to complete a level. Their attempts may be either successful or unsuccessful. In addition, during a certain attempt different numerical indicators can be changed: the number of stars, resources, in-game currency.

    To analyze such attempts, devtodev provides a basic Progression event. In the Progression event you send information about how players pass a particular game location, whether their attempt is successful, and how numerical indicators change.

    The sequence in which locations are passed is not important in this case: after the location N players can go to any location M.

    Report based on a Progression event:

    Virtual Currency Payment

    This event is for games only. It is worthwhile to integrate this event into a game type project, as specified in the . In projects with the “app” type, game events will not be tracked and displayed in the interface, even if they are integrated. You can verify and change the project type in Settings → .

    Many games, especially f2p, have in-game currency. Players can spend it on virtual goods. To work with virtual currency purchases, devtodev has developed the Virtual Currency Payment event.

    Reports based on a Virtual Currency Payment events:

    Windows (UWP/WSA)

    WSA Integration

    Module initialization

    1. For the Messaging module to function you need the basic Analytics package. Before the notification initialization, you need to initialize the SDK. More about it you can read here: Unity Integration.

    2. Add the DTDAnalytics initialization block after the DTDMessaging initialization block.

    Attention! Use the #if UNITY_WSA define to surround any notification module code on the WSA platform.

    Optional

    Module status check

    Use the following method to check current status:

    The current module status will be sent to onGetMessagingEnabling callback.

    Listening to events

    You can listen to basic events of the Messaging module: create the class that implements the IDTDPushListener interface and send it to the DTDMessaging.WSA.SetPushListener method.

    Class example:

    A complete example of notification module initialization:

    External interface of the DTDMessaging module

    Method
    Description

    Build a Windows Store App in Unity

    Build a Windows Store App in Unity. After the app is built, a Visual Studio project will be created. Proceed with the following changes.

    There is a difference in the implementation of the elements mentioned below for different types of projects:

    IL2CPP + XAML

    Put the following source in your App class (usually it is App.xaml.cpp file). Add several lines of code in a generated App.xaml.cpp class. After defining headers:

    And at the end of of the App::OnLaunched(LaunchActivatedEventArgs^ e) and App::OnActivated(IActivatedEventArgs^ args) functions.

    For Example:

    IL2CPP + D3D

    Put the following source in your App class (usually it is App.cpp file). Add several lines of code in a generated App.cpp class. After defining headers:

    And at the end of of the App::OnActivated(CoreApplicationView^ sender, IActivatedEventArgs^ args) function.

    Besides, in the UI editor of the Package.appxmanifest file you need to do the following:

    1. Add Background Tasks in the Declarations tab and mark it as System Event. After that, add DevToDev.Background.ToastNotificationBackgroundTask to the Entry Point field.

    2. Add Background Tasks in the Declarations tab and mark it as Push Notification. After that, add DevToDev.Background.RawNotificationBackgroundTask to the

    Disabling

    To disable notifications, call the following method:

    Working with remote configuration in devtodev

    Contact us to request access. Use form or reach out to our Customer Success team directly within the platform. We will also greatly appreciate your feedback. Please add REMOTE CONFIGS when submitting your request.

    Remote Configuration (Remote config or RC) allows you to customize the app by sending key-value pairs directly to the user’s device. These configs are defined in the devtodev interface and sent to the SDK immediately or at a scheduled time. Remote configs are stored on the devtodev servers. This allows to trigger changes in the app, without updating app versions or changing app code every time.

    Preparation

    Before using a remote configuration in devtodev interface, you need to set variables through the SDK and use the methods to apply new parameters. If the app is offline and will not be able to present the config to the user, they will see default values and the app will continue to function correctly.

    Integrate remote configs with devtodev SDK:

    Remote configuration SDK integration

    How to create a remote configuration

    In order to create and send a remote configuration to user devices, you need to set parameter values and define conditions to select the audience.

    1

    Define the audience and schedule changes

    2

    Conditions

    Conditions define the user group (similar to a segment) that will see the modified interface. You can use basic and custom user properties, as well as previously created segments, to filter the audience.

    The order of conditions determines their evaluation sequence. The parameter value is assigned based on the first condition that evaluates to true. The condition at the top of the list has the highest priority.

    The order is important since several conditions may apply to one user. For example, Parameter A is sent to users from the US that have finished 5 levels. Parameter B is for users that have completed the onboarding (onboarding is obligatory). In this case, both parameters can apply to the US, 5+ levels group of users. You can change the order of the Conditions to define the parameter configuration these users will see.

    You can filter the conditions by selecting Filter in the top right corner.

    Create new condition

    Click +Condition to add a new condition.

    1. Configure your condition settings:

    • Condition name

    • Color – select a color tag. You can use the color tags to organize and filter your conditions.

    • Description – add details about this condition and audience.

    1. Next, define the Audience conditions with user properties.

    1. Select when the new parameter value will apply to the condition audience:

    • Permanent – the changes will apply instantly.

    • Scheduled – the changed parameter value will apply only during the set time frame.

    Click Finish to complete condition creation.

    You can change the condition settings later using the Edit button (pencil icon) or simply by clicking on the condition card.

    Parameters

    Parameters represent changes in the app’s UI/UX, a configuration of the interface visible to selected users. They should be pre-implemented in the app and linked to a variable. Parameter value is stored in a key-value format, for example, “button_color” = “green“.

    The Parameters section shows detailed information about all of your created parameters:

    • Parameter – name, value type and description.

    • Condition – to what audience does this parameter apply.

    • Value – shows parameter value for each condition.

    • Users – how many users have this configuration.

    You can filter the parameters by selecting Filter in the top roght corner.

    Create new parameter

    Click +Parameter to add a new parameter.

    Configure your parameter settings:

    • Parameter name (key) – the name should be unique and correspond to the variable in your app code.

    • Type – value type can be a Number, String, JSON or Boolean.

    • Description – what does this parameter change in the app.

    • Default value – set the default value for any condition.

    Next, configure Conditional values.

    1. Click +Condition and select one or multiple conditions from the list.

    2. Define a parameter value for each condition or use an in-app deafult.

    3. Click Finish to complete parameter creation.

    If you do not have any created yet, you can click Finish and add a condition later.

    Without conditions, parameter changes will be applied to all users.

    You can change the parameter later using the Edit button in three dots menu.

    Folders

    For convenience, you can organise parameters in folders. Click Add Folder, give it a name and description and click Create Folder.

    You can move parameters to different folders.

    • Click on three dots on the right and click Move to folder, select a destination folder and click Move.

    • You can also move parameters in bulk: select the necessary parameters and click Move to Folder button in the top right corner.

    To edit folder information, delete or ungroup a folder, select a corresponding option in the three dots menu.

    Changelog

    Each configuration of parameters and conditons is stored as a different version.

    You can compare the versions side by side and, if needed, Roll back to an old version of the configuration. Rolling back will create a new version of the config and send changes to user devices.

    Using remote configuration with A/B tests

    We will add an option to send a parameter configuration directly from the A/B test page in the future updates.

    For now, if you would like to check which parameter configuration performs better during an A/B test, you need to do the following:

    1. Before launching an A/B test, create a condition with the same audience that will be used in the test.

    2. Create a parameter configuration that you would like to test with this condition.

    3. Publish changes. The configuration will be sent to user devices.

    4. Launch the A/B test. The SDK will update the configuration on device and all events will be marked with an A/B test group.

    Limitations

    • For now, you cannot use Custom events to define a audience. However, you can create a with the necessary event and use this segment instead.

    • You cannot see the current configuration in the user card.

    • The stores around 200 configuration versions.

    App Store

    1

    SDK Integration

    2

    3

    SDK integration

    Prerequisites:

    Native SDK

    • StoreKit SDK v1

    • DTDAnalytics SDK 2.5.0 and higher

    To initialize the service, call the startAutoTracking method of the DTDPurchases interface. Call the method after the SDK initialization call:

    Subscription restore

    Subscription renewals, cancellations, and other status changes are tracked using server-to-server data from the App Store. See the following sections for setup.

    In order to be able to track status changes for subscriptions that were issued prior to the devtodev SDK integration, you must send the history of previously purchased user subscriptions to devtodev.

    The SDK keeps track of the need to send this historical data so that it does not make “unnecessary” requests to the App Store. Use the isRestoreTransactionHistoryRequired method to check whether it is necessary to send data about previously purchased subscriptions to devtodev.

    The isRestoreTransactionHistoryRequired method returns a BOOL value.

    Here is an example of a purchase history request with the check:

    DTDPurchases service will get the information about previously purchased subscriptions automatically from SKPaymentQueue. Sending the data via the method is not required.

    Settings on the App Store Connect side

    To get detailed information about the transaction, devtodev requires access to the App Store Server API. To grant this access, you will need to generate an In-App Purchase API key.

    Generating the key:

    1. Authorize on .

    2. Navigate to the Users and Access section.

    3. In the Integrations tab (1), select In-App Purchase from the menu on the left (2), and click (+) to add the key (3).

    Settings on devtodev side

    In-App Purchases

    1. Go to Settings → Payments integration → IAP auto tracking → Integrate.

    2. Fill in the integration form with the data :

      • App Bundle ID

    Subcriptions

    1. In order for IAP auto tracking to work correctly with subscriptions, you need to integrate App Store Server Notification. Go to Settings → Payments integration → Subscriptions → Integrate.

    2. Fill in the iOS Bundle ID and copy the Endpoint URL.

    3. Navigate to . In the left menu column, under General, go to App information

    DevToDev.Analytics.Uwp 2.4.0nuget

    Anti-cheat Methods

    This generation of SDK is deprecated and is no longer supported. Information about the .

    Validation of payments and time adjustments in devtodev SDK for iOS

    Churn Rate

  • App Version Analysis

  • RFM Analysis

  • Payments amounts

  • Transactions

  • Cumulative ARPU

  • RFM Analysis

  • Payments amounts

  • Transactions

  • Track Sessions
    Engagement Dashboard
    Sessions
    Retention
    Audience structure
    How to integrate Payment event
    Monetization Dashboard
    Gross Structure
    Conversion to Payments
    Cumulative ARPU
    How to integrate the Subscriptions event (SDK)
    How to set up Subscriptions integration with Store
    Monetization Dashboard
    Subscriptions
    Gross Structure
    Conversion to Payments
    How to integrate Tutorial Step event
    Tutorial Analysis
    Limits
    Limits
    How to integrate Custom events
    Conversion Funnels
    User Flow
    Custom Events
    application settings
    General settings
    How to integrate LevelUp event
    In-game Analysis Dashboard
    Economy Balance
    Virtual Goods & Purchases
    Game Structure (Player levels tab)
    application settings
    General settings
    How to integrate CurrencyAccrual event
    In-game Analysis Dashboard
    Economy Balance
    application settings
    General settings
    How to integrate Progression Event
    Game Structure (Locations tab)
    application settings
    General settings
    How to integrate Virtual Currency Payment Event
    In-game Analysis Dashboard
    Economy Balance
    Virtual Goods & Purchases
    Entry Point field
    .

    void DTDMessaging.WSA.SetMessagingEnabling(bool value)

    The method responsible for enabling or disabling of the push notification module

    void DTDMessaging.WSA.GetMessagingEnabling(Action<bool> onGetMessagingEnabling)

    The method that returns current state of the push notification module to onGetMessagingEnabling callback

    void DTDMessaging.WSA.SetPushListener (IDTDPushListener pushListener)

    It sets a listener for push notification event trapping

    Payments validation

    To be protected from fraudulent transactions, we recommend you to use devtodev Anticheat service.

    Use this method, and devtodev will check the transaction's validity with the payment platform, and the response will be returned to the application.

    The result can take one of the following values:

    In case of a successful check call the following main SDK method:

    If the transaction hasn’t passed verification, do not perform the Payment event.

    We do not recommend to use the result of devtodev anti-cheat verification as a condition for giving or not giving in-game currency or item purchased by a user!

    Time cheats check

    To check for time cheats call checkTime method every time when the app is being launched

    The result can take one of the following values:

    Validation of payments and time adjustments in devtodev SDK for Android

    Payments validation

    To be protected from fraudulent transactions, we recommend you to use devtodev Anticheat service

    Use this method, and devtodev will check the transaction validity with the payment platform, and the response will be returned to the application.

    Call following method when GooglePlay returns the transaction to your onActivityResult:

    You can get sharedSecret key here:

    1. Go to the Google Play Developer Console and sign in. Make sure that you sign in to the account from which the application you are licensing is published (or will be published).

    2. In the application details page, locate the Services & APIs link and click it.

    3. In the Services & APIs page, locate the Licensing & In-App Billing section.

    Your public key for licensing is given in the Your License Key For This Application field.

    The result can take one of the following values:

    In case of a successful check call following the main SDK method:

    If the transaction hasn’t passed verification, do not perform the Payment event.

    We do not recommend to use the result of devtodev anti-cheat verification as a condition for giving or not giving in-game currency or item purchased by a user!

    Time cheats check

    To check for time cheats call checkTime method every time when the app is being launched

    The result can take one of the following values:

    Validation of payments and time adjustments in devtodev SDK for Unity

    Payments validation

    To be protected from fraudulent transactions, we recommend you to use devtodev Anticheat service.

    Use this method, and devtodev will check the transaction validity with the payment platform, and the response will be returned to the application.

    1. Call the method for payment verification:

    or if you are using Unity IAP plugin:

    where OnReceiptVerifyCallback is the function like this:

    Here's how to find your application's public key for licensing (for Google Play platform only, for other platforms the publicKey is not used):

    1. Go to the Google Play Console and sign in. Make sure that you sign in to the account from which the application you are licensing is published (or will be published).

    2. In the application details page, locate the Services & APIs link and click it.

    3. In the Services & APIs page, locate the Licensing & In-App Billing section. Your public key for licensing is given in the Your License Key For This Application field.

    ReceiptVerificationStatus can take one of the following values:

    Don't forget that it is enough to set only receipt field to check the payment on iOS (iTunes) or Windows/Windows Phone (Microsoft Store), and for Android (Google Play) the fields signature and publicKey should be set.

    Сore SDK should be initialized prior to the call of VerifyPayment function.

    2. In case of an unsuccessful check (ReceiptNotValid result) do not call SDK method RealPayment. In other cases:

    Time cheats check

    To check for time cheats call VerifyTime method.

    1. Call the method to time verification:

    where OnTimeVerifyCallback is the function like this:

    DevToDevTimeVerificationStatus can take one of the following values:

    Сore SDK should be initialized prior to the call of VerifyTime function.

    current version can be found here
    #if UNITY_WSA
      DTDMessaging.WSA.SetMessagingEnabling(true);
    #endif#
    #if UNITY_WSA
      DTDMessaging.WSA.{AnyMethod}
    #endif#
    void GetMessagingEnabling(Action<bool> onGetMessagingEnabling)
    public class MyPushListener : IDTDPushListener
    {
        public void OnPushServiceRegistrationSuccessful(string deviceId)
        {
            Debug.Log(deviceId);
        }
    
        public void OnPushServiceRegistrationFailed(string error)
        {
            Debug.Log(error);
    
        }
    
        public void OnPushNotificationReceived(DTDPushMessage message)
        {
            Debug.Log(message);
        }
    
        public void OnInvisibleNotificationReceived(DTDPushMessage message)
        {
            //IOS only.
        }
    
        public void OnPushNotificationOpened(DTDPushMessage pushMessage, DTDActionButton actionButton)
        {
            Debug.Log(pushMessage.ToString());
            Debug.Log(actionButton.ToString());
        }
    }
    public class MyPushListener : IDTDPushListener
    {
        public void OnPushServiceRegistrationSuccessful(string deviceId)
        {
            Debug.Log(deviceId);
        }
    
        public void OnPushServiceRegistrationFailed(string error)
        {
            Debug.Log(error);
        }
    
        public void OnPushNotificationReceived(DTDPushMessage message)
        {
            Debug.Log(message);
        }
    
        public void OnInvisibleNotificationReceived(DTDPushMessage message)
        {
            Debug.Log(message);
        }
    
        public void OnPushNotificationOpened(DTDPushMessage pushMessage, DTDActionButton actionButton)
        {
            Debug.Log(pushMessage.ToString());
            Debug.Log(actionButton.ToString());
        }
    }
    
    public class NotificationExample : MonoBehaviour
    {
        private const string APP_KEY = "***************"
        void Start()
        {
            DTDAnalytics.Initialize(APP_KEY);
    #if UNITY_WSA
            DTDMessaging.WSA.SetPushListener(new PushListener());
            DTDMessaging.WSA.SetMessagingEnabling(true);
    #endif
        }
    }
    //...headers
    
    extern "C" __declspec(dllimport) void __stdcall AddActivatedEventArgs(IInspectable* activatedEventArgs);
    void App::OnActivated(IActivatedEventArgs^ args) 
    {
     //...other code
     AddActivatedEventArgs(reinterpret_cast<IInspectable*>(static_cast<Platform::Object^>(args)));
    }
    
    void App::OnLaunched(LaunchActivatedEventArgs^ e) 
    {
     auto args = static_cast<IActivatedEventArgs^>(e);
     AddActivatedEventArgs(reinterpret_cast<IInspectable*>(static_cast<Platform::Object^>(args)));
    }
    //...headers
    extern "C" __declspec(dllimport) void __stdcall AddActivatedEventArgs(IInspectable* activatedEventArgs);
    void App::OnActivated(CoreApplicationView^ sender, IActivatedEventArgs^ args) 
    {
     //...other code
     AddActivatedEventArgs(reinterpret_cast<IInspectable*>(static_cast<Platform::Object^>(args)));
    }
    DTDMessaging.WSA.SetMessagingEnabling(false);
    [DevToDevCheat verifyPaymentWithCompletion:(void (^)(ReceiptStatus))completionBlock];
    typedef enum {
        ReceiptValid,
        ReceiptNotValid,
        ReceiptServerError,
        ReceiptInternalError,
        ReceiptSandbox
    } ReceiptStatus;
    [DevToDev realPayment: (NSString *) transactionId withInAppPrice:(float) inAppPrice 
             andInAppName: (NSString *) inAppName andInAppCurrencyISOCode: (NSString *) inAppCurrencyISOCode];
    [DevToDevCheat checkTime: (void (^)(TimeStatus status)) completionBlock];
    typedef enum {
        Valid,
        Forward,
        Rewind
    } TimeStatus;
    DevToDevCheat.verifyPayment(String receipt, String signature, String publicKey, 
                                OnVerifyListener onVerifyListener);
    public enum VerifyStatus {	
                               Valid,
                               Invalid,
                               InternalError,
                               ServerError
                             };
    DevToDev.realPayment(String pPaymentId, float pInAppPrice, String pInAppName, String pInAppCurrencyISOCode);
    DevToDevCheat.verifyTime(OnTimeVerifyListener onTimeVerifyListener);
    public enum TimeStatus {
                             Valid,
                             Forward,
                             Rewind
                           };
    DevToDev.AntiCheat.VerifyReceipt(string receipt, string signature, string publicKey,
                                     OnReceiptVerifyCallback callback);
    DevToDev.AntiCheat.VerifyReceipt(string purchasedProduct, string publicKey, OnReceiptVerifyCallback callback)
    public void onReceiptVerifyCallback (DevToDev.ReceiptVerificationStatus status) {
      Debug.Log ("Verification status" + status);
      //TODO put your source here
    }
    public enum ReceiptVerificationStatus {
      ReceiptValid,
      ReceiptNotValid,
      ReceiptServerError,
      ReceiptSandbox,
      ReceiptInternalError    
    };
    DevToDev.Anatylics.RealPayment(string pPaymentId, float pInAppPrice, string pInAppName,
                                   string pInAppCurrencyISOCode);
    DevToDev.AntiCheat.VerifyTime(OnTimeVerifyCallback callback);
    public void onTimeVerifyFinished (DevToDev.TimeVerificationStatus status) {
      Debug.Log ("Verification status" + status);
      //TODO put your source here
    };
    public enum TimeVerificationStatus {
       TimeValid,
       TimeForward,
       TimeRewind
    };
    Create parameters

    Set values and select conditions

    3

    Publish changes

    The configuration is finalised and sent to user devices.

    Click Publish changes to update the configuration.

    Last published – date when the configuration was published.

  • You can use in-app default – in this case the parameter will use the default value define in your app code.

  • Create conditions
    Conditions
    condition
    user segment
    changelog
    Contact Us
    Parameters changelog
    Conditions changelog
    Unity
    • DTDAnalytics 3.9.0

    • DTDPurchases 3.9.0

    Specify the name of the key, for example: "devtodev API Key", and click Generate.

  • To grant the necessary access to the devtodev service, it is necessary to pass the following information:

    • Issuer ID

    • Key ID

    • The generated .p8 file of the In-App Purchase key

    • Bundle identifier of the application (App Bundle ID)

  • Issuer ID
  • Key ID

  • Upload the .p8 file of the In-App Purchase key.

  • When the integration is complete, the status will change to Active.

  • . Paste the copied endpoint URL into the
    Production Server UR
    L and
    Sandbox Server URL
    fields. Click
    Save
    to confirm your changes.
    Settings on the App Store Connect side
    Settings on devtodev side
    For In-App Purchases
    For Subscriptions
    subscriptionHistory
    App Store Connect
    obtained earlier from the App Store
    App Store Connect
    DTDAnalytics.Initialize(APPKey, new DTDAnalyticsConfiguration
    {
      UserId = userId,
      ApplicationVersion = Application.version,
      CurrentLevel = lvl,
      LogLevel = DTDLogLevel.No,
      TrackingAvailability = DTDTrackingStatus.Enable
    });
    DTDPurchases.StartAutoTracking();
    let config = DTDAnalyticsConfiguration()
    DTDAnalytics.initialize(applicationKey: "App ID", configuration: config)
    DTDPurchases.startAutoTracking()
    DTDAnalyticsConfiguration *config;
    [DTDAnalytics applicationKey:@"App ID" configuration:config];
    [DTDPurchases startAutoTracking];
    Logo
    Logo
    DevToDev.Messaging.Uwp 2.4.0nuget

    iOS

    CocoaPods

    CocoaPods is the easiest way to add devtodev into your iOS project.

    1. Firstly, install CocoaPods using:

    2. In the project directory execute the command:

    3. In the created Podfile add the dependency:

    4. Finally, run the command in your Xcode project directory:

    CocoaPods should download and install the devtodev library, and create a new Xcode workspace. Open this workspace in Xcode.

    Manual installation

    1.

    2. Add DTDAnalytics.xcframework to the project

    3. Add frameworks:

    • AppTrackingTransparency.framework

    • AdSupport.framework

    4. Add initialization todidFinishLaunchingWithOptions method:

    • An App ID can be found in the settings of the respective app in devtodev (Settings → SDK → Integration → Credentials).

    • config - an object instance of DTDAnalyticsConfiguration, which is used for specifying additional properties during the initialization.

    DTDAnalyticsConfiguration

    Example:

    Integration features

    For Objective-C

    1. Create Bridging-Header. To do this, you need to add any swift file to the project (don’t delete it later) and choose ‘Create Bridging Header’ in the offered dialog box.

    2. Make sure that the ‘Build Settings’ for ‘Defines Module’ value evaluates to ‘YES’.

    3. While importing, use: #import <DTDAnalytics/DTDAnalytics-Swift.h>

    For SwiftUI

    For SDK to function properly, it needs to be integrated at the earliest moment of the app launch. It is recommended that you use the following method of main entry point initialization:

    Apps targeted at children

    When developing and publishing apps targeted at children under 13 years old, you need to ensure special conditions for data processing. Any mobile app aimed at children or intended for users in a region with strict regulations on child online protection, must comply with current laws.

    Please study the following requirements:

    • USA:

    • EU:

    If your app has to comply with the legal requirements (COPPA), use the following recommendations:

    1. Implement the coppaControlEnable method. The method disables collection of ad IDs and vendor IDs (IDFA, IDFV).

    2. To comply with

      1. Remove AppTrackingTransparency.framework and all the links pointing to it.

    Call the coppaControlEnable method before SDK initialization. If the method was not called, the SDK will work as before.

    Privacy Manifest

    The Privacy Manifest is a new way introduced at for third-party SDK developers to provide information about their privacy policies.

    The Privacy Manifest describes the methods for ensuring code confidentiality in the application in a unified format. When publishing the application, Xcode will combine the privacy manifests of all third-party SDKs used in your application into a single, convenient report. This report makes it easier to create more accurate privacy labels (Nutrition Labels).

    The Privacy Manifest includes the following sections for data entry:

    • Privacy Tracking Enabled

    • Privacy Tracking Domains

    • Privacy Nutrition Label Types

    • Privacy Accessed API Types

    Privacy Tracking Enabled. A Boolean value indicating whether the application or third-party SDK uses data for tracking, as defined within the App Tracking Transparency framework.

    Privacy Tracking Domains. An array of strings listing the internet domains that the application or third-party SDK connects to and participates in tracking.

    Privacy Nutrition Label Types. An array of dictionaries describing the types of data collected by the application or third-party SDK. Nutrition Labels are needed to let users know what data the application collects before installing it from the App Store.

    Privacy Accessed API Types. An array of dictionaries describing the types of APIs accessed by the application or third-party SDK, which are marked as APIs and require verification for access.

    Privacy manifest for Devtodev SDKs:

    Analytics module

    Privacy Nutrition Label Types

    Collected Data Type
    Linked to User
    Used for Tracking
    Collection Purposes

    Privacy Accessed API Types

    Privacy Accessed API Type
    Privacy Accessed API Reasons

    Messaging module

    Privacy Nutrition Label Types

    Collected Data Type
    Linked to User
    Used for Tracking
    Collection Purposes

    Privacy Accessed API Types

    Privacy Accessed API Type
    Privacy Accessed API Reasons

    SDK Signature

    Since we distribute our SDKs as binary dependencies, we have implemented a signing practice. Now, when you use a new version of the SDK, Xcode will confirm that it has been signed by us, increasing the integrity of the software supply chain.

    Unity

    This generation of SDK is deprecated and is no longer supported. Information about the current version can be found here.

    Integration

    Only Unity 5.4 and above is supported.

    Please do the following to integrate your application with devtodev:

    1. Add the application to the Space using the wizard for adding an application.

    2. Attention! If your Unity project can be used for compilations for different platforms, you need to add the applications in devtodev for each platform. As a result, the statistics will be gained for each platform separately.

    3. Attention! If you install SDK versions (1.*) 2.0, 2.0.1 or 2.0.2, you have to delete them before integrating the latest version.

    • Switch on "Analytics" by pressing "On" button

    • Add AppKey and SecretKey for all the using platforms (you can select the platform by clicking on it). If you need to debug, switch logging on.

      App ID and Secret key can be found in the application settings (Open "Settings" → "SDK" → "Integration").

    • Script with all needed parameters if SDK initialization and tracking the user session will be automatically created and added to the scene.

    Using code. Add the following strings to the GameObject which will be on the scene during the whole cycle of application work:

    The appId and appSecret values are unique for each app on each platform and can be found in the settings of appropriate app ("Settings" → "SDK" → "Integration").

    The specificity of integration on Android platform

    Add following lines at the bottom of proguard config

    The specificity of integration on iOS platform

    If you are planning to build an app for iOS, you need to add libz.tbd to the XCode project settings. This library is used by devtodev Unity SDK to compress data sent to devtodev servers. Also, you have to add UserNotifications.framework as an optional library.

    Please add AdSupport.framework into the project for your SDK to function correctly with iOS and also add AppTrackingTransparency.framework for iOS 14.

    Here’s how you can add them.

    Option 1

    1. Create Editor folder in the Assets folder.

    2. In the Assets folder create DevToDevPostBuild.cs script. The script is below:

    3. Uncomment proj.AddFrameworkToProject(projectGuid, "AppTrackingTransparency.framework", true); if necessary.

    Option 2.

    1. Add AdSupport.framework into the Frameworks section of the generated Unity xcodeproj project.

    2. For iOS 14, also add AppTrackingTransparency.framework.

    Additional initialization

    If the application you integrate SDK in is a part of a cross-platform project, then the user data initialization is required.

    Since the analytics of cross-platform projects is based on a unique user (unlike the usual projects where it is based on device identifiers), you have to:

    • Set a unique cross-platform user identifier (it will be used for cross-platform project data collection).

    • Actualize the user data. Mostly it is about game applications where the player has a game level as a characteristic. For such projects, you need to set the current player level.

    We recommend you set the user identifier before SDK initialization, otherwise, the user identifier from the previous session will be used since the SDK initialization moment till the UserID property is set.

    If your cross-platform application is supposed to be used without cross-platform authorization, don't use the UserID property or use the empty string ("") as the user identifier. SDK will assign the unique identifier to the user. This identifier will be used until the real cross-platform identifier assigns to the user.

    If your application allows user to re-login (changing the user during the working session of application), then the UserID property and CurrentLevel method should be called just after the authorization. You don't need to call the SDK initialization one more time.

    Debug mode

    To enable the debug mode and make SDK notifications displayed in the console use this method:

    Collecting data about the amount of sessions and their length

    The data about the amount of sessions and their length is collected automatically by default.

    In case you want to control the beginning and the end of a session manually, use the methods below:

    For the start of the session use the StartSession method:

    For the end of the session use the EndSession method:

    Delivering your application to the Mac App Store

    1. Read an and make sure you follow all the recommendations.

    2. Delete a meta file from DevToDevOSX.bundle:

    3. Rename CFBundleIdentifier in Info.plist inside the devtodev plugin, for example:

    4. Sign the DevToDevOSX.bundle with the .entitlements you created earlier. To do this, type the following into the macOS Terminal:

    Android

    Android Push Notifications

    This generation of SDK is deprecated and is no longer supported. Information about the current version can be found here.

    Push Notifications on Android are sent with the help of the FCM service. To work with it, two keys are required: a client and server key. If you have a project, move on to the second part of this article.

    Creating a project in Firebase

    Add a new project to the .

    Fill in the name and country of your project.

    Congratulations, the project has been created! Now you need to indicate the package name of your Android app.

    After a successful registration in Firebase, you can receive your keys that you will use in devtodev.

    Obtaining the necessary keys

    Go to the settings of your Android project.

    Remember or copy Server key and Sender ID from the Cloud Messaging tab. You will need them to integrate push notifications and create push campaigns.

    Implementation in the app

    Download the generated by google-service.json file and add it to the project.

    The Google services plugin for loads the google-services.json file that you just downloaded. Modify your build.gradle files to use the plugin.

    1. Project-level build.gradle (<project>/build.gradle):

    2. App-level build.gradle (<project>/<app-module>/build.gradle):

    3. Finally, press "Sync now" in the bar that appears in the IDE:

    4. In Activity (where the SDK initializes) add the push notifications initialization and the listener of push notifications processing.

    In case you use several push notifications services or would like to use your own implementation of FirebaseMessagingService, please add the call of the method for displaying messages sent from the devtodev system:

    For example:

    Changing the application settings in devtodev system

    1. Go to and then to your project settings. On the Cloud messaging tab get the Firebase Cloud Messaging token of your project.

    2. Proceed to Settings of your app in devtodev.

    3. Go to Integration page and insert the previously received Firebase Cloud Messaging token to the FCM token field in Push notifications section.

    4. If the Firebase Cloud Messaging token is correct, you will see the following result

    Creating a new push notification in devtodev interface

    1. Open PUSH NOTIFICATIONS section and click on 'Add new campaign" button

    2. Fill in campaign name, select an app for delivery*

    3. Choose user group to send a message. You can choose existing segment or create a new one

    4. Enter notification details

    You can create a campaign only after at least one push token comes from devtodev SDK integrated to your application. Otherwise the app will not be displayed in the list.

    Windows 8.1 and Windows 10

    Integration of push notifications on Windows 8.1 and Windows 10

    This generation of SDK is deprecated and is no longer supported. Information about the current version can be found here.

    General information

    To enable Push Notifications you will have to perform the following actions:

    • Add the application to your space in devtodev system

    • Activate Windows Messaging Service ang get SID and Client Secret values

    • Add SID and Client Secret to the application integration settings in devtodev system

    • Integrate devtodev SDK to the application (see the "SDK integration" section to learn more how to integrate and initialize devtodev SDK)

    How to get SID and Client Secret

    1. Go to the application settings in your Windows Store dashboard

    2. Open Push Notifications submenu in Services menu

    3. Go to Live Services site:

    4. "Package SID" and "Client secret" will be your SID and Client Secret strings respectively

    Implementation to app

    1. Integrate devtodev SDK to your project. Even if you don't need devtodev analytics in your app, you should call DevToDev.SDK.Initialize(string appKey, string appSecret).

    2. Add the following source after DevToDev.SDK.Initialize(string appKey, string appSecret) is called:

      The PushType can have one of the following values:

    3. The control of the current value of a badge. When an app is launched the current value of a badge is reset to zero by default. In order to disable automatic resetting to zero and manually control the value of a badge use the following methods:

    Changing the application settings in devtodev system

    1. Proceed to Settings of your app.

    2. Go to PUSH NOTIFICATIONS page in Settings and insert the previously received Package SID and Client secret to appropriate fields in Push notifications section.

    3. If the Package SID and Client secret are correct, you will see the following result:

    Creating a new push notification in devtodev interface

    1. Open PUSH NOTIFICATION section and click on "Add new campaign" button

    2. Fill in campaign name, select an app for delivery*

    3. Choose the user group to send a message. You can choose existing segment or create a new one

    4. Enter toast or tile details

    *Attention! You can create a campaign only after at least one push token comes from devtodev SDK integrated to your application. Otherwise the app will not be displayed in the list.

    Import historical data via API

    Historical data import is available for .

    If you want to migrate to devtodev from another analytical system, move your data from your company's data collection mechanism, or use the data stored on your servers, feel free to use our Import Data Wizard.

    We recommend that you start importing your historical data no later than one month after connecting your project to devtodev. At the same time, it is desirable that the right-hand border of the data interval is as close as possible to the start date of importing your historical data to devtodev.

    You can import any data about users or events that are allowed by the devtodev data API. The most frequent task is to import an existing user database (with information about registration dates, player character levels, and other characteristics), data on payments, and user sessions. You can also dispatch other events but it is not reasonable to cover a period longer than 90 days from the import start date.

    DTDAnalytics.isRestoreTransactionHistoryRequired { isNeedRestore in
      if isNeedRestore {
        DispatchQueue.main.async {
          SKPaymentQueue.default().restoreCompletedTransactions()
        }
      }
    }
    [DTDAnalytics isRestoreTransactionHistoryRequiredWithCompletionHandler:^(BOOL isNeedRestore){
      if (isNeedRestore == true) {
        dispatch_async(dispatch_get_main_queue(), ^{
          [SKPaymentQueue.defaultQueue restoreCompletedTransactions];
        });
      }
    }];
    if (Application.platform == RuntimePlatform.IPhonePlayer || Application.platform == RuntimePlatform.OSXPlayer)
    {
      var apple = storeExtensionProvider.GetExtension<IAppleExtensions>();
      DTDAnalytics.IsRestoreTransactionHistoryRequired((required) =>
      {
        if(!required) return;
        apple.RestoreTransactions((success, text) =>
        {
          Debug.Log(success ? $"Purchases restored successfully on iOS." : $"{text}");
        });
      });
    }
    sudo gem install cocoapods
    pod init
    platform :ios, '9.0'
    
    target 'TargetName' do
      use_frameworks!
      pod 'DTDAnalytics', '~> 2.0.0'
    end
    pod install
    Logo

    The level of logging the SDK activity. The DTDLogLevel.no value is used by default. For troubleshooting during integration it is recommended to set it to DTDLogLevel.debug, and either switch it off DTDLogLevel.no. Use DTDLogLevel.no in the release version.

    Remove AdSupport.framework all the links pointing to it.

    Analytics

    Purchase History

    No

    No

    Analytics

    Other Data Types

    No

    No

    Analytics

    Parameter

    Type

    Description

    currentLevel

    int

    The player level at the moment of devtodev SDK initialization. It is recommended (but optional) to use to improve data precision.

    userId

    string

    A custom user identifier provided by the developer. If you utilize the default calculation by the device ID, this identifier can be used for finding a user in devtodev.

    In case your project utilizes the calculation by the user identifier, you must set this parameter because it becomes the main user identifier in devtodev.

    trackingAvailability

    DTDTrackingStatus (enum)

    The property allows or disallows devtodev tracking of the user. By default, it is set to DTDTrackingStatus.enable. SDK stores the previously assigned value. Pass DTDTrackingStatus.disable if the user opted out of tracking in line with GDPR.

    logLevel

    Product Interaction

    No

    No

    Analytics

    Device ID

    No

    No

    Analytics

    User ID

    No

    User Defaults

    CA92.1: Access info from same app, per documentation

    Device ID

    No

    No

    Analytics

    Other Data Types

    No

    No

    Analytics

    User Defaults

    CA92.1: Access info from same app, per documentation

    Download the latest version of devtodev SDK from the repository
    Children’s Online Privacy Protection Act (COPPA)
    General Data Protection Regulation (GDPR) Article 8
    Apple’s guidelines
    WWDC23

    DTDLogLevel (enum)

    No

    let config = DTDAnalyticsConfiguration()
    config.logLevel = .error
    DTDAnalytics.initialize(applicationKey: "App ID", configuration: config)
    DTDAnalyticsConfiguration *config = [[DTDAnalyticsConfiguration alloc] init];
    config.logLevel = DTDLogLevelError;
    [DTDAnalytics applicationKey:@"App ID" configuration:config];
    let config = DTDAnalyticsConfiguration()
    config.currentLevel = 1
    config.userId = "CustomUserID"
    config.trackingAvailability = .enable
    config.logLevel = .no
    DTDAnalytics.initialize(applicationKey: "App ID", configuration: config)
    DTDAnalyticsConfiguration *config;
    config.currentLevel = @1;
    config.userId = @"CustomUserID";
    config.trackingAvailability = DTDTrackingStatusEnable;
    config.logLevel = DTDLogLevelNo;
    [DTDAnalytics applicationKey:@"App ID" configuration:config];
    @main
    struct TestSwiftUIApp: App {
        init() {
            let config = DTDAnalyticsConfiguration()
            config.logLevel = .debug
            DTDAnalytics.initialize(applicationKey: "App ID", configuration: config)
        }
    
        var body: some Scene {
            WindowGroup {
                ContentView()
            }
        }
    }
    DTDAnalytics.coppaControlEnable()
    DTDAnalytics.initialize(applicationKey: "App ID", configuration: config)
    [DTDAnalytics coppaControlEnable];
    [DTDAnalytics applicationKey:@"App ID" configuration:config];
    To do that, delete the following files and catalogues:

    For 1.*

    • Assets/DevToDev/ (the folder)

    • Assets/Plugins/Android/ (files android-suport-v4.jar, AndroidManifest.xml, devtodev.jar, devtodev_android_wrapper.jar, google-play-services.jar)

    • Assets/Plugins/iOS/ (files AccrualType.h, CustomEventParams.h, all DevToDev*.h, Gender.h, libdevtodev.a, ReceiptStatus.h, SocialNetwork.h, TimeStatus.h, TutorialState.h)

    • Assets/Plugins/Metro/devtodev.dll

    For 2.0

    • Assets/devtodev (the folder)

    • Assets/Plugins/DevToDevOSX.bundle

    After deleting, unpack devtodev.unitypackage version 2.1 and replace all the files. If you used an interface integration, you have to re-integrate SDK.

  • Unpack the devtodev.unitypackage into the project

  • There are 2 types of integration available:

    In the interface.

    • Open the main screen of the app.

    • Open Window/devtodev menu, then you'll see the following window:

  • Download the latest version of devtodev SDK from the GitHub repository.
    article
  • Optional. To set the custom icons to be shown in push-notification on the SDK part, use the following methods: To set the small icon:

    To set the default color of the small icon:

    To set the large icon:

    Use resources of your app. For example, R.drawable.ic_launcherIcons which set in the push-notification wizard have priority over the icons which set in these methods.

  • Make some tests and correct the message if it's required

  • Schedule the delivery

  • That's it!

  • Firebase console
    Gradle
    Firebase console
  • Add several lines of the code to switch on the push notification in the SDK

  • Create a campaign for sending push notifications in "Push" section

  • Attention! There is a difference in the implementation of the elements mentioned below for Windows 8.1+ and Windows 10+ projects.

    Windows 8.1+: Put the following source in your Application class (usually it is App.xaml.cs file) at the end of the OnLaunched(LaunchActivatedEventArgs e) function. For Example:

    Windows 10+: Put the following source in your Application class (usually it is App.xaml.cs file) at the end of the OnLaunched(LaunchActivatedEventArgs e) and OnActivated(IActivatedEventArgs args)​ functions. For Example:

  • Make sure that these functions are enabled in Package.appmanifest of you project (the flag "Toast capable" is enabled by default for Windows 10+ projects, it is absent in the manifest).

  • Add the following two Background Tasks in Package.appmanifest:

  • Keep in mind that your application must be built with the same Windows Store preferences you used in Chapter 3.2. In the "Create App Packages" window you have to log in with your Live ID and pick the appropriate application form the list. A file Package.StoreAssociation.xml will be added into the Project.

  • Schedule the delivery

  • That's it!

  • The main condition for the successful import of historical data is the match of IDs that are currently dispatched for user/device identification from the devtodev SDK built into your project, with the IDs in your possession - the IDs to which you can link the imported historical data to.

    The best option of importing historical data is when you set custom user IDs tracking in the devtodev system. A custom user ID in the devtodev system is an ID assigned by the developer (see setUserId method in the devtodev SDK integration documentation for the corresponding platform). This is usually the number of the record about the user in your database or a third-party ID by which you authorize and identify the user.

    Attention! By default, devtodev uses device ID for identification. Switching the project to identification by user ID can be done by contacting your account manager or by writing a request to our technical support. Switching to identification by user ID is irreversible!

    After the date of switching the project to identification by user ID, it is advisable to wait 7 days before the start of importing historical data.

    But there is a nuance when it comes to importing historical data - during data merging, the data obtained from third parties will be lost (statistics from markets, data on traffic sources from advertising trackers, and data on income received from advertising networks). If you have such data, then after completing the import process, contact your account manager and they will try to reload the data for the required period.

    To start the process of importing historical data, go to the settings of the project into which you want to load historical data and select Import Historical Data.

    The process of loading historical data consists of several stages:

    1. Preparation stage Click on the start button on the Historical data page. A temporary project will be created in devtodev (you can see it in the list of projects). It will have the same name as the original project, but with the addition of the TMP suffix. You will need to export your historical data to this temporary project. To upload, use the API key that you see on the Import Historical data page. Check out the devtodev data API documentation. Prepare a script that will send the historical data of the project to devtodev. It is extremely important that events are dispatched in chronological order for (at least) each user individually (in a JSON file the dispatched events should be ordered starting from the older ones at the beginning of the file to the newer ones at the end).

    2. Data loading stage After you have prepared the data for loading and are ready to start exporting them to devtodev, click the Start loading data button. At this moment, our server will switch to the mode of receiving historical data. Load the prepared data. If there is a lot of data, then you can expect the loading process to take up to several days. You should aim to keep it within 2 weeks.

    3. Processing uploaded data After your script has finished uploading data to devtodev, click the Upload Finished button on the data upload step. After clicking this button, we will start transferring the uploaded data to our database and calculating metrics for this period of time. The calculation can take up to several hours. At the end of the data processing, the interface for loading historical data will automatically proceed to the next step - data verification. Once data processing is completed we will additionally send you a bell notification.

    4. Reviewing the loaded data This is an extremely important step in the data loading process because it is here that you understand whether you did everything right and are satisfied with the result, or something went wrong, which means that you have to implement the necessary changes and try importing again. Open the devtodev interface and go through all the temporary project reports that can be built from the data you have imported. It is best if you compare the metric data aggregated by devtodev after importing with the metric data aggregated by the analytical system from which you are migrating. If you see incorrect data in the reports (the data does not match the information from your previous analytical system reports), try to find out what could have caused this problem, and to be more accurate, what data could be loaded incorrectly. Contact devtodev support if you are unable to determine the source of the problem. To reupload historical data, click the Clear uploaded data button. Then the data will be deleted and you can try again. If you don’t want to make another attempt, click the Cancel process button. If devtodev shows the data you expected to see - hooray! You have succeeded and you can complete the migration process.

    Attention! If you agree with the result and complete the process of importing historical data (click the Verified button), then re-export or adding another chunk of historical data will be impossible. This action is irreversible!

    5. Historical data is loaded

    Well done, not everyone can reach this stage! Your temporary project ceased to exist. From now on, only the project with the loaded historical data is available to you.

    Learn more about the specifics of loading historical data using API

    You can encapsulate the events either by using historical streamflow or by sending all events for each user individually. But the main thing is that events must be ordered by the date from the oldest to the newest in both each individual parcel and during the entire data loading process.

    As first events, we recommend sending data about the user/device and the application, dating them with the date of user registration. Then you can send any other events.

    This is an example of sent data:

    Business and Enterprise price plans

    Prerequisite:

    Currently remote configs are available only for SDK version 2.6.0 (Android & iOS) and Unity 3.10.0 and higher.

    Google Play

    1

    Settings in Google Cloud Platform Console

    Enable Google Play Android Developer API

    Set up Service account

    Set up Pub/Sub service

    2

    3

    Settings in Google Cloud Platform Console

    If you have already activated the and set up a to work with devtodev, you can skip this section and proceed to .

    Google Play Android Developer API

    1. Go to under your Google account. Select the project (1) for which you want to configure Google Play Developer API. Then go to the APIs and services section (2).

    1. Go to the Library section.

    1. Find the Google Play Developer API section.

    1. Press ENABLE to enable the Google Play Androd Developer API.

    Service account

    Go to the section.

    1. Select the project to link the service account and where you will collect subscription data later. Create a new project if it is not in the list.

    2. In the list of available service keys, click CREATE SERVICE ACCOUNT.

    Pub/Sub service

    Go to the in the Pub/Sub service.

    1. Select the project where you would like to collect subscription data. Click CREATE TOPIC.

    2. To create a topic: fill in the Topic ID (use the screenshot below as an example), disable Add a default subscription option (1) and click CREATE (2).

    Settings on devtodev side

    1. Go to Settings → Payments integration → IA refunds tracking and click edit (3).

    2. Fill in the integration form with the data obtained earlier:

      • Android App ID

    Settings in Google Play Console

    Service account and User permissions

    To set up user permission, you need to log into your Google Play Console account and invite your .

    1. Sign into the .

    2. In the Users and permissions section, you can add users and manage their permissions.

      • Go to Users and permissions.

    Please note that the specified permissions may not apply immediately!

    1. Click Invite user to finish the user setup.

    Monetisation setup

    After in the Pub/Sub service, you'll need to register the created topic as a target for Real-time developer notifications (RTDN).

    1. Go to the app’s Monetise with Play → Monetisation setup section.

    2. Enter the full name of the topic in the Topic name field.

    3. Tick the Enable real-time notifications checkbox.

    4. Send a test notification using the link below (4). If everything is configured correctly, the test notification will change the integration status of the

    Android

    The SDK is available as an AAR (recommended) and JAR library. The library is available in the MavenCentral and .

    Google implementation

    Attention!

    DevToDev.Analytics 2.4.0nuget
    public class YourBehaviourScript : MonoBehaviour
    {
    void Start() 
    {
    #if UNITY_ANDROID
    // <param name="androidAppId"> devtodev App ID for Google Play version of application </param>
    // <param name="androidAppSecret"> devtodev Secret key for Google Play version of application </param>
       DevToDev.Analytics.Initialize(string androidAppId, string androidAppSecret);
    #elif UNITY_IOS
    // <param name="iosAppId"> devtodev App ID for App Store version of application </param>
    // <param name="iosAppSecret"> devtodev Secret key for App Store version of application </param>
       DevToDev.Analytics.Initialize(string iosAppId, string iosAppSecret);
    #elif UNITY_WEBGL
    // <param name="webglAppId"> devtodev App ID for Web version of application </param>
    // <param name="webglAppKey"> devtodev Secret key Web version of application </param>
       DevToDev.Analytics.Initialize(string webglAppId, string webglAppSecret);
    #elif UNITY_STANDALONE_WIN
    // <param name="winAppId"> devtodev App ID for Windows Store version of application </param>
    // <param name="winAppSecret"> devtodev Secret key for Windows Store version of application </param>
       DevToDev.Analytics.Initialize(string winAppId, string winAppSecret);
    #endif
    }
    };
    -keep class com.devtodev.** { *; }
    -dontwarn com.devtodev.**
    #if UNITY_IOS
    using System.IO;
    using UnityEditor;
    using UnityEditor.Callbacks;
    using UnityEditor.iOS.Xcode;
    namespace DevToDev
    {
       public class DevToDevPostBuild
       {
           const string APP_TARGET_NAME = "Unity-iPhone";
           [PostProcessBuildAttribute(1)]
           public static void OnPostprocessBuild(BuildTarget target, string pathToBuiltProject)
           {
               if (target != BuildTarget.iOS)
               {
                   return;
               }
               iOSPostBuild(pathToBuiltProject);
           }
           private static void iOSPostBuild(string projPath)
           {
               string pbxprojPath = projPath + "/Unity-iPhone.xcodeproj/project.pbxproj";
               PBXProject proj = new PBXProject();
               proj.ReadFromString(File.ReadAllText(pbxprojPath));
               string projectGuid = proj.TargetGuidByName(APP_TARGET_NAME);
               proj.AddFrameworkToProject(projectGuid, "AdSupport.framework", true);
               // IOS 14. Xcode 12 required.
               //proj.AddFrameworkToProject(projectGuid, "AppTrackingTransparency.framework", true);
               File.WriteAllText(pbxprojPath, proj.WriteToString());
           }
       }
    }
    #endif
    
    /// <summary> Property allows to initialize the user. 
    /// It applies when SDK initialization or user relogin.</summary>
    /// <param name="activeUserId">unique cross-platform user identifier (max. 64 symbols)</param>
    DevToDev.Analytics.UserId = activeUserID;
    
    /// <summary> Method sets the current user level. 
    /// Using this method allows to actualize the SDK user data in game cross-platform applications.</summary>
    /// <param name="level">number of current game level of the user</param>
    DevToDev.Analytics.CurrentLevel(level);
    
    /// <summary>  Property allows to set current application version.
    /// Attention! This property is necessary for WEB and Windows Standalone apps only.
    /// It will be ignored on other platforms.</summary>
    /// <param name="version"> current version of your application</param>
    DevToDev.Analytics.ApplicationVersion = version;
    
    /// <summary> devtodev App Id and Secret key can be found in the devtodev application
    /// settings page ("Settings" → "SDK" → "Integration") </summary>
    DevToDev.Analytics.Initialize(string appId, string appSecret);
    /// <summary> Enable/Disable log</summary>
    /// <param name="isEnabled">Enabled/Disabled log</param>
    DevToDev.Analytics.SetActiveLog(bool isEnabled);
    //Call this when the session starts or is resumed
    DevToDev.Analytics.StartSession();
    //Call this when the session is completed
    DevToDev.Analytics.EndSession();
    File.Delete (projPath + "/Contents/Plugins/DevToDevOSX.bundle/Contents.meta");
    string plistPath = appPath + "/Contents/Plugins/DevToDevOSX.bundle/Contents/Info.plist";
    PlistDocument plist = new PlistDocument ();
    plist.ReadFromString (File.ReadAllText (plistPath));
    plist.root.SetString ("CFBundleIdentifier", PlayerSettings.applicationIdentifier + ".devtodev");
    File.WriteAllText (plistPath, plist.WriteToString ());
    codesign -f --deep -s 'Mac Developer: Developer Name' --entitlements "yourapp.entitlements" "path/to/your.app/Contents/Plugins/DevToDevOSX.bundle"
    DevToDevPushManager.setCustomSmallIcon(int resourceId);
    DevToDevPushManager.setCustomSmallIconColor(int colorHexadecimal)
    DevToDevPushManager.setCustomLargeIcon(int resourceId);
    buildscript {
      dependencies {
        // Add this line
        classpath 'com.google.gms:google-services:4.3.3'
      }
    }
    dependencies {
       ...
       implementation 'com.devtodev:android:1.14.5'
       implementation 'com.google.android.gms:play-services-base:17.1.0'
       implementation 'com.google.firebase:firebase-core:17.2.3'
       implementation 'com.google.firebase:firebase-messaging:20.1.0'
    }
    // Add to the bottom of the file
    apply plugin: 'com.google.gms.google-services'
    DevToDevPushManager.displayPushNotification(Context context, RemoteMessage remoteMessage);
    public class MyFirebaseMessagingService extends FirebaseMessagingService {
        @Override
        public void onMessageReceived(RemoteMessage remoteMessage) {
            Map<String, String> data = remoteMessage.getData();
            if (data != null) {
                if (data.containsKey("_k")) {
                    DevToDevPushManager.displayPushNotification(this, remoteMessage);
                } else {
                    showNotification(remoteMessage);
                }
            }
        }
    }
    public class MainActivity extends AppCompatActivity implements PushListener {
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            DevToDevPushManager.setPushListener(this);
            DevToDevPushManager.init(getIntent());
        }
    
        @Override
        public void onRegisteredForPushNotifications(String s) {
            // Insert the code for processing the received token
    
        }
    
        @Override
        public void onFailedToRegisteredForPushNotifications(String s) {
            // Insert the code for tracking integration errors
        }
    
        @Override
        public void onPushNotificationsReceived(Map<String, String> map) {
            // Insert the code to track received notifications
        }
    
        @Override
        public void onPushNotificationOpened(PushMessage pushMessage, @Nullable ActionButton actionButton) {
            // Insert the code to track opened notification
        }
    }
    protected override void OnLaunched(LaunchActivatedEventArgs e) {
       //...other source
       DevToDev.PushManager.HandleToastNavigation(e.Arguments);
    }
    protected override void OnLaunched(LaunchActivatedEventArgs e) {
       //...other source
       DevToDev.PushManager.HandleToastNavigation(e.Arguments);
    }
    
    protected override void OnActivated(IActivatedEventArgs args) {
       //...other source
       if (args.Kind == ActivationKind.ToastNotification) {
           var toastArgs = args as ToastNotificationActivatedEventArgs;
           DevToDev.PushManager.HandleToastNavigation(toastArgs.Argument);
       }
    }
    //It is called when push token is received successfully
    PushManager.PushTokenReceived = (pushToken) => {
       //pushToken - the string contains the push token
    };
    
    //It is called when there is an error in push token delivery.
    PushManager.PushTokenFailed = (error) => {
       //error - the error string. This function will be called when push token have not been obtained.
    };
    
    //It is called when push notification is received.
    PushManager.PushReceived = (PushType type, IDictionary<string, string> pushAdditionalData) => {
       //type - type of the push message
       //params - IDictionary<string, string> with the custom user parameters form the push message
    };
    
    //It is called when push notification is opened.
    PushManager.PushOpened = (PushMessage pushMessage, ActionButton actionButton) => {
       //pushMessage - DevToDev.PushMessage. Represents toast notification message
       //actionButton - DevToDev.ActionButton. Windows 10 only! 
       //Represents toast button that was clicked. Could be null if toast body was clicked
    };
    
    DevToDev.PushManager.Initialize();
    public enum PushType {
       ToastNotification, //Notification that can be seen by a user. 
       SilentNotification //Raw-notification. A user can't see it.
    }
    //Disables automatic clearing of a badge at start. 
    //Must be called before DevToDev.PushManager.Initialize();
    DevToDev.PushManager.AutoClearBadgeOnStart = false; 
    
    //Decreases the current value of a badge on "number" units.
    DevToDev.PushManager.DecreaseBadge(int number);
    
    //Clears the current value of a badge.
    DevToDev.PushManager.ClearBadgeCount();
    {
        "reports": [
            {
                "deviceId": "user id",
                "userId": "user id",
                "packages": [
                    {
                        "language": "en",
                        "country": "GB",
                        "appVersion": "1.2",
                        "events": [
                            {
                                "code": "di",
                                "osVersion": "10.2.2",
                                "os": "iOS",
                                "displayPpi": 401,
                                "displayResolution": "1920x1080",
                                "dispalyDiagonal": "5.5",
                                "manufacturer": "Apple",
                                "model": "iPhone8,2",
                                "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_2) AppleWebKit/602.3.12 (KHTML, like Gecko) Version/10.0.2 Safari/602.3.12",
                                "timeZoneOffset": 7200,
                                "idfv": "30FE1CE1-1125-4657-97B0-638744C3C6D1",
                                "idfa": "0A60DCF2-3186-4801-9192-D8CFA995DD6D",
                                "timestamp": 1710772505122
                            },
                            {
                                "code": "ss",
                                "timestamp": 1710772505123,
                                "level": 1
                            },
                            {
                                "code": "pl",
                                "timestamp": 1710772511089,
                                "sessionId": 1710772505123,
                                "level": 1,
                                "parameters": {
                                    "nickname": "John Doe",
                                    "cheater": false
                                }
                            },
                            {
                                "code": "tr",
                                "timestamp": 1710772715371,
                                "sessionId": 1710772505123,
                                "level": 1,
                                "step": -1
                            },
                            {
                                "code": "tr",
                                "timestamp": 1710772725344,
                                "sessionId": 1710772505123,
                                "level": 1,
                                "step": 1
                            },
                            {
                                "code": "lu",
                                "timestamp": 1710772736345,
                                "sessionId": 1710772505123,
                                "level": 2,
                                "balance": {
                                    "money1": 123,
                                    "money2": 11
                                }
                            },
                            {
                                "code": "tr",
                                "timestamp": 1710772741456,
                                "sessionId": 1710772505123,
                                "level": 2,
                                "step": 2
                            },
                            {
                                "code": "tr",
                                "timestamp": 1710772752425,
                                "sessionId": 1710772505123,
                                "level": 2,
                                "step": -2
                            },
                            {
                                "code": "ce",
                                "timestamp": 1710772773675,
                                "sessionId": 1710772505123,
                                "level": 2,
                                "name": "eventName",
                                "parameters": {
                                    "intParameter": 134,
                                    "stringParameter": "hello",
                                    "doubleParameter": 12.98
                                }
                            },
                            {
                                "code": "rp",
                                "timestamp": 1710772798278,
                                "sessionId": 1710772505123,
                                "level": 2,
                                "productId": "com.example.application.starterpack",
                                "orderId": "280001601071201",
                                "price": 19.99,
                                "currencyCode": "USD"
                            },
                            {
                                "code": "ue",
                                "timestamp": 1710772898278,
                                "level": 2,
                                "length": 393,
                                "sessionId": 1710772505123
                            }
                        ]
                    }
                ]
            }
        ]
    }

    If a user enters an A/B test, they will receive parameter values accoring to their test group configuration.

    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).

    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.

    Logo

    Fill in the Service account name field. The Service Account ID field will be filled in automatically (use the screenshot below as an example) and click CREATE AND CONTINUE.

    s
  • Skip the optional steps 2-3 and click DONE at the end of the form.

  • After creating a service account, you will be returned to the available Service accounts list. Select the created account and open the KEYS tab (1), click ADD KEY (2), and then select the JSON key type option (3) in the pop-up window. Click CREATE to confirm your choice. The generated private key file will be downloaded automatically. You will need to upload this key to devtodev later in the following steps (see Settings on devtodev side).

  • Go to the IAM section (1) in the Permissions tab and click GRANT ACCESS (2) to add roles to the service account. In the New principals section (3), enter the service account address and grant it the Pub/Sub Subscriber role (4). If you do not have this role, you need to activate the Pub/Sub service in the Cloud Console (activate Pub/Sub API). Save the changes (5).

  • Select a topic, click on three dots and select View permissions.

  • Click ADD PRINCIPAL to add the service account.

  • Add the [email protected] service account and grant it the role of Pub/Sub Publisher. Save the changes.

  • Open the list of subscriptions and click CREATE SUBSCRIPTION.

  • In the appeared form:

    • Specify the Subscription ID (1).

    • Fill in the Topic name in Select a Cloud Pub/Sub topic field (2).

    • In the Delivery type select Push (3).

    • Copy Endpoint URL from devtodev (see steps 8-9) and insert it in the Endpoint URL field (4). All other parameters remain unchanged. Save the changes.

  • To get Endpoint URL, go to devtodev, select the same app and open app settings (Settings → Payments integration → IA refunds tracking).

  • Copy the Endpoint URL and paste it in the corresponding field in Google Cloud (step 7).

  • Upload the Private key file obtained in Service Account step.

    Click Save.

  • When the integration is complete, the status will change to Active.

  • Click Invite new users.

  • Add Service Account as a new user:

    • Add your service account email in the Email address field (1).

    • In the Permissions section (2) select the apps (3) accessible to the service account. Click Apply (5). Grant permissions to individual apps, or use account permissions to grant access to all apps in your developer account.

    • Grant the necessary rights to perform actions (see next step Account permissions).

  • Open Account permissions tab. Grant the following permissions:

    • View app information (1)

    • View app quality information (2)

    • View financial data (3)

    Click Apply to confirm.

  • Subscriptions
    in devtodev to Active (Settings → Payments integration → Subscriptions → Market connection).
  • Click Save changes to finish the setup.

  • Settings on devtodev side
    Settings in Google Play Console
    Add service account and grant permissions
    Set up Real-time developer notifications
    Google Play Android Developer API
    service account
    Pub/Sub service setup
    Google Cloud Console
    Service accounts
    list of topics
    previously created service account
    Google Play Developer Console
    creating a subscription
    From the SDK version com.devtodev:android-analytics:'2.2.3' and above you need to add com.devtodev:android-google:'1.0.1'.

    This framework encapsulates work with Google ads ID. When developing and publishing apps for kids COPPA, you don’t need com.devtodev:android-google. You can find more information about working with COPPA at the end of this guide.

    Step 1. Declare repositories

    In the Project build.gradle file, declare the mavenCentral repository:

    Step 2. Add Gradle Build Dependencies

    If you use Gradle for building apps specify the following dependencies in the application build.gradle file.

    dependencies {
        // Requirement
        implementation ("com.google.code.gson:gson:*.*.*")
        implementation ("com.google.android.gms:play-services-ads-identifier:*.*.*")
        // Starting from version 2.2.3 and above, it is required
        implementation ("com.devtodev:android-google:*.*.*")
    
        // if you use AAR (recommended) or JAR downloaded from GitHub, please add:
    
    dependencies {
        // Requirement
        implementation 'com.google.code.gson:gson:*.*.*'
        implementation 'com.google.android.gms:play-services-ads-identifier:*.*.*'
        // Starting from version 2.2.3 and above, it is required
        implementation 'com.devtodev:android-google:*.*.*'
        
        // if you use AAR (recommended) or JAR downloaded from GitHub, please add:
        implementation fileTree(dir: "libs", include: ["*.aar"]) 
    

    Peculiarities of working with dependencies

    Working with Advertising ID with Android API level less than 26

    If you plan to use com.google.android.gms:play-services-ads-identifier:18.2.0 and above, you need to add com.android.tools:desugar_jdk_libs to maintain backward compatibility with devices with API level less than 26, see Use Java 8 language features and APIs | Android Studio | Android Developers

    We also recommend AGP 8.0+ as it makes it easier to configure com.android.tools:desugar_jdk_libs

    Huawei implementation

    Step 1. Declare repositories

    If you use Gradle for compiling apps, declare the following dependencies in the build.gradle file in the dependency block:

    Step 2. Add Gradle Build Dependencies

    In the Project build.gradle file declare the agconnect plugin

    In the application build.gradle file declare the following dependencies:

    And add a plugin:

    For more information see huawei official documents.

    Step 3. AppGallery

    The com.devtodev.android-huawei framework works with OAID and ODID IDs. In case the OAID is undefined, we use the ODID. For both IDs to work correctly, take the following steps:

    1. Create a project and an app in AppGallery. Open AppGalleryConnect → Project Settings.

    2. Sign your app using a certificate (see here).

    3. Enter SHA-256 certificate in the App information section. Read more about certificate creation here.

    After taking all the steps described above, open the ‘App information’ section and download agconnect-services.json. You need to place this file in the app folder (read more).

    If during testing the app you see that OAID is unavailable and OAID throws errors, you need to first of all check that the tested build is signed with a certificate.

    SDK Initialization

    Use the following way to initialize the library in the first Activity onCreate() method:

    • You can find the App ID in the settings of the respective app in devtodev (Settings → SDK → Integration → Credentials).

    • config - is a DTDAnalyticsConfiguration object instance that is used for specifying additional properties during initialization.

    DTDAnalyticsConfiguration

    Parameter

    Type

    Description

    currentLevel

    Integer

    The player level at the moment of devtodev SDK initialization. It’s optional but we recommend using it for improving data accuracy.

    userId

    String

    A custom user ID assigned by the developer. In the case of default calculation by device IDs, the identifier can be used for searching users in devtodev. In case the project uses calculation by user IDs, the parameter is mandatory because it becomes the principal calculation ID in devtodev.

    trackingAvailability

    DTDTrackingStatus (enum)

    The property allows or disallows devtodev tracking of the user. By default, it is set to DTDTrackingStatus.enable. SDK stores the previously assigned value. Pass DTDTrackingStatus.disable if the user opted out of tracking in line with GDPR.

    logLevel

    Example:

    SDK obfuscation rules

    Add the following strings to the proguard-rules.pro file of your app

    Apps targeted at children

    When developing and publishing apps targeted at children under 13 years old, you need to ensure special conditions for data processing. Any mobile app aimed at children or intended for users in a region with strict regulations on child online protection, must comply with current laws.

    Please study the following requirements:

    • USA: Children’s Online Privacy Protection Act (COPPA)

    • EU: General Data Protection Regulation (GDPR) Article 8

    If your app has to comply with the legal requirements (COPPA), use the following recommendations:

    1. Implement the coppaControlEnable method. The method disables collection of ad IDs and vendor IDs.

    2. If your app is using Google services, remove the following dependencies from gradle:

    3. If your app is using Huawei services, remove the following dependencies from gradle:

    Call the coppaControlEnable method before SDK initialization. If the method was not called, the SDK will work as before.

    GitHub repository

    Android

    Android Integration

    Platform integration

    Push Notifications on Android are sent with the help of the FCM service.

    You can find instructions on how to create a project in Firebase and integrate Firebase Services into your application in the Firebase documentation.

    In devtodev (Settings → Push notifications → Push notifications panel), specify the Firebase project ID and authorize devtodev to send messages and manage messaging subscriptions for your Firebase application.

    To get the Firebase project ID, go to the Project Settings → General of your Android project in the Firebase Console. Copy the ID and paste it in devtodev settings.

    Download google-services.json and put it to Assets folder.

    Module initialization

    1. For the Messaging module to function you need the basic Analytics package. Before the notification initialization, you need to initialize the SDK. More about it you can read here: .

    2. After the DTDAnalytics initialization block add:

    3. To activate the Messaging module, call :

    Optional:

    You can listen to basic notification module events. To do this, create a class that implements the IDTDPushListener interface and pass it to the DTDMessaging.Android.SetPushListener method.

    Example of the class:

    Full example of notification module initialization:

    Set a custom icon and sound for push notifications

    Unity 2019 and older

    To set a custom sound, icon and its colour in a push notification, copy icons and sounds files in the Assets/Plugins/Android/res/ folder and add the following strings to the manifest file code:

    To set a large user icon in the push notification, add:

    Unity 2020 and newer

    • Delete the \Assets\Plugins\Android\res folder (together with the .meta file) - it will cause an error during assembly.

    • Create a new folder in a separate folder outside of the project.

    • In the new folder, create

    • Create a res folder in the same folder.

    • Add your resources to the res folder while keeping the folder structure intact (drawable, xml, raw, etc.). An example of the resulting structure:

    • Run the following code in the in the command line/terminal:

    • Place the resulting aar file to the \Assets\Plugins\Android\ (edited) folder

    • Add the following strings to the project’s Android manifest file ( \Assets\Plugins\Android\AndroidManifest.xml):

    External interface of the DTDMessaging module for Android platform

    Method
    Description

    Class for receiving Notification data (DTDPushMessage). Main class properties

    Property
    Description

    Class for handling buttons clicked in the notification (DTDActionButton)

    Property
    Description

    Working with A/B tests in the devtodev

    A/B testing is the best way to challenge your hypotheses. A/B testing is essentially an experiment where you show your users different variants of the app at random and then analyze the results to determine which variation performed better.

    In devtodev, you can work with A/B testing in the ‘A/B Testing’ section on the app level. All tests are stored in one table. Besides basic information about each test, you can see its status. There are five types of status:

    • Draft – draft of an unexecuted test.

    • Stopped – the test was stopped before completion. It’s not possible to restart the test. If you want to restart it, make a copy of the test and launch it.

    • In progress – the test is currently in progress.

    • Finished: No winner – test results determined that there was no winner.

    • Finished: Success – test results determined a winner group.

    How to prepare for A/B testing

    Before creating a test in the devtodev interface, you need to set variables through the SDK and use the methods for launching A/B tests. Use certain classes to set the variables and their default values. In case the variables are involved with the test, their values will vary depending on the group defined by the server. If the app will be offline and won’t be able to present the test to the user, he will see default values and the app will continue to function correctly.

    you can find more information about SDK configuration (about setting variables and methods for launching A/B tests).

    Creating an A/B test

    To go to the test creation wizard, open the desired devtodev project, navigate to the ‘A/B Testing’ tab and click the ‘+ Add new A/B test’ button.

    You have opened the segment creation wizard that consists of five steps:

    1. Experiment name

    Enter a unique name of the test and its description. Try to make the description of the test as detailed as possible: its hypothesis, audience, description of the test groups, target metric, desired outcome, etc. Or simply insert a link to the test description. Check out this article to .

    2. Set the audience

    In this section, you need to create test assignment rules and define the audience size. Use the ‘Filter your audience’ and ‘Triggering event’ sections to set the assignment rules.

    Filter your audience – use this option to define user properties that are needed to be included into the test. The devtodev SDK which is integrated into the application, will use the filters to select the audience whose current properties match the test requirements.

    In this example, all paying users will participate in the test.

    If you use several filters at once, only users or devices (this depends on the selected user identification method) that meet all conditions will participate in the test.

    Set a triggering event if you want your test to include only the users who performed a certain event.

    In this example, the devtodev SDK will include the user or device (this depends on the selected user identification method) in the test when the SDK receives information that the said user or device reached the fourth level.

    To each trigger event, you can add more parameters that are related to the event. Events have different lists of additional parameters (see ).

    Please note that you can’t use an event that you sent to devtodev via API as a trigger event.

    The selected filters and trigger events cannot be altered after the start of the experiment.

    The filters and trigger events become available for audience configuration after at least one event/property is received via the SDK, processed and accounted for in the devtodev analytics.

    When applying both filters and trigger events, all conditions have to be met for the user/device to be included into the test.

    Audience fraction – use this option to define the percentage or an absolute number of users out of those selected by the filter and/or completed the trigger event who will participate in the test. If the initial audience size is not enough for drawing firm conclusions, you can change it even after the test begins.

    Please note that one user can participate in only one test at a time.

    If you need to run several tests in parallel, then:

    • You need to create non-overlapping test audiences

    • If your audience overlaps, you need to configure the audience fraction so that part of the audience will be included into each test (e.g. 50% of the overlapping audience gets included into each of the two tests).

    If you know the number of users that you need to achieve a statistically significant result, insert it in the ‘Max number of observation’ cell.

    In this example, 100% of users who completed more than three levels at the time of a sign up will be included in the test.

    A user can be excluded from the test only in two cases:

    • The test time is up.

    • The test is stopped.

    3. Goals

    In this section, you can define the goal of your A/B test – metrics for analysis, criteria for stopping the experiment for a group, and the duration of the experiment.

    You can set up one ‘Primary metric’ and no more than five ‘Secondary metrics’ for each test. The ‘Primary metric’ is used to assess the test result and to calculate statistical significance of the obtained result. ‘Secondary metrics’ are optional and do not take part in the final result assessment. However, they can improve the quality of the analysis and prove that the implemented changes did not influence other key metrics of the app.

    For example, you can select one of the following as a secondary metric:

    • One of the fundamental metrics (ARPU, Paying conversion, Day-N retention, etc.)

    • User engagement with an event.

    • Average number of times an event was completed (per user).

    Below, you can set the ‘Estimated experiment duration’ (days). The test will be stopped after the set number of days. You can also automatically stop the test execution in case the winning group is defined – simply check the ‘Stop the experiment when there is a winning group’ box.

    If you don’t see any sense in continuing the test or you want to change the group settings and restart it, you are free to change the duration of the test or even stop it anytime during its course.

    To calculate the size of the test audience, use any of the . To use them, first estimate the current value of the Primary metric and then define the result that you expect to get from the tested changes. In addition, you can set up several user experience funnels and display their results in the test report. This will give you additional information about how successful the test has been.

    The main goal of the test above is to improve conversion to payment. However you can use the same method to test the conversion to trial or to ARPU.

    4. Group settings

    One of the most crucial steps is setting up test groups and variables. You create a config containing various groups and their parameters. After the devtodev SDK reports that the user has been successfully included in the experiment, one of the groups becomes available in the app via the SDK.

    By default, there are two groups available to you: Group A and a control group. You can increase the number of groups to maximum 4 in a single test by clicking the ‘+Add group’ button.

    The control group usually includes users as they currently are. This way, you can test other variants to see the change in their metrics, relative to the same metrics at the moment. For each group you need to define a set of variables that is composed of the name of the variable and its value. The variables have to be defined inside of your app – they are supposed to grant your users different experiences.

    In the above example, you can see three groups: the control group (it has default parameters) and two more groups that have other parameters for the button_color and button_size variables. The test will be focused on defining the most favorable size and color of the button. If one of the groups wins, it may lead to the change of interface for all users of the app.

    When the app is launching, the SDK defines the test that the user will participate in. Then he is randomly assigned to a test group and the SDK applies all the variables defined for this group.

    5. Checking of the group settings

    To make sure that all the test groups are set up correctly and that the app is handling the selected variables the right way, we highly recommend you to test the current test settings. In this section, you can check how the A/B test configuration runs on test devices, manually determine relevant groups for them and also check how the design and app behavior change in different groups.

    Click ‘+Add test device’ to add a test device using an Advertising ID / User ID / devtodev ID or select it from the list of test devices in the current devtodev Space. After that, select a user group that you want to test on and click ‘Start checking’.

    The settings of the selected group will be applied to the selected test device. From this moment on, the test device will start the test for the selected group and they will not wait for meeting the entry conditions that you have specified above.

    Test devices do not save the information about the active test or the group. After you successfully finish testing one group, select the next one for the same test device and click ‘Restart checking’.

    To be able to access the active test and its group at a test device after restarting the app, use the DTDRemoteConfig.сacheTestExperiment() method before initializing the SDK.

    After you check all group settings on the test devices, you can launch the test for the entire selected audience or save it as a draft.

    A maximum of 8 A/B tests can be run simultaneously in one project.

    Finish the test

    The test can have several outcomes. Let's look at them in more detail.

    Force stop and test delete

    It may so happen that you’ve launched an incorrectly configured test and now you need to stop it. To do this, you can select the required test by clicking on it in the list of experiments.

    • To stop the test (full stop, no chance to resume) – open the A/B test report and click on the edit icon in the upper right corner. The test editing wizard will open. Click on the Stop Test button at the bottom of the wizard page.

    • To remove the A/B test from the list – open the test editing wizard. At the bottom of the wizard page, click the Delete Test button. Please note that you can delete only the tests that were stopped or completed. The created A/B tests stay in the project until the user deletes them.

    The SDK updates the A/B test config only during initialization. If the deleted experiment was previously activated on any device, then when the SDK is initialized, it will be available for activation. After the config gets updated, the SDK will remove this test from the database but will not report it via external interfaces.

    This is intended to avoid changing the app settings that were received from the experiment config at the beginning of the session (e.g, the UI that the user is currently interacting with). The next time the app starts, the test will be deleted during SDK initialization.

    Do not update the app interface and behavior when the user interacts with it. Do not use the network to receive default parameters. It is better to define them in the app

    Test completion using the specified criteria

    • If you check the ‘Stop the experiment when there is a winning group’ box at the third step of the A/B test creation process, the test will automatically stop if ‘Probability to be best’ of one of the groups is larger than 95%. This metric (Probability to be best) can be considered to be a Bayesian approach. This value is auto-calculated for each group based on the selected Primary metric. If you want to stop the test when reaching a higher number (e.g. 99%), you can change the test duration and continue with its execution until you reach the desired outcome.

    • The test can finish when it reaches the end of the time period specified at the ‘Goals’ step in the ‘Estimated experiment duration → duration days’ section which is responsible for the test duration. For example, you set ‘duration days’ as 3. This means that the entire audience has only three days since the test creation to be included into the test and participate in it. When the app is launched and the SDK is initialized, it will compare the current time with the end time of the test. If the experiment time is up, the SDK will erase the test from the database and the users will not be able to receive any data. If the test time has run out when the app has been used, the SDK will not respond.

    This is intended to avoid changing the app settings that were received from the experiment config at the beginning of the session (e.g, the UI that the user is currently interacting with). The next time the app starts, the test will be deleted as described above.

    Test completion report

    During the test execution, a report on the test results will be built and updated in real time. In the upper block, you can find all the basic information about the current state:

    • Current test status

    • Name of the group with the highest ‘Probability to be best’ and its value

    • Number of groups, total number of users in the test, and number of users in each group

    • Experiment time frame

    Below you can see a graph. Its horizontal axis represents calendar days starting from the test start date, while the vertical axis represents the value of the selected metric for each of the groups. You can select the displayed metric in the top left corner of the graph. These are:

    • The Primary and Secondary metrics selected in the wizard

    • Probability to be best

    • A/B test audience

    Underneath you can find a table with aggregated values for each of the metrics in each test group and their fluctuations relative to the control group. The ‘Probability to be best’ is also calculated for each metric including the Secondary. This way you can make sure that all the tested changes do not influence other metrics in a negative way. After that you can see the funnels configured in the A/B test creation wizard. They contain data on the number of users at each funnel stage, conversion rate from one stage to another, and the ‘Probability to be best’ for conversion from the first to the last stage.

    A/B test groups

    When a user gets included into a test group, he is automatically marked with the ID of this group.

    If you want to drill down even more and understand a subtle difference in metrics and behavior of the users, you can use these user groups as filters in any devtodev reports. Simply go to the report filters, open the ‘Segments’ tab, and select the required segment.

    • A/B test segment values are saved as a separate user property (‘A/B test groups’) in the user card at the moment the user gets included into an A/B test group.

    • The user can not enroll into two A/B tests at the same time. He can be included into one test and after it completes, be included into another. In this case, the ‘ A/B test groups’ field will contain names of the two groups.

    Google Play

    1

    SDK Integration

    2

    3

    4

    SDK integration

    Prerequisites:

    • DTDAnalytics 2.5.0 and higher

    To initialize the service, call the startAutoTracking method of the DTDPurchases interface. Call the method after the SDK initialization call:

    Subscription restore

    The SDK will automatically restore the list of previously purchased subscriptions at the first launch of the autotracking service. No additional integration is required.

    Settings in Google Cloud Platform Console

    If you have already activated the and set up a to work with devtodev, you can skip this section and proceed to .

    Google Play Android Developer API

    1. Go to under your Google account. Select the project (1) for which you want to configure Google Play Developer API. Then go to the APIs and services section (2).

    1. Go to the Library section.

    1. Find the Google Play Developer API section.

    1. Press ENABLE to enable the Google Play Androd Developer API.

    Service account

    Go to the section.

    1. Select the project to link the service account and where you will collect subscription data later. Create a new project if it is not in the list.

    2. In the list of available service keys, click CREATE SERVICE ACCOUNT.

    Pub/Sub service

    Go to the in the Pub/Sub service.

    1. Select the project where you would like to collect subscription data. Click CREATE TOPIC.

    2. To create a topic: fill in the Topic ID (use the screenshot below as an example), disable Add a default subscription option (1) and click CREATE (2).

    Settings on devtodev side

    1. Go to Settings → Payments integration → IAP auto tracking and click edit (3).

    2. Fill in the integration form with the data obtained earlier:

      • Android App ID

    Settings in Google Play Console

    Service account and User permissions

    To set up user permission, you need to log into your Google Play Console account and invite your .

    1. Sign into the .

    2. In the Users and permissions section, you can add users and manage their permissions.

      • Go to Users and permissions.

    Please note that the specified permissions may not apply immediately!

    1. Click Invite user to finish the user setup.

    Monetisation setup

    After in the Pub/Sub service, you'll need to register the created topic as a target for Real-time developer notifications (RTDN).

    1. Go to the app’s Monetise with Play → Monetisation setup section.

    2. Enter the full name of the topic in the Topic name field.

    3. Tick the Enable real-time notifications checkbox.

    4. Send a test notification using the link below (4). If everything is configured correctly, the test notification will change the integration status of the

    IOS

    Integration with iOS push notification service

    This generation of SDK is deprecated and is no longer supported. Information about the .

    General information

    To enable Push Notifications, please perform the following actions:

    android {
        defaultConfig {
            // Required when setting minSdkVersion to 20 or lower
            multiDexEnabled = true
        }
        compileOptions {
            // Flag to enable support for the new language APIs
            coreLibraryDesugaringEnabled = true
            // Sets Java compatibility to Java 8
            sourceCompatibility JavaVersion.VERSION_1_8
            targetCompatibility JavaVersion.VERSION_1_8
        }
    }
    dependencies {
        // For AGP 7.4+
        coreLibraryDesugaring ("com.android.tools:desugar_jdk_libs:2.0.3")
        // For AGP 7.3
        // coreLibraryDesugaring ("com.android.tools:desugar_jdk_libs:1.2.3")
        // For AGP 4.0 to 7.2
        // coreLibraryDesugaring ("com.android.tools:desugar_jdk_libs:1.1.9")
    }
    android {
        defaultConfig {
            // Required when setting minSdkVersion to 20 or lower
            multiDexEnabled true
        }
        compileOptions {
            // Flag to enable support for the new language APIs
            coreLibraryDesugaringEnabled true
            // Sets Java compatibility to Java 8
            sourceCompatibility JavaVersion.VERSION_1_8
            targetCompatibility JavaVersion.VERSION_1_8
        }
    }
    dependencies {
        // For AGP 7.4+
        coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.3'
        // For AGP 7.3
        // coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.2.3'
        // For AGP 4.0 to 7.2
        // coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.9'
    }
    dependencies {
        // Requirement
        implementation ("com.google.code.gson:gson:*.*.*")
        implementation ("com.huawei.agconnect:agconnect-core:*.*.*")
        implementation ("com.huawei.hms:opendevice:*.*.*")
        // Starting from version 2.2.3 and above, it is required
        implementation ("com.devtodev.android-huawei:*.*.*")
        implementation ("com.huawei.hms:ads-identifier:*.*.*")
        
        // if you use AAR (recommended) or JAR downloaded from GitHub, please add:
        implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.aar"))))
        
        // or just add the dependency, get the latest version from
        // https://mvnrepository.com/artifact/com.devtodev/android-analytics
        implementation ("com.devtodev:android-analytics:*.*.*")
    }
    dependencies {
        // Requirement
        implementation 'com.google.code.gson:gson:*.*.*'
        implementation 'com.huawei.agconnect:agconnect-core:*.*.*'
        implementation 'com.huawei.hms:ads-identifier:*.*.*'
        implementation 'com.huawei.hms:opendevice:*.*.*'
        // Starting from version 2.2.3 and above, it is required
        implementation 'com.devtodev.android-huawei:*.*.*'
        
        // if you use AAR (recommended) or JAR downloaded from GitHub, please add:
        implementation fileTree(dir: "libs", include: ["*.aar"]) 
        
        // or just add the dependency, get the latest version from
        // https://mvnrepository.com/artifact/com.devtodev/android-analytics
        implementation 'com.devtodev:android-analytics:*.*.*'
    }
    class MainActivity : Activity() {
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
          
            val analyticsConfiguration = DTDAnalyticsConfiguration()
            analyticsConfiguration.logLevel = DTDLogLevel.Error
            DTDAnalytics.initialize("App ID", analyticsConfiguration, context = this)
        }
    }
    public class MainActivity extends Activity {
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            
            DTDAnalyticsConfiguration configuration = new DTDAnalyticsConfiguration();
            configuration.setLogLevel(DTDLogLevel.Error);
            DTDAnalytics.INSTANCE.initialize("App ID", configuration, context);
        }
    }
    val config = DTDAnalyticsConfiguration()
    config.currentLevel = 1
    config.userId = "CustomUserID"
    config.trackingAvailability = DTDTrackingStatus.Enable
    config.logLevel = DTDLogLevel.No
    DTDAnalytics.initialize("App ID", config, context = this)
    DTDAnalyticsConfiguration config = new DTDAnalyticsConfiguration();
    config.setCurrentLevel(1);
    config.setUserId("CustomUserID");
    config.setTrackingAvailability(DTDTrackingStatus.Enable);
    config.setLogLevel(DTDLogLevel.No);
    DTDAnalytics.INSTANCE.initialize("App ID", config, context);
    'com.google.android.gms:play-services-ads-identifier'  
    'com.devtodev:android-google'
    'com.huawei.hms:ads-identifier' 
    'com.huawei.hms:opendevice' 
    'com.devtodev.android-huawei'
    DTDAnalytics.coppaControlEnable()
    DTDAnalytics.initialize("App ID", config, context = this)
    DTDAnalytics.INSTANCE.coppaControlEnable();
    DTDAnalytics.INSTANCE.initialize("App ID", config, context);
    repositories {
       //.. other repositories 
       mavenCentral()
    }
    repositories {
       //.. other repositories 
       mavenCentral()
       maven { url 'https://developer.huawei.com/repo/' }
    }
    
    allprojects {
        repositories {
            //.. other repositories
            mavenCentral()
            // Configure the Maven repository address for the HMS Core SDK.
            maven {url 'https://developer.huawei.com/repo/'}
        }
    }
    dependencies {
        classpath 'com.huawei.agconnect:agcp:*.*.*'
    }
    plugins {
        //.. other plugins
        id 'com.huawei.agconnect'
    }
    -keep class com.devtodev.** { *; }
    -dontwarn com.devtodev.**
    // For Google Mobile Services 
    -keep class com.google.android.gms.** { *; }
    // For Huawei Mobile Services 
    -keep class com.huawei.hms.**{*;}
    Historical data import overview
    AndroidManifest.xml
    with the following content (replace
    company
    and
    package
    with you own names)

    Used to pass the push notification to the FirebaseMessagingService if it was implemented by the client but not by the SDK

    void DTDMessaging.SetPushListener (IDTDPushListener pushListener)

    It sets a listener for push notification event trapping

    bool IsBackground

    The button-click app open mode

    void DTDMessaging.Android.Initialize()

    The push notification initialization method

    void DTDMessaging.Android.StartPushService()

    The push notification activation method. It passes the isAllowed current state

    void DTDMessaging.Android.PushIsAllowed (bool isAllowed)

    A property responsible for the activation/deactivation of push notifications.When the state transitions, it sends a pt with isAllowed (true or false) status to the server.

    The isAllowed flag status is stored in the SDK.

    void DTDMessaging.Android.GetPushState(Action<bool?> onGetPushState)

    The method that returns the push module state to onGetPushState callback. If getting the current state is impossible, it returns null

    void DTDMessaging.Android.GetToken(Action<string> onGetToken)

    The method that returns push registration token to onGetToken callback

    IDictionary<string,string> GetData():

    Complete information passed with the push notification.

    DTDActionType ActionType:

    The property that returns the value of enum’s DTDActionType.

    Possible values:

    App - app open

    Url - external link open

    Share - share content

    Deeplink - an in-app link opening

    string ActionString

    The property that returns an optional action identifier

    IDictionary<string,string> AdditionalData()

    Additional data sent to push notification

    DTDActionType ActionType

    The property that returns the value of enum’s DTDActionType.

    Possible values:

    App - app open

    Url - external link open

    Share - share content

    Deeplink - an in-app link opening

    string ActionString

    Property that returns an optional action identifier

    string ButtonId

    Property that returns the ID of the clicked button

    string ButtonText

    Property that returns the text of the clicked button

    string ButtonIcon

    Property that returns the button icon name

    Unity Integration

    void DTDMessaging.Android.ProcessPushNotification (IDictionary<string, string> firebaseMessaging)

    Prerequisites:

    • DTDAnalytics 2.5.0 and higher

    Prerequisites:

    • DTDAnalytics 3.9.0

    • DTDPurchases 3.9.0

    Fill in the Service account name field. The Service Account ID field will be filled in automatically (use the screenshot below as an example) and click CREATE AND CONTINUE.

    s
  • Skip the optional steps 2-3 and click DONE at the end of the form.

  • After creating a service account, you will be returned to the available Service accounts list. Select the created account and open the KEYS tab (1), click ADD KEY (2), and then select the JSON key type option (3) in the pop-up window. Click CREATE to confirm your choice. The generated private key file will be downloaded automatically. You will need to upload this key to devtodev later in the following steps (see Settings on devtodev side).

  • Go to the IAM section (1) in the Permissions tab and click GRANT ACCESS (2) to add roles to the service account. In the New principals section (3), enter the service account address and grant it the Pub/Sub Subscriber role (4). If you do not have this role, you need to activate the Pub/Sub service in the Cloud Console (activate Pub/Sub API). Save the changes (5).

  • Select a topic, click on three dots and select View permissions.

  • Click ADD PRINCIPAL to add the service account.

  • Add the [email protected] service account and grant it the role of Pub/Sub Publisher. Save the changes.

  • Open the list of subscriptions and click CREATE SUBSCRIPTION.

  • In the appeared form:

    • Specify the Subscription ID (1).

    • Fill in the Topic name in Select a Cloud Pub/Sub topic field (2).

    • In the Delivery type select Push (3).

    • Copy Endpoint URL from devtodev (see steps 8-9) and insert it in the Endpoint URL field (4). All other parameters remain unchanged. Save the changes.

  • To get Endpoint URL, go to devtodev, select the same app and open app settings (Settings → Payments integration → Subscriptions). Click Integrate.

  • Copy the Endpoint URL and paste it in the corresponding field in Google Cloud (step 7).

  • Upload the Private key file obtained in Service Account step.

    Click Save.

  • When the integration is complete, the status will change to Active.

  • If you use subscriptions, go to Payments integration → Subscriptions and click Integrate.

  • Upload the Private key file obtained in Service Account step (1). Specify Android App ID (2). Add all existing subscriptions, specifying the bundle, subscription period, and name for each subscription. Click Save (3) to finish integration.

  • Click Invite new users.

  • Add Service Account as a new user:

    • Add your service account email in the Email address field (1).

    • In the Permissions section (2) select the apps (3) accessible to the service account. Click Apply (5). Grant permissions to individual apps, or use account permissions to grant access to all apps in your developer account.

    • Grant the necessary rights to perform actions (see next step Account permissions).

  • Open Account permissions tab. Grant the following permissions:

    • View app information (1)

    • View app quality information (2)

    • View financial data (3)

    Click Apply to confirm.

  • Subscriptions
    in devtodev to Active (Settings → Payments integration → Subscriptions → Market connection).
  • Click Save changes to finish the setup.

  • Settings in Google Cloud Platform Console
    Enable Google Play Android Developer API
    Set up Service account
    Set up Pub/Sub service
    Settings on devtodev side
    Settings in Google Play Console
    Add service account and grant permissions
    Set up Real-time developer notifications
    Google Play Android Developer API
    service account
    Pub/Sub service setup
    Google Cloud Console
    Service accounts
    list of topics
    previously created service account
    Google Play Developer Console
    creating a subscription
    dependencies {
        implementation ("com.devtodev:android-google-purchases:*.*.*")
        // Starting from version 6.0.0
        implementation ("com.android.billingclient:billing:*.*.*")
    }
    val config = DTDAnalyticsConfiguration()
    DTDAnalytics.initialize("App ID", config, context)
    DTDGooglePurchases.initialize(context)
    DTDGooglePurchases.startAutoTracking()
    DTDAnalytics.Initialize(APPKey, new DTDAnalyticsConfiguration
    {
      UserId = userId,
      ApplicationVersion = Application.version,
      CurrentLevel = lvl,
      LogLevel = DTDLogLevel.No,
      TrackingAvailability = DTDTrackingStatus.Enable
    });
    DTDPurchases.StartAutoTracking();
    DTDAnalyticsConfiguration config = new DTDAnalyticsConfiguration();
    DTDAnalytics.INSTANCE.initialize(appKey, config, context);
    DTDGooglePurchases.INSTANCE.initialize(context);
    DTDGooglePurchases.INSTANCE.startAutoTracking();
    dependencies {
        implementation 'com.devtodev:android-google-purchases:*.*.*'
        // Starting from version 6.0.0
        implementation 'com.android.billingclient:billing:*.*.*'
    }
    implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.aar"))))
    // or just add the dependency, get the latest version from
    // https://mvnrepository.com/artifact/com.devtodev/android-analytics
    implementation ("com.devtodev:android-analytics:*.*.*")
    // Optional (recommended)
    implementation ("com.android.installreferrer:installreferrer:*.*.*")
    }
    // or just add the dependency, get the latest version from
    // https://mvnrepository.com/artifact/com.devtodev/android-analytics
    implementation 'com.devtodev:android-analytics:*.*.*'
    // Optional (recommended)
    implementation 'com.android.installreferrer:installreferrer:*.*.*'
    }

    DTDLogLevel (enum)

    The level of logging the SDK activity. The DTDLogLevel.no value is used by default. For troubleshooting during integration it is recommended to set it to DTDLogLevel.debug, and either switch it off DTDLogLevel.no. Use DTDLogLevel.no in the release version.

  • Add the application to your space in devtodev system.

  • Generate Developer or Production Certificate for the application and get Private key file (.p12) on its basis.

  • Submit the data to the application settings in devtodev system.

  • Integrate devtodev SDK to the application (see the SDK integration section to learn more about integrating and initializing devtodev SDK).

  • Add several lines of the code to switch in the push notification to the SDK.

  • Create a campaign for sending push notifications in section.

  • Enabling Push Notifications and certificate generation

    Enabling Push Notifications

    First enable push notifications in your Xcode project.

    The library provides support for iOS 10 notification attachments, such as images, animated gifs, and video. In order to take advantage of this functionality, you will need to create a notification service extension alongside your main application.

    Create a new iOS target in Xcode (File -> New -> Target) and select the Notification Service Extension type.

    ​In Member Center, the Push Notifications service will appear as Configurable (not Enabled) until you create a client SSL certificate.

    Drag the devtodevAppExtensions.framework into your app project.

    Modify your extension.

    1. Delete all dummy source code for your new extension

    2. Inherit from DTDMediaAttachmentExtension in NotificationService

    Adding Capabilities

    Use Xcode to enable push notifications in the target’s Capabilities pane:

    Enable Background Modes and Remote notifications under the target’s Capabilities section:

    Creating a Universal Push Notification Client SSL Certificate

    You use Member Center to generate a push notification client SSL certificate that allows your notification server to connect to the APNs. Each App ID is required to have its own client SSL certificate. The client SSL certificate Member Center generates is a universal certificate that allows your app to connect to both the development and production environments.

    Only a team agent or admin can generate Apple Push Notification service SSL certificates.

    To generate a universal client SSL certificate

    1. In Certificates, Identifiers & Profiles, select Certificates.

    2. Click the Add button (+) in the upper-right corner.

    3. Under Production, select the “Apple Push Notification service SSL (Sandbox & Production)” checkbox, and click Continue.

    4. Choose an App ID from the App ID pop-up menu, and click Continue. Choose the explicit App ID that matches your bundle ID.

    5. Follow the instructions on the next webpage to create a certificate request on your Mac, and click Continue.

    6. Click Choose File.

    7. In the dialog that appears, select the certificate request file (with a .certSigningRequest extension), and click Choose.

    8. Click Generate.

    9. Click Download.

    Follow these steps to export the certificate from Apple web-site to the P12-file:

    1. Open "Keychain access" application

    2. If the certificate hasn't been added to keychain access yet, choose "File" → "Import". Find the certificate file (CER-file) provided by Apple

    3. Choose "Keys" section in "Keychain access" application

    4. Choose a personal key associated with your iPhone developer certificate. Personal key is identified by open certificate associated with it "iPhone developer: ". Choose "File" → Export objects. Save key as .p12

    5. You'll be suggested to create a password which is used when you need to import the key to another computer

    Convert the certificate from Apple web-site to the P12-file on Windows OS

    Convert Apple certificate file to the PEM-file. Start following command-line operation from bin catalog OpenSSL.

    Convert personal key from Mac OS keychain to the PEM-key:

    Now you are able to create P12-file using PEM-key and iPhone developer certificate:

    If you are using key from Mac OS keychain then choose PEM-version created in the previous step. Otherwise, you can use OpenSSL key for Windows OS.

    Upload the certificate to the site

    Upload the .p12-file into Integration section of application settings panel (Settings -> Push Notifications):

    SDK Integration

    After the certificate has been generated you can start to integrate Push SDK into your app.

    1. Open the "Capabilities" tab in the XCode. Switch "Push Notifications" and "Background Modes" to ON. In "Background Modes" check "Remote notifications".

    2. Add the following strings to the AppDelegate class:

    If you are using iOS 12 or above, you can specify custom options for notification settings:

    3. Open "Build Settings", find the parameter "Other Linker Flags", then add 2 flags: -ObjC and -lc++

    4. Compile and run the app. You will need a device because the simulator does not support push notifications. Xcode will automatically choose a new provisioning profile. If an error occurred during the launch make sure that there is a correct profile set in the Code Signing Identity. You'll be asked to confirm push notifications. An app will request permission only once, if user confirms it - notifications will be accepted otherwise he won't get any push messages from your app. Users can change it in device settings.

    Creating a new push notification in devtodev interface

    1. Open PUSH NOTIFICATIONS section and click on the "Add new campaign" button

    2. Fill in the campaign name

    You can create a campaign only after at least one push token comes from devtodev SDK integrated into your application. Otherwise, the app will not be displayed in the list.

    3. Choose a user group to send a message. You can choose an existing segment or create a new one

    4. Enter notification details

    5. Test push notification (or skip this step)

    6. Confirm push gateway

    7. Schedule the delivery

    8. That's it!

    current version can be found here
    Here
    learn more about test planning
    Events
    Size Calculators

    Unreal Engine

    Plugin installation

    The SDK is available in GitHub repository. Download the Source code (zip) of latest release. Unzip the archive and copy DTDAnalytics folder to the Plugins folder of your project.

    For a C++ project type, add the DTDAnalytics name to the list of dependency module names to the <module_name>.Build.cs file of the module in which you plan to use the plugin.

    Example:

    Data types

    class UDTDAnalyticsLibrary

    A class that implements analytic methods.

    Header file:

    class UDTDUserCardLibrary

    A class that implements user card methods.

    The class header:

    enum class EDTDTrackingStatus : uint8

    SDK tracking status.

    Header file:

    Values:

    • Unknown = 0 - leave tracking unchanged

    • Enable = 1 - tracking enabled

    • Disable = 2 - tracking disabled

    Example:

    enum class EDTDLogLevel : uint8

    SDK logging level.

    Header file:

    Values:

    • Unknown = 0 - leave logging level unchanged

    • No = 1 - logging disabled

    • Error = 2 - logging of errors

    Example:

    enum class EDTDAccrualType : uint8

    Types of resource accumulation.

    Header file:

    Values:

    • Earned = 0 - earned resources

    • Bought = 1 - purchased resources

    Example:

    enum class EDTDSocialNetwork : uint8

    Predefined social media.

    Header file:

    Values:

    • Facebook = 0

    • Vkontakte = 1

    • Twitter = 2

    Example:

    enum class EDTDReferralProperty : uint8

    Referral properties.

    Header file:

    Values:

    • Source = 0

    • Medium = 1

    • Content = 2

    Example:

    struct FDTDOptionalInt32

    An optional parameter of int32 type

    Header file:

    Member
    Type
    Description

    For your convenience, we implemented the conversion constructor:

    Example:

    struct FDTDOptionalString

    An optional parameter of FString type.

    Header file:

    Member
    Type
    Description

    For your convenience, we implemented the conversion constructor:

    Example:

    struct FDTDAnalyticsConfiguration

    Configuration of the analytics plugin.

    Header file:

    Member
    Type
    Description

    Example:

    FDTDCustomEventParams

    Custom parameters of a custom event.

    Header file:

    Member
    Type
    Description

    Warning: avoid duplicating keys in parameters of different types, because in native code dictionaries are merged into a single dictionary [string: any].

    Example:

    struct FDTDStartProgressionEventParams

    Parameters of the progression start event.

    Header file:

    Member
    Type
    Description

    Example:

    struct FDTDFinishProgressionEventParams

    Parameters of the progression completion event.

    Header file:

    Member
    Type
    Description

    Example:

    Delegates

    Header file:

    Definitions:

    enum class EDTDGender: uint8 (Deprecated)

    User gender.

    Header file:

    Values:

    • Unknown = 0

    • Male = 1

    • Female = 2

    Example:

    SDK initialization

    SDK initialization without parameters:

    Member
    Type
    Description

    SDK initialization with parameters:

    Member
    Type
    Description

    Unity

    This generation of SDK is deprecated and is no longer supported. Information about the .

    Push Notifications are available only for the supported platforms: iOS, Android, Windows Store/Windows Phone 8.1/10.

    To enable Push Notifications you will have to perform the following actions:

    DTDMessaging.Android.Initialize();
    DTDMessaging.Android.StartPushService();
    public class MyPushListener : IDTDPushListener
    {
        public void OnPushServiceRegistrationSuccessful(string deviceId)
        {
            Debug.Log(deviceId);
        }
    
        public void OnPushServiceRegistrationFailed(string error)
        {
            Debug.Log(error);
    
        }
    
        public void OnPushNotificationReceived(DTDPushMessage message)
        {
            Debug.Log(message);
        }
    
        public void OnInvisibleNotificationReceived(DTDPushMessage message)
        {
            //IOS only.
        }
    
        public void OnPushNotificationOpened(DTDPushMessage pushMessage, DTDActionButton actionButton)
        {
            Debug.Log(pushMessage.ToString());
            Debug.Log(actionButton.ToString());
        }
    }
    public class MyPushListener : IDTDPushListener
    {
        public void OnPushServiceRegistrationSuccessful(string deviceId)
        {
            Debug.Log(deviceId);
        }
    
        public void OnPushServiceRegistrationFailed(string error)
        {
            Debug.Log(error);
        }
    
        public void OnPushNotificationReceived(DTDPushMessage message)
        {
            Debug.Log(message);
        }
    
        public void OnInvisibleNotificationReceived(DTDPushMessage message)
        {
            Debug.Log(message);
        }
    
        public void OnPushNotificationOpened(DTDPushMessage pushMessage, DTDActionButton actionButton)
        {
            Debug.Log(pushMessage.ToString());
            Debug.Log(actionButton.ToString());
        }
    }
    
    public class NotificationExample : MonoBehaviour
    {
        private const string APP_KEY = "***************"
        void Start()
        {
            DTDAnalytics.Initialize(APP_KEY);
            DTDMessaging.Android.SetPushListener(new PushListener());
            DTDMessaging.Android.Initialize();
            DTDMessaging.Android.StartPushService();
        }
    }
    <meta-data
     android:name="com.devtodev.push.default_small_icon"
     android:resource="@drawable/ic_icon_name" />
    
    <meta-data
     android:name="com.devtodev.push.default_small_icon_color"
     android:resource="@color/icon_color" />
    <meta-data
    android:name="com.devtodev.push.default_large_icon"
    android:resource="@mipmap/ic_large_icon_name"/>
    <manifest package="com.company.package">
    </manifest>
    ├─── AndroidManifest.xml
    └─── res
        └─── drawable
            └─── smallIcon0.png
         └─── mipmap
            └─── largeIcon0.png
         └─── raw
            └─── iconsound.wav 
    jar cvf resources.aar -C . .
    <meta-data android:name="com.devtodev.push.default_small_icon" android:resource="@drawable/smallIcon0" />
    <meta-data android:name="com.devtodev.push.default_large_icon" android:resource="@mipmap/largeIcon0" />
    //NotificationService.h
    #import <devtodevAppExtensions/devtodevAppExtensions.h>
    
    @interface NotificationService : DTDMediaAttachmentExtension
    
    @end
    
    //NotificationService.m
    #import "NotificationService.h"
    
    @implementation NotificationService
    // NOTE: Keep this empty implementation to prevent class stripping.
    @end
    openssl x509 -in developer_identity.cer -inform DER -out developer_identity.pem -outform PEM
    openssl pkcs12 -nocerts -in mykey.p12 -out mykey.pem
    openssl pkcs12 -export -inkey mykey.key -in developer_identity.pem -out iphone_dev.p12
    #import "AppDelegate.h"
    #import <devtodev/devtodev.h>
    
    @implementation AppDelegate
    
    - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary
    *)launchOptions {
        
       [DevToDev initWithKey:@“APP_KEY” andSecretKey:@“SECRET_KEY”];
    
        dispatch_async(dispatch_get_main_queue(), ^{
          [DevToDev pushManager].delegate = self;
          [DevToDev pushManager].pushNotificationsOptions = (DTDNotificationOptionAlert | DTDNotificationOptionBadge | DTDNotificationOptionSound);
          [DevToDev pushManager].pushNotificationsEnabled = YES;
        });
    
        // Your code...
        return YES;
    }
    
    
    /**
     * @brief Device received a token and was successfully subscribed for notifications
     * @param deviceToken device push token
     **/
    -(void) didRegisterForRemoteNotificationsWithDeviceToken: (NSString *) deviceToken {
    
    }
    
    /**
     * @brief Error occured while registering for push notifications
     * @param error text
     **/
    -(void) didFailToRegisterForRemoteNotificationsWithError: (NSError *) error {
    
    }
    
    /**
     * @brief Push notification has been received by the device
     * @param notification data
     **/
    -(void) didReceiveRemoteNotification: (NSDictionary *) notification {
    
    }
    
    /**
     * @brief Push notification has been opened
     * @param pushMessage body
     * @param actionButton button that was clicked
     **/
    -(void) didOpenRemoteNotification: (DTDPushMessage *) pushMessage withAction: (DTDActionButton *) actionButton {
    
    }
    
    /**
     * @brief Push notification receive response
     * Note: This method is relevant only for iOS 10 and above.
     * @param response notification
     **/
    -(void) didReceiveNotificationResponse: (DTDNotificationResponse *)response {
    
    }
    typedef NS_OPTIONS(NSUInteger, DTDNotificationOptions) {
        DTDNotificationOptionBadge   = (1 << 0),
        DTDNotificationOptionSound   = (1 << 1),
        DTDNotificationOptionAlert   = (1 << 2),
        DTDNotificationOptionCarPlay = (1 << 3),
        DTDNotificationOptionCriticalAlert = (1 << 4),
        DTDNotificationOptionProvidesAppNotificationSettings = (1 << 5),
        DTDNotificationOptionProvisional = (1 << 6)
    };

    Warning = 3 - logging of warnings and errors

  • Info = 4 - logging of information messages, warnings and errors

  • Debug = 5 - logging of debugging messages, informational messages, warnings and errors

  • Googleplus = 3
  • Whatsapp = 4

  • Viber = 5

  • Evernote = 6

  • Googlemail = 7

  • Linkedin = 8

  • Pinterest = 9

  • Reddit = 10

  • Renren = 11

  • Tumblr = 12

  • Qzone = 13

  • Campaign = 3
  • Term = 4

  • FDTDOptionalString

    Application version (Windows)

    TrackingAvailability

    EDTDTrackingStatus

    Tracking settings

    TMap<FString, bool>

    Boolean parameters

    TMap<FString, int64>

    Resources earned

    HasValue

    bool

    Option label

    Value

    int32

    Parameter value

    HasValue

    bool

    Option label

    Value

    FString

    Parameter value

    LogLevel

    EDTDLogLevel

    Logging level

    CurrentLevel

    FDTDOptionalInt32

    Current level

    UserId

    FDTDOptionalString

    User ID

    StringParameters

    TMap<FString, FString>

    String parameters

    IntParameters

    TMap<FString, int64>

    Integer parameters

    FloatParameters

    TMap<FString, float>

    Real parameters (floating-point numbers)

    Difficulty

    FDTDOptionalInt32

    Difficulty

    Source

    FDTDOptionalString

    Source

    SuccessfulCompletion

    bool

    Successful completion of the progression (‘false’ by default)

    Duration

    int32

    Duration (if 0, duration is calculated automatically)

    Spent

    TMap<FString, int64>

    Resources spent

    appKey

    FString

    You can find it in the settings of the corresponding application in devtodev (Settings → SDK → Integration → Credentials → App ID)

    appKey

    FString

    You can find it in the settings of the corresponding application in devtodev (Settings → SDK → Integration → Credentials → App ID)

    config

    FDTDAnalyticsConfiguration

    Initialization parameters

    Blueprint
    Blueprint

    ApplicationVersion

    BoolParameters

    Earned

    PublicDependencyModuleNames.Add("DTDAnalytics");
    #include "DTDAnalytics/Public/DTDAnalyticsBPLibrary.h"
    #include "DTDAnalytics/Public/DTDUserCardBPLibrary.h"
    #include "DTDAnalytics/Public/DTDTrackingStatus.h"
    EDTDTrackingStatus TrackingStatus = EDTDTrackingStatus::Enable;
    #include "DTDAnalytics/Public/DTDLogLevel.h"
    EDTDLogLevel LogLevel = EDTDLogLevel::Info;
    #include "DTDAnalytics/Public/DTDAccrualType.h"
    EDTDAccrualType AccrualType = EDTDAccrualType::Earned;
    #include "DTDAnalytics/Public/DTDSocialNetwork.h"
    EDTDSocialNetwork SocialNetwork = EDTDSocialNetwork::Facebook;
    #include "DTDAnalytics/Public/DTDReferralProperty.h"
    EDTDReferralProperty ReferralProperty = EDTDReferralProperty::Source;
    #include "DTDAnalytics/Public/DTDOptionalInt32.h"
    FDTDOptionalInt32(int32 value) : HasValue(true), Value(value) {}
    FDTDOptionalInt32 OptionalParameter = 1;
    #include "DTDAnalytics/Public/DTDOptionalString.h
    FDTDOptionalString(FString value) : HasValue(true), Value(value) {}
    FDTDOptionalString OptionalParameter = FString("StringValue");
    #include "DTDAnalytics/Public/DTDAnalyticsConfiguration.h"
    FDTDAnalyticsConfiguration config;
    config.LogLevel = EDTDLogLevel::Debug;
    config.CurrentLevel = 3;
    config.UserId = FString("CUID");
    config.ApplicationVersion = FString("1.2.3");
    config.TrackingAvailability = EDTDTrackingStatus::Enable;
    #include "DTDAnalytics/Public/DTDCustomEventParams.h"
    FDTDCustomEventParams params;
    params.BoolParameters.Add("BoolKey", true);
    params.FloatParameters.Add("FloatKey", 3.3);
    params.IntParameters.Add("IntKey");
    params.StringParameters.Add("StringKey", "StringValue");
    #include "DTDAnalytics/Public/DTDStartProgressionEventParams.h"
    FDTDStartProgressionEventParams params;
    params.Difficulty = 3;
    params.Source = FString("Source");
    #include "DTDAnalytics/Public/DTDFinishProgressionEventParams.h"
    FDTDFinishProgressionEventParams params;
    params.Duration = 200;
    params.SuccessfulCompletion = true;
    params.Earned.Add("CurrencyName1", 1);
    params.Spent.Add("CurrencyName2", 2);
    #include "DTDAnalytics/Public/DTDDelegates.h"
    DECLARE_DELEGATE_OneParam(FDTDLongListenerDelegate, int64);
    DECLARE_DELEGATE_OneParam(FDTDGetterStringDelegate, const FString&);
    DECLARE_DELEGATE_OneParam(FDTDGetterBoolDelegate, bool);
    DECLARE_DELEGATE_OneParam(FDTDGetterIntDelegate, int32);
    DECLARE_DELEGATE_OneParam(FDTDGetterLongDelegate, int64);
    DECLARE_DELEGATE_OneParam(FDTDGetterGenderDelegate, EDTDGender);
    DECLARE_DELEGATE_TwoParams(FDTDGetterOptionalStringDelegate, bool, const FString&);
    DECLARE_DELEGATE_TwoParams(FDTDGetterOptionalBoolDelegate, bool, bool);
    DECLARE_DELEGATE_TwoParams(FDTDGetterOptionalFloatDelegate, bool, float);
    DECLARE_DELEGATE_TwoParams(FDTDGetterOptionalLongDelegate, bool, int64);
    DECLARE_DELEGATE_ThreeParams(FDTDGetterOptionalStringWithKeyDelegate, bool, const FString&, const FString&);
    DECLARE_DELEGATE_ThreeParams(FDTDGetterOptionalBoolWithKeyDelegate, bool, const FString&, bool);
    DECLARE_DELEGATE_ThreeParams(FDTDGetterOptionalFloatWithKeyDelegate, bool, const FString&, float);
    DECLARE_DELEGATE_ThreeParams(FDTDGetterOptionalLongWithKeyDelegate, bool, const FString&, int64);
    #include "DTDAnalytics/Public/DTDGender.h"
    EDTDGender Gender = EDTDGender::Female;
    UDTDAnalyticsBPLibrary::Initialize("AppKey");
    FDTDAnalyticsConfiguration config;
    config.LogLevel = EDTDLogLevel::No;
    config.CurrentLevel = 3;
    config.UserId = FString("CUID");
    config.ApplicationVersion = FString("1.2.3");
    config.TrackingAvailability = EDTDTrackingStatus::Enable;
    UDTDAnalyticsBPLibrary::InitializeWithConfig("AppKey", config);
  • Add the application to your space in devtodev system

  • Android. Get API key from Google APIs Console. It is necessary to activate Google Cloud Messaging for Android before key generation. Detailed information on how to receive an API key you can find in native Android devtodev SDK documentation

  • iOS. Generate Developer or Production Certificate for the application and get Private key file (.p12) on its basis. Detailed information on how to receive a Private key file you can find in native iOS devtodev SDK documentation

  • Submit the data to the application settings in devtodev system

  • Integrate devtodev SDK to the application (see the "" to learn more about integrating and initializing devtodev SDK)

  • Add several lines of the code to switch in the push notification to the SDK

  • Create a campaign for sending push-notifications in "Push" section

  • SDK Integration

    Android platform features:

    Go to Firebase console and then to your project or create a new one. Here is complete guide on adding your project to Firebase console and enabling Cloud messaging.

    Download google-services.json from your Firebase console. Add this file into your project’s Assets folder.

    Please do the following to find google-services.json:

    1. Choose your project in the Firebase console

    2. Choose project settings in the Project overview

    3. Scroll down to the SDK setup and configuration. Click on the google-services.json

    Using devtodev and Firebase Messaging services at the same time

    If you want to use both devtodev and Firebase Messaging services at the same time, you need to disable Firebase listener.

    • Find androidmanifest.xml used in your app. If you don’t use Custom Manifest, you need to create it. Tick the Custom Main Manifest checkbox:

      You can read more about the manifest here.

    • Add the following line to the “Application” section:

    • You should get something like this:

    Windows Store 10 platform features:

    Build a Windows Store App in Unity. After the app is built, Visual Studio project will be created. Proceed with the following changes.

    There is a difference in the implementation of the elements mentioned below for different types of projects:

    .NET + D3D: Put the following source in your App class (usually it is an App.cs file) at the end of the ApplicationView_Activated(CoreApplicationView sender, IActivatedEventArgs args)​ function.

    .NET + XAML: Put the following source in your App class (usually it is an App.xaml.cs file) at the end of the OnLaunched(LaunchActivatedEventArgs args) and OnActivated(IActivatedEventArgs args) functions.

    IL2CPP + XAML: Put the following source in your App class (usually it is App.xaml.cpp file). Add several lines of code in a generated App.xaml.cpp class. After defining headers:

    And at the end of of the App::OnLaunched(LaunchActivatedEventArgs^ e) and App::OnActivated(IActivatedEventArgs^ args) functions.

    For Example:

    IL2CPP + D3D: Put the following source in your App class (usually it is App.cpp file). Add several lines of code in a generated App.cpp class. After defining headers:

    And at the end of of the App::OnActivated(CoreApplicationView^ sender, IActivatedEventArgs^ args) function.

    Make sure that these functions are enabled in Package.appmanifest of you project (the flag "Toast capable" is enabled by default for Windows 10+ projects, it is absent in the manifest).

    Add the following three Background Tasks in Package.appmanifest:

    Entry point for Push Notification tasks type:

    Entry points for System Event tasks type:

    You must also associate your application with the Windows Store app (otherwise push notifications will not be delivered). Open "Store->Associate App with the Store" menu, login with your Live ID and pick the appropriate application form the list. A file Package.StoreAssociation.xml will be added into the Project.

    The specificity of integration on iOS platform

    1. Build an iOS App in Unity. After the app is built, Xcode project will be created. Proceed with the following changes

    2. Enable push notifications in your Xcode project

    3. The library provides support for iOS 10 notification attachments, such as images, animated gifs, and video. In order to take advantage of this functionality, you will need to create a notification service extension alongside your main application

    4. Create a new iOS target in Xcode (File -> New -> Target) and select the Notification Service Extension type

    5. ​In Member Center, the Push Notifications service will appear as Configurable (not Enabled) until you create a client SSL certificate

    Add devtodevAppExtensions.framework to newly created extension. Make sure that Deployment Target is pointed as iOS 10.0 or higher:

    Make sure that field Architectures contains "Standard architectures armv7, arm64" setting both in the project and the extension build settings:

    Modify your extension:

    1. Delete all dummy source code for your new extension

    2. Inherit from DTDMediaAttachmentExtension in NotificationService

    Adding Capabilities

    Use Xcode to enable push notifications in the target’s Capabilities pane:

    Enable Background Modes and Remote notifications under the target’s Capabilities section:

    Two ways to integrate push notifications:

    • Using the graphic interface:

      1. Open the Window/devtodev menu element

      2. Switch Push Notifications tumbler on

      3. If you need to use push token for some aims or to handle the getting of notifications by user, add the GameObject with the following function to the scene:

      4. Set the target game object, needed script and functions in the interface:

    • Using code: Before calling Analytics.Initialize add the following strings:

    Creating a new push-notification in devtodev interface

    1. Open PUSH NOTIFICATIONS section and click on the "Add new campaign" button

    2. Fill in the campaign name, select an app for delivery 3. Choose a user group to send a message. You can choose an existing segment or create a new one 4. Enter notification details 5. Schedule the delivery 6. That's it!

    You can create a campaign only after at least one push token comes from devtodev SDK integrated into your application. Otherwise, the app will not be displayed in the list.

    current version can be found here
    Push Notifications

    Check out this guide to A/B testing essentials and strategies!

    A/B testing overview

    Push API

    To use Push API you need to have individual User API token, which can be found in the settings of space.

    You’ll see the block with User API token on the space settings page only if your tariff plan and access rights allow to use devtodev API. You can reset User API token or create it again on the same page.

    Please note, if you use several spaces, in every space user has an individual User API token.

    In order to display the notifications on the device, an SDK integration is required.

    Request format

    The request of assignment should be sent to:

    Where

    • user_token – individual User API token of a user. It could be sent with both GET and POST methods.

    • v1 – the current version of API.

    Request content is sent with POST method in JSON format.

    The body of a request can contain the following properties:

    Property
    Type
    Description

    An example of a request for sending a simple notification to an iOS device:

    POST

    Response format

    If a request is formed correctly and there are no obstacles for sending a notification to users, the answer is a JSON of the following type:

    where

    • audience (number) – the number of users found.

    • successful (number) – the number of successfully sent notifications.

    • erroneous (number) – the number of notifications rejected by the delivery service.

    In case there is an error in a request, the answer is made in the following format:

    where

    • status_code (number) – a general status of an error.

    • errors (array) – an array of error descriptions.

    • code (number) – the exact code of an error from the table of errors.

    The list of possible errors is given in a table below.

    List of possible errors

    Status code
    Code
    Value of "msg" field
    Error description

    Besides the errors listed above, error_details field may contain errors from the connected notification services. You can find descriptions of such errors in documentation for these services:

    Unity

    Check out to streamline the process.

    SDK Integration

    iOS

    iOS Integration

    Platform integration

    Creating a Universal Push Notification Client SSL Certificate

    You use Member Center to generate a push notification client SSL certificate that allows your notification server to connect to the APNs. Each App ID is required to have its own client SSL certificate. The client SSL certificate Member Center generates is a universal certificate that allows your app to connect to both the development and production environments.

    Data Export to Cloud Storage (BigQuery / Amazon S3)

    Cloud export is available for .

    devtodev has an option to export user and event data to a cloud storage. The event data is uploaded once every hour. User data is uploaded every day, if we receive at least one event for the last 24 hours.

    To export your data to one of the supported cloud storages, please send a request to .

    Remote configuration SDK integration

    How to configure remote configuration in devtodev interface:

    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:

    Android

    User identifiers

    In order to find a user to whom you need to send a notification, it is possible to specify one or several available identifiers:

    <service android: name = "com.google.firebase.messaging.cpp.ListenerService"
    android: exported = "true"
    android: enabled = "false"
    tools: node = "replace" />
    <?xml version="1.0" encoding="utf-8"?>
    <manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" package="com.devtodev.unitysdk2" android:versionCode="1" android:versionName="1.0">
      <application android:label="@string/app_name" android:icon="@drawable/app_icon">
        <service android:name="com.google.firebase.messaging.cpp.ListenerService" android:exported="true" android:enabled="false" tools:node="replace" />
        <!-- The MessagingUnityPlayerActivity is a class that extends
             UnityPlayerActivity to work around a known issue when receiving
             notification data payloads in the background. -->
        <activity android:name="com.google.firebase.MessagingUnityPlayerActivity" android:configChanges="fontScale|keyboard|keyboardHidden|locale|mnc|mcc|navigation|orientation|screenLayout|screenSize|smallestScreenSize|uiMode|touchscreen">
          <intent-filter>
            <action android:name="android.intent.action.MAIN" />
            <category android:name="android.intent.category.LAUNCHER" />
          </intent-filter>
          <meta-data android:name="unityplayer.UnityActivity" android:value="true" />
        </activity>
        <service android:name="com.google.firebase.messaging.MessageForwardingService" android:exported="true" />
      </application>
    </manifest>
    private void ApplicationView_Activated(CoreApplicationView sender, IActivatedEventArgs args) {
        //...other code
        DevToDev.ActivatedEventHandler.Handle(args);
    }
    protected override void OnLaunched(LaunchActivatedEventArgs e) {
        //...other code
        DevToDev.ActivatedEventHandler.Handle(e);
    }
    
    protected override void OnActivated(IActivatedEventArgs args) {
        //...other source
        DevToDev.ActivatedEventHandler.Handle(args);
    }
    //...headers
    extern "C" __declspec(dllimport) void __stdcall AddActivatedEventArgs(IInspectable* activatedEventArgs);
    void App::OnActivated(IActivatedEventArgs^ args) {
        //...other code
        AddActivatedEventArgs(reinterpret_cast<IInspectable*>(static_cast<Platform::Object^>(args)));
    }
    
    void App::OnLaunched(LaunchActivatedEventArgs^ e) {
        //...other code
        auto args = static_cast<IActivatedEventArgs^>(e);
    	AddActivatedEventArgs(reinterpret_cast<IInspectable*>(static_cast<Platform::Object^>(args)));
    }
    //...headers
    extern "C" __declspec(dllimport) void __stdcall AddActivatedEventArgs(IInspectable* activatedEventArgs);
    void App::OnActivated(CoreApplicationView^ sender, IActivatedEventArgs^ args) {
        //...other code
        AddActivatedEventArgs(reinterpret_cast<IInspectable*>(static_cast<Platform::Object^>(args)));
    }
    devtodev.background.PushNotificationTriggerTask
    devtodev.background.ToastNotificationActionTriggerTask
    devtodev.background.ToastNotificationHistoryChangedTriggerTask
    //NotificationService.h
    #import <devtodevAppExtensions/devtodevAppExtensions.h>
    
    @interface NotificationService : DTDMediaAttachmentExtension
    
    @end
    
    //NotificationService.m
    #import "NotificationService.h"
    
    @interface NotificationService ()
    
    @end
    public void PushReceived(IDictionary<string, string> pushAdditionalData) {
         //pushAdditionalData - push-notification data that you send to your app
    }
    
    public void PushOpened(DevToDev.PushMessage pushMessage, DevToDev.ActionButton actionButton) {
         //pushMessage - DevToDev.PushMessage. Represents toast notification message
         //actionButton - DevToDev.ActionButton. Represents toast button that was clicked.
         //               Could be null if toast body was clicked
    }
    
    public void PushTokenFailed(string error) {
         //handle push-notifications error here
    }
    
    public void PushTokenReceived(string pushToken) {
         //pushToken - your push token
    }
    
    DevToDev.PushManager.PushReceived = PushReceived;
    DevToDev.PushManager.PushOpened = PushOpened;
    DevToDev.PushManager.PushTokenFailed = PushTokenFailed;
    DevToDev.PushManager.PushTokenReceived = PushTokenReceived;
    
    DevToDev.PushManager.PushNotificationsOptions = (DTDNotificationOptions.Alert | DTDNotificationOptions.Badge | DTDNotificationOptions.Sound | DTDNotificationOptions.Provisional); //Notification options for iOS, optional property
    DevToDev.PushManager.PushNotificationsEnabled = true; 
    
    // FOR ANDROID ONLY! Optional. Using custom push-notification icons on Android.
    // <summary> To set the custom icons to be shown in push-notification on the SDK part,
    // use the following methods.
    // Attention! Icons which set in the push-notification wizard 
    // have priority over the icons which set in these methods.</summary>
    // <param name="iconName">Icon file from resources of your app
    // (from Assets/Plugins/Android/res folder)</param>
    
    //FOR ANDROID ONLY! To set the small icon:
    DevToDev.PushManager.CustomSmallIcon = iconName;
    
    //FOR ANDROID ONLY! To set the large icon: 
    DevToDev.PushManager.CustomLargeIcon = iconName;

    string

    Optional. The unique identifier of a request. It is specified by a developer. It is used for the filtering of repeated sendings of identical requests in case of the loss of connection, etc. Requests with the same identifier can't be repeated during 10 minutes after their sending.

    audience

    array

    Required. An array of mailing audience. The element of an array is an object with an individual for every platform set of user identifiers. It is allowed to specify several known identifiers for one user. The maximum number of elements of an array is 1000.

    The lists of available identifiers can be found in the description to each platform.

    ios

    object

    An object containing a notification and its properties for the

    android

    object

    An object containing a notification and its properties for the .

    win

    object

    An object containing a notification and its properties for the . Use this object for any version of Windows (Windows Phone 8.1, Windows Phone 10, Windows 8.1, Windows 10).

    uwp

    object

    An object containing a notification and its properties for the 10 / WP 10. Don’t use this object for sending to other versions of Windows.

    error_details (array) – a detailed description of reasons of a delivery fail.

    msg (string) – a brief description of an error.

    Malformed json

    JSON error in the body of the request. Fix the formatting error.

    400

    4

    Field not found: %field_name%

    An obligatory field can not be found. You need to complete the request with this field.

    400

    6

    Invalid app id %app id value%

    The requested project can not be found. An unknown application. This error can arise when a user makes a mistake with an app ID or when an application with this ID has been removed.

    401

    11

    Authorization error. Wrong user token %user_token value%

    Authorization error. The set token is wrong. User_token field. User API token.

    401

    12

    Authorization error. User_token is not set.

    Authorization error. User_token field is absent. User API token should be set either as a parameter in GET string of the request or in POST body of the request.

    403

    13

    Access denied. You have no access to the app %app id value%

    Access error. User has no access to this application.

    403

    14

    Access denied. You have no access to the report file %file id value%

    Access error. User has no access to this file. This error can arise if you have to access the application used in a previously created request.

    403

    15

    Access denied. You have no access to API.

    Access error. No access to User API token. This error can arise when access rights have been changed (as a consequence of changing a user role or tariff plan).

    403

    23

    Access denied. You have no access to Push API.

    Access error. This error can arise when you have no rights to Push API for your user role or tariff plan.

    400

    24

    Push service is not enabled in an application settings.

    Push service is disabled. Enable the service on the of an app.

    400

    25

    The devtodev SDK is not integrated with the application you specified

    The SDK is not integrated.

    400

    26

    No push token has been received from the devtodev SDK integrated with the app you specified. Please make sure the SDK is integrated correctly.

    No push token has been received from the devtodev SDK integrated with the app you specified. Please make sure the SDK is integrated correctly.

    400

    27

    The audience array should not contain more than 1000 elements.

    The audience array contains more than 1000 elements. Reduce the number of users in one request.

    400

    28

    Unexpected value for field %field%. Received value: %value%. Expected values: %values%.

    There is an unexpected value for the field. Use the recommendation and correct the request.

    400

    29

    Unexpected field %field%

    The request contains the field not specified in the documentation. The field must be excluded from the request.

    400

    30

    Invalid value for field %field%. Received value: %value%. Expected: %description%

    Invalid value has been attributed to the field. Use the recommendation and correct the request.

    400

    31

    Notification payload size limit exceeded.

    Reduce the payload size.

    400

    32

    The sending was blocked. The request with the %pack_id% identifier has already been sent during previous 10 minutes.

    Requests with the same identifier can't be repeated during 10 minutes after their sending.

    400

    33

    The requested users have not been found, or there have been no push-token received from them.

    The requested users have not been found, or there have been no push-token received from them.

    user_token

    string

    Required. An individual user API token. It can be found on the space settings page. It is possible to send it with both POST and GET methods.

    app_id

    string

    Required. An application identifier. It can be found in the application’s settings in the Integration section. Required.

    campaign_tag

    string

    Optional. The name of a campaign. The grouping of statistics for the assessment of the efficiency of a campaign is made by the name of a campaign. The API Stats report can be found in the Push section.

    500

    1

    Unknown Error

    Unknown error. Please contact devtodev technical support.

    400

    2

    Request body is empty

    The empty body of the request. There is no POST data in the request.

    400

    Push notifications
    Android (Firebase)
    iOS (APNS)
    Windows (WNS)

    pack_id

    3

    If you have previously used the Unity SDK version 2+, you need to delete the following files and folders in the "Assets" folder of your project:

    To integrate devtodev analytics SDK, you can use one of the two methods: by using the Unity Package Manager (recommended) or by manually importing the unitypackage.

    Integration by using the Unity Package Manager

    If you integrated the devtodev package manually, then you need to delete the Assets/DevToDev and Plugins/DevToDev folders.

    1. Open the Package Manager (Window → Package Manager), click + in the top left corner and select Add package from git URL.

    2. Copy the repository URL https://github.com/devtodev-analytics/package_Analytics.git to the input box and click Add.

    3. Wait for the Unity Package Manager to download the package.

    4. For Android projects, add an identification package:

    If you work with SDK version 3.5.0 and above, and you want to use Google Ad ID, you need to add devtodev-analytics/package_Google. When developing and publishing apps for kids (COPPA), you do not need devtodev-analytics/package_Google. Read more about working with COPPA in the COPPA section.

    If you work with SDK version 3.5.0 and above, and you want to use Huawei Ad ID, you need to add . When developing and publishing apps for kids , you do not need . Read more about working with in the .

    You can pick a specific SDK version by adding # and a version number at the end of the URL, for example: https://github.com/devtodev-analytics/package_Analytics.git#v3.3.2

    Integration by importing unitypackage

    1. Download DTDAnalytics.unitypackage from http://github.com/devtodev-analytics/Unity-sdk-3.0/releases/latest.

    2. In the Unity Editor menu, open Assets → Import Package → Custom Package.

    3. Select the DTDAnalytics.unitypackage that you have just downloaded.

    4. Click Import.

    5. For Android projects, import an identification package:

    If you work with SDK version 3.5.0 and above, and you want to use Google Ad ID, you need to import the DTDGoogle.unitypackage. When developing and publishing apps for kids (COPPA), you do not need the DTDGoogle.unitypackage. Read more about working with COPPA in the COPPA section.

    If you work with SDK version 3.5.0 and above, and you want to use Huawei Ad ID, you need to import the DTDHuawei.unitypackage. When developing and publishing apps for kids , you do not need the DTDHuawei.unitypackage. Read more about working with in the .

    SDK Initialization

    Create a script with the following code and attach it to the GameObject that will survive the entire life cycle of the app.

    You can find the AppID in the settings of the respective app in devtodev (Settings → SDK → Integration → Credentials).

    config - an object instance of DTDAnalyticsConfiguration, which is used for specifying additional properties during the initialization.

    DTDAnalyticsConfiguration

    Parameter

    Type

    Description

    CurrentLevel

    Integer

    The player level at the moment of devtodev SDK initialization. It’s optional but we recommend using it for improving data accuracy.

    UserId

    String

    A custom user ID assigned by the developer. In the case of default calculation by device IDs, the identifier can be used for searching users in devtodev. In case the project uses calculation by user IDs, the parameter is mandatory because it becomes the principal calculation ID in devtodev.

    TrackingAvailability

    DTDTrackingStatus (enum)

    The property allows or disallows devtodev tracking of the user. By default, it is set to DTDTrackingStatus.Enable. SDK stores the previously assigned value. Pass DTDTrackingStatus.Disable if the user opted out of tracking in line with GDPR.

    LogLevel

    Example:

    Specific integration features of certain platforms

    Windows Standalone

    SDK Activity

    The SDK can’t control app activity in case you use Windows Standalone therefore this responsibility is shifted to the developer. While initializing the SDK, the activity starts automatically and after that, the activity status will not auto-change. To track app activity, the developer can use the following methods: DTDAnalytics.StartActivity and DTDAnalytics.StopActivity. It is recommended to use the DTDAnalytics.StopActivity method to stop activity when the app goes into the background or gets closed. You can use the DTDAnalytics.StartActivity method to resume activity when the app gets reopened from the taskbar.

    For other platforms, there is no need to manually call the DTDAnalytics.StartActivity and DTDAnalytics.StopActivity methods.

    Universal Windows Platform (WSA)

    In the version 2019.2, the Executable Only option was added to the Build Type. This option is incompatible with the SDK because the SDK needs access to the project file in order to register the DevToDev.Background service.

    Android

    To resolve external android dependencies, you need to use External Dependency Manager for Unity.

    Add the following strings to proguard.txt (read more about Unity proguard here):

    Huawei

    1. Select your project in https://developer.huawei.com/consumer/en/service/josp/agc/index.html#/myProject In the “General information” section find “App information” and download the “agconnect-services.json“ file.

    2. If you imported the DTDGoogle package, delete the imported files Assets\Plugins\DevToDev\Android\DTDGoogleAndroid.dll and Assets\DevToDev\Analytics\Editor\GoogleDependencies.xml

    3. Import the DTDHuawei.unitypackage manually from GitHub or use Unity Package Manager with .

    4. In the assets/Plugins/Android/ folder create a settingsTemplate.gradle file with the following content:

    5. Open Window → devtodev and select Create android plugin folder

    6. Copy the “agconnect-services.json“ file to the Assets\Plugins\Android\devtodev.plugin folder

    7. Open Assets → External Dependency Manager → Android ResolverAssets → External Dependency Manager → Android Resolver and click Resolve

    8. To add the following rule:

    iOS

    To integrate with Xcode, the SDK uses PostProcessBuild in the DTDPostProcessAnalytics and DTDPostProcessMessaging scripts. If you use custom PostProcessBuild scripts, add them callbackOrder of less than 98 to avoid conflicts.

    Your app must request tracking authorization before it can get the advertising identifier. See the detailed instruction on how to request it.

    If you want to disable tracking of advertising identifiers, you need to open the DTDPostProcesssAnalytics.cs file and comment out the line 39: project.AddFrameworkToProject(targetGuid, "AppTrackingTransparency.framework", true);

    iOS SDK Signature and Privacy Manifest

    At WWDC23 Apple introduced new privacy manifests and xcframework signature. More information about it can be found here.

    Apps targeted at children

    When developing and publishing apps targeted at children under 13 years old, you need to ensure special conditions for data processing. Any mobile app aimed at children or intended for users in a region with strict regulations on child online protection, must comply with current laws.

    Please study the following requirements:

    • USA: Children’s Online Privacy Protection Act (COPPA)

    • EU: General Data Protection Regulation (GDPR) Article 8

    If your app has to comply with the legal requirements (COPPA), use the following recommendations:

    • Implement the CoppaControlEnable method. The method disables collection of ad IDs and vendor IDs (IDFA, IDFV).

    • To comply with Apple’s guidelines, remove from Xcode project:

      1. AppTrackingTransparency.framework and all the links pointing to it.

      2. AdSupport.framework and all the links pointing to it.

      3. Depending on the SDK version:

        1. 3.9.0 and older: Set IS_COPPA_ENABLED = true in DTDPostProcessAnalytics.cs (Assets/DevToDev/Analytics/Editor)

        2. 3.9.1 and newer: Add a new : DTD_COPPA

    1. Implement the CoppaControlEnable method. The method disables collection of ad IDs and vendor IDs.

    2. If you are using DTDGoogle or DTDHuawei from Unity Package Manager, disable it.

    3. If you are using DTDGoogle.unitypackage, remove the following files:

    Call the CoppaControlEnable method before SDK initialization. If the method was not called, the SDK will work as before.

    AI-assisted integration

    Only a team agent or admin can generate Apple Push Notification service SSL certificates.

    To generate a universal client SSL certificate

    1. In Certificates, Identifiers & Profiles, select Certificates.

    2. Click the Add button (+)

    3. Under Production, select the “Apple Push Notification service SSL (Sandbox & Production)” checkbox, and click Continue.

    4. Choose an App ID from the App ID pop-up menu, and click Continue. Choose the explicit App ID that matches your bundle ID.

    5. Create a certificate request on your Mac.

    6. Click Choose File.

    7. In the dialog that appears, select the certificate request file (with a .certSigningRequest extension), and click Choose File.

    8. Click Generate.

    9. Click Download.

    Follow these steps to export the certificate from Apple web-site to the P12-file:

    1. Open "Keychain access" application

    2. If the certificate hasn't been added to keychain access yet, choose "File" → "Import". Find the certificate file (CER-file) provided by Apple

    3. Choose "Keys" section in "Keychain access" application

    4. Choose a personal key associated with your iPhone developer certificate. Personal key is identified by open certificate associated with it "iPhone developer: ". Choose "File" → Export objects. Save key as .p12

    5. You'll be suggested to create a password which is used when you need to import the key to another computer

    Upload the certificate to the site

    Upload the .p12-file into Integration section of application settings panel (Settings -> Push Notifications):

    Module initialization

    1. For the Messaging module to function you need the basic Analytics package. Before the notification initialization, you need to initialize the SDK. More about it you can read here: Unity Integration.

    2. After the DTDAnalytics initialization block call the StartNotificationService method to activate the Messaging module:

    Optional:

    You can listen to basic notification module events. To do this, create a class that implements the IDTDPushListener interface and pass it to the DTDMessaging.IOS.SetPushListener method.

    Example:

    You can specify the necessary display options using the DTDMessaging.IOS.SetNotificationOptions method.

    Example:

    A complete example of the notification module initialization:

    External interface of the DTDMessaging module for iOS platform

    Methodes
    Description

    void DTDMessaging.IOS.StartPushService()

    The method responsible for push notification activation:

    • Requests user permission to receive push notifications

    • Sends push token and current state isAllowed

    void DTDMessaging.IOS.GetToken(Action<string> onGetToken)

    The method that returns current push token to onGetToken callback

    DTDMessaging.IOS.SetPushListener(IDTDPushListener listener)

    The method for assigning a push notification event listener

    void DTDMessaging.IOS.PushNotificationsOptions(uint options)

    options is responsible for setting up the display of Push Notifications. It is set by the developer to select the method of notifying the user. It can be changed by the end-user.

    By default, it has the value:

    [.DTDNotificationOptionBadge, .DTDNotificationOptionSound, .DTDNotificationOptionAlert]

    void DTDMessaging.IOS.PushIsAllowed (bool isAllowed )

    The method is responsible for enabling / disabling the ability to send a push notification to the user from devtodev.

    IDTDPushListener interface

    Delegate method
    Description

    void OnPushServiceRegistrationSuccessful (string deviceId);

    The method is called if a push token is successfully received, represented by a string.

    void OnPushServiceRegistrationFailed (string error);

    The method is called if at the time of receiving a pushToken errors occur. It passes the text of the error that occurred

    void OnInvisibleNotificationReceived (DTDPushMessage message);

    The method is called when an invisible remote push notification is received. The DTDPushMessage object is passed

    void OnPushNotificationReceived (DTDPushMessage message);

    The method is called when a remote push notification is received while the application runs in the Foreground state. The DTDPushMessage object is passed.

    void OnPushNotificationOpened (DTDPushMessage pushMessage, DTDActionButton actionButton);

    The method is called when the end-user opens a remote push notification. The DTDPushMessage object and the optional DTDActionButton object are passed.

    Class for receiving Notification data (DTDPushMessage). Main class properties

    Property
    Description

    IDictionary<string,string> GetData():

    Complete information passed with the push notification.

    DTDActionType ActionType:

    The property that returns the value of enum’s DTDActionType.

    Possible values:

    App - app open

    Url - external link open

    Share - share content

    Deeplink - open a link that leads straight to a specific in-app location

    string ActionString

    The property that returns an optional action identifier

    IDictionary<string,string> AdditionalData()

    Additional data sent to push notification

    Class for handling buttons clicked in the notification (DTDActionButton)

    Property
    Description

    DTDActionType ActionType

    The property that returns the value of enum’s DTDActionType.

    Possible values:

    App - app open

    Url - external link open

    Share - share content

    Deeplink - open a link that leads straight to a specific in-app location

    string ActionString

    The property that returns an optional action identifier

    string ButtonId

    The property that returns the ID of the clicked button

    string ButtonText

    The property that returns the text of the clicked button

    DTDNotificationOptions

    It is an OptionSet used for push notification authorization and configuration interaction with users.

    Attention: The user can change the allowed parameters in the notification settings at any time.

    Possible values:

    • DTDNotificationOptionBadge - an option for displaying a badge on the application icon

    • DTDNotificationOptionSound - an option for playing sound

    • DTDNotificationOptionAlert - an option for displaying an alert

    • DTDNotificationOptionCarPlay - an option for showing a push notification in CarPlay DTDNotificationOptionCriticalAlert - an option for playing sound for critical alerts regardless of whether “do not disturb” is on or not. (Critical alerts require special permission from Apple). Available from iOS 12.0 onwards

    • DTDNotificationOptionProvidesSettings - an option for indicating that the system should show a notification settings button in the application. Available from iOS 12.0 onwards

    • DTDNotificationOptionProvisional - an option for sending provisional notifications to the Notification Center without interrupting functioning processes. Available from iOS 12.0 onwards

    • DTDNotificationOptionAnnunciation - an option for Siri to automatically read messages through AirPods. Available from iOS 13.0 onwards

    Attention: DTDNotificationOptionProvisional - Provisional push notifications are shown in the User Notification Center but not on the lock screen. This type of push notification doesn’t require an explicit opt-in from the user. You can start sending them as soon as the user installs and launches your app. However, the user can also explicitly enable/disable your notifications.

    Use this setting to avoid the permission request on app launch because it is seen as intrusive, and most users opt-out of it.

    It’s also important that when using the DTDNotificationOptionProvisional setting, the user needs to go to Notification Center settings to allow explicit notifications.

    XCODE build

    After the build, open the signing and capabilities tab in the XCode project settings and add "Push Notifications" and "Background Modes" (tick the box against Remote notifications).

    Notification with Attachments

    The framework provides support for iOS 10+ notification attachments, such as images, animated gifs, and video. In order to take advantage of this functionality, you will need to create a notification service extension alongside your main application. Create a new iOS target in Xcode (File -> New -> Target) and select the Notification Service Extension type. ​In the Member Center, a Push Notifications service will appear as Configurable (not Enabled) until you create a client SSL certificate.

    The framework supports notification attachments: images, audio, and video. To enable the functionality, you need to complete several steps:

    • Create a new iOS target in Xcode (File -> New -> Target) and select the Notification Service Extension

    • Set language to ‘Swift’

    • In the next window, click ‘Activate’

    • In the ‘Build Settings’ tap, change 'Architectures’ to ‘Standard Architecture (arm64, armv7)

    • Open the ‘Frameworks’ folder in your project, find the ‘DTDMessagingUnity’ framework and tick the box of your target in the ‘Target Membership’ section

    • In the NotificationService class (in the folder named after your target), replace the code with the following:

    Custom sounds

    To use custom sounds for push notifications, you need to copy the necessary sound files to the Libraries folder of your Xcode project. You can read more about supported formats here.

    When creating a push company, it is important to specify the full name of the sound with file extension (for example, sound.wav).

    Export to BigQuery

    To export data to BigQuery you will need to:

    1. Create a service account, if it does not already exist.

    2. Get service account credentials.

    3. Create a dataset.

    4. Choose what kind of data you want to export to your dataset.

    In the request specify the following details:

    1. Service account credentials;

    2. Name and location of the dataset in BigQuery;

    3. Export configuration (see below).

    Creating a service account in BigQuery

    1. If you do not have a service account, create one by following the Google Cloud manual.

    2. Your service account has to have rights for table creation and data upload. Add bigquery.user or bigquery.admin role to your service account. Make sure to add these permissions to your role:

      • bigquery.jobs.create

      • bigquery.tables.create

    3. Create credentials for your service account, if there are none yet. Follow to create access keys. To create keys, add serviceAccountKeyAdmin role to your service account.

    Creating a dataset

    Follow this manual to create a dataset in BigQuery.

    Name your dataset devtodev, that way we can send your data to BigQuery.

    Also, while creating a dataset, keep location in mind.

    You cannot change the location of the dataset later! More on locations in BigQuery.

    Export configuration

    After creating a service account and a dataset we need to configure export in devtodev.

    You can choose one of two ways to export your data:

    Export data to one table — all event data will be uploaded to one common table named p<project id>_events.

    Export data by event type — every event type will be uploaded to their respective table. The list of event types is below.

    Every event type will have a table with a name like this p<project id>_events_<event type>[_<event name>]. For example:

    • p234_rp —this is a table for real payment events from a project with id 234.

    • p234_ce_mission_start — this is a table for a custom event named “mission_start“ from a project with id 234.

    You can match project name and project id in the _projects table, which will be automatically filled at the time of the first export.

    Active user information will be uploaded to a separate table named p<project id>_users regardless of how you choose to upload event data.

    Export to Amazon S3

    To export data to Amazon S3 you will need to:

    1. Create an account, if it does not already exist.

    2. Get credentials (accessKey and secretKey).

    3. Create a bucket.

    4. Choose what kind of data you want to export to your bucket.

    In the request specify the following details:

    1. Account credentials;

    2. Name and region of the bucket in Amazon S3;

    3. Export configuration (see below).

    Creating an Amazon S3 account

    If you do not already have an account, follow this AWS manual to create one.

    Getting credentials

    See this manual for more detail on how to find your credentials.

    We need accessKey and secretKey which are located in ~/.aws/credentials file. We will also need your region information, it is located in ~/.aws/config file. Execute aws configure command in AWS developer console to get accessKey and secretKey.

    Example:

    Creating a bucket

    Follow this manual to create a bucket in S3.

    The Name of the bucket should be unique, see more on bucket naming. Also, while creating a dataset, keep the region in mind.

    Objects can never leave the region unless they are explicitly transferred! More on AWS regions.

    Export configuration

    After creating an account and a bucket we need to configure export in devtodev.

    Your data will be stored in a bucket directory named p<project id> which will store .csv files compressed with gzip. Each directory will have a project_info.txt file with the project name and application id in devtodev service.

    You can choose one of two ways to export your data:

    Export data to one table — all event data will be uploaded to one common table.

    Example of such table: 2021_05_26_08_00_54common86ddf8a5-1e7f-4f2c-a4d3-22f4d6a8860c

    Export data by event type — every event type will be uploaded to their respective table. The list of event types is below.

    Some examples:

    • 2021_05_26_08_08_28ce[editor_item_remove]a9413576-0a32-4a2a-ad84-940150e9a218 — this is a table for a custom event named “editor_item_remove“.

    • 2021_05_26_08_08_11rp556dbd8d-71c9-41b4-9564-d43b39ca1b7d — this is a table for real payment events.

    Active user information will be uploaded to a separate users table regardless of how you choose to upload event data.

    Example of such table: 2021_05_26_08_07_52users439c129f-d70b-4f98-ad86-4cb01054732b

    List of event types

    During export configuration you can select what type of events you want to export. You can also select which project should be exported and which should not.

    The list below contains event types (with fields) available for export.

    Common basic fields for all event types

    Event types

    Event name

    Event code

    Additional fields

    EventTrackingStatus

    ts

    allow_tracking — is tracking allowed

    EventUserInfo

    ui

    language — device locale

    custom_udid — custom user id

    EventDeviceInfo

    di

    device_version

    device_os

    display_resolution

    display_dpi

    androidid

    idfa

    idfv

    advertisingid

    serialid

    manufacturer

    model

    device_model

    EventDeviceInfoV2

    Business and Enterprise price plans
    [email protected]
    Change app behavior for all users or just for a specific audience.
  • Conduct A/B tests to compare different configurations on the same audience and find the best performing one.

  • 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.

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

    Integration plan in short

    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.

    3. Integrate a listener method that will notify you if at least one parameter value has changed. Add a reaction logic to this notification based on our proposed strategies.

    4. to activate the remote configs.

    How remote configs work

    1. In the devtodev interface you can change parameter values for a specific audience (Remote Configuration) or create an A/B test (A/B Testing).

    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 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 and triggers for each change source. Using the update notification and list of changes, select one of the strateges to react.

    4. When you receive a notification about parameter changes, you can activate () 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.

    Attention! If the user enters an A/B test, the SDK will mark of their events with an A/B test group regardless whether you have activated the proposed parameter changes. We recommend activating and applying these changes as soon as possible!

    Parameter value priority

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

    Integration

    Setting up default parameter values

    Set the variable (parameter) values in the DTDRemoteConfig class using the DTDRemoteConfig.defaults property.

    The SDK does not change any values in the defaults property.

    After setting DTDRemoteConfig.defaults, you will be able to get parameter values using the DTDRemoteConfig.config property.

    Always use the config property to get up-to-date parameter value configurations.

    Waiting for an A/B test group

    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.

    The default wait time groupDefinitionWaiting is 10 seconds.

    You can reduce or extend the wait time value.

    For example:

    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.

    You can set the DTDRemoteConfig.groupDefinitionWaiting value only before SDK initialization!

    Remote config initialization

    In order to use the remote configs or A/B tests, use the DTDAnalytics.initializeWithRemoteConfig to initialize devtodev SDK.

    Migrating from previous SDK versions

    • If you have previously worked with devtodev A/B tests, 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.

    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.

    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.

    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.

    You must call the applyConfig method to apply and use the test values.

    The following SDK launches will not call the onChanged(update: DTDRemoteConfigUpdate) method automatically.

    Using the onChanged method with remote configuration

    1. devtodev server sends new or updated parameter configuration to the SDK.

    2. The onChanged() method is called.

    Using the onChanged method with A/B test

    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.

    Applying the config

    To accept and activate the remote configuration, call the DTDRemoteConfig.applyConfig() method.

    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.

    To decline a remote config or A/B test participation, call the DTDRemoteConfig.resetConfig() method. After calling this method, the parameter values will be set to defaults.

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

    Strategies for config application

    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 the 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.

    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.

    External interfaces description

    DTDRemoteConfig

    The SDK provides threads synchronization when working with this class.

    Property
    Description

    groupDefinitionWaiting:Double

    Wait time for A/B test configuration.

    Default value - 10.0 (measured in seconds).

    defaults: Map<String, Any>

    Paramateres and their default values.

    config: DTDRemoteConfigCollection

    A collection of current parameters and their values for A/B tests.

    It allows access to the configuration values by using the subscripting syntaxis.

    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.

    cacheTestExperiment()

    A debug method for saving a test experiment after restarting the application.

    DTDRemoteConfigCollection

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

    DTDRemoteConfigValue

    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.

    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.

    DTDRemoteConfigListener

    Implements the method that reports about 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

    Fields
    Description

    val keys: List<DTDRemoteConfigChangeKey>

    Contains a list of updated keys.

    DTDRemoteConfigChangeKey

    The source of the key update.

    Fields
    Description

    val key: String

    val source: DTDRemoteConfigSource

    Contains the key name and update source.

    List of errors

    This 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

    Example 1

    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.

    Example 2

    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.

    Working with remote configuration in devtodev

    Contact us to request access. Use Contact Us form or reach out to our Customer Success team directly within the platform. We will also greatly appreciate your feedback. Please add REMOTE CONFIGS when submitting your request.

    Prerequisite:

    Currently remote configs are available only for SDK version 2.6.0 (Android & iOS) and Unity 3.10.0 and higher.

    If a user enters an A/B test, they will receive parameter values accoring to their test group configuration.

    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).

    The priority order for parameter value source is the following:

    1. Top priority – A/B test parameter value.

    Push token. If you use push token, all fields with other identifiers will be ignored!

    advertisingId

    string

    Advertising ID

    androidId

    string

    Android ID

    serialId

    string

    Serial ID

    userId

    string

    User id is applicable if an internal identifier (cross-platform user identifier) is used in your app.

    devtodevId

    number

    Numeric user identifier in devtodev database.

    Notification settings

    The object of a message for the Android platform contains 2 fields:

    • payload (object) - describes the main content of a notification

    • options (object) - describes the optional settings of notification delivery

    Notification content settings ("payload” object)

    Property

    Type

    Description

    title

    string

    A short string describing the purpose of a notification.

    text

    string

    The text of an alert message.

    data

    object

    Optional if notification is not hidden. You can pass custom parameters with messages and use them within an app. For instance, you can activate advertising campaign or any other functionality for a user who has received this message.

    Example:

    "data": { "my_key": "value", "my_another_key": "15" }

    small_icon

    Notification delivery and display settings ("options" object)

    Property

    Type

    Description

    hidden

    boolean

    Switching the notification to the hidden mode. If “true” - a notification will not be displayed to a user, but will be transferred to an app. Such a message must not contain any properties except data in the “payload” object.

    priority

    string

    Optional. The priority of a notification. Default value is "normal". Specify one of the following values:

    • high" – GCM attempts to deliver high priority messages immediately allowing the GCM service to wake a sleeping device when possible and open a network connection to your app server. Apps with instant messaging, chat, or voice call alerts, for example, generally need to open a network connection and make sure GCM delivers the message to the device without delay. Set high priority only if the message is time-critical and requires the user’s immediate interaction, and beware that setting your messages to high priority contributes to a battery drain more compared to normal priority messages.

    • "normal" — This is the default priority for message delivery. Normal priority messages won't open network connections on a sleeping device, and their delivery may be delayed to preserve a battery. For less time-sensitive messages (such as notifications of new email or other data to sync) choose normal delivery priority.

    expire

    number

    This option identifies the date when a notification is no longer valid and can be discarded. It is possible to use either relative time in seconds passed since the moment of sending, or specify an exact date in UNIX epoch format expressed in seconds (UTC).

    Default value is 7 days (604800 seconds) after sending. Max. relative value for Android platform is 2 419 200 seconds (4 weeks).

    Keep in mind that an "expire" value of 0 means messages that can't be delivered immediately will be discarded. However, as long as such messages are never stored, this provides the best latency for sending notifications.

    collapse_key

    Examples

    Visible push notification

    POST

    Hidden push notification

    POST

    Property

    Type

    Description

    token

    string

    SDK integration

    Abode Air

    Integration of push notifications on Android and iOS using devdodev SDK for Adobe Air

    This generation of SDK is deprecated and is no longer supported.

    Push Notifications are availible only for iOS and Android.

    Android push notifications

    Obtaining configuration file for firebase push notifications

    Go to and then to your project or create a new one.

    Current project:

    New project:

    Save the google-services.json file and add it to your app so that it is placed inside the Assets folder in a ready .apk file. If you use Flash Builder IDE simply copy it in the root folder, Flash Builder will do the rest for you.

    Implementation to app

    1. Update Adobe Air SDK. Pay attention that version of the Adobe Air SDK should be at least 22.0, otherwise FCM classes will not be imported into your project. However, you still can use Analytics, but application will not be able to accept Push Notifications.

    2. Add dependencies to your project. These libraries are located in the same archive with com.devtodev.SDK.ane extension in the dependencies folder. These extensions contain firebase, gps и android-support libraries Java, that are used for sending Push Notifications. If you have already imported firebase-auth, firebase-common, firebase-iid, firebase-messaging, play-services-auth, play-services-base, play-services-basement, support-v4 or they are included in other extensions, you don't need to import them again. In any case, we recommend to use data from the library of a version not lower than 25. Add the following to the manifest file of your app:

    3. Add the following to your application's manifest:

    Changing the application settings in devtodev system

    1. Proceed to Setting -> PUSH NOTIFICATIONS:

    2. Insert the previously received Server API Key to the API Key field in Push notifications section.

    3. If the Server API Key is correct, you will see the following result

    Creating a new push-notification in devtodev interface

    1. Open PUSH NOTIFICATIONS section and click on 'Add new campaign' button

    2. Fill in campaign name, select an app for delivery

    3. Choose user group to send a message. You can choose existing segment or create a new one.

    4. Enter notification details

    You can create a campaign only after at least one push token comes from devtodev SDK integrated to your application. Otherwise the app will not be displayed in the list.

    iOS push-notifications

    To enable Push Notifications, please perform the following actions:

    1. Add the application to your space in devtodev system

    2. Generate Developer or Production Certificate for the application and get Private key file (.p12) on its basis

    3. Submit the data to the application settings in devtodev system

    4. Integrate devtodev SDK to the application (see the “SDK integration” division to learn more about integrating and initializing devtodev SDK)

    Certificate generation

    1. Open "Keychain access" utility (Launchpad → Other) and choose "Request a Certificate From a Certificate Authority" option.

    2. Fill in all requied fields in Certificate Assistant window, set flag an "Saved on disk" item and click "Continue". Save the file.

    3. Log in at iOS Provisioning Portal. Open "App IDs" section, choose your app and click "Edit".

    Convert iPhone developer certificate into a P12 file on Mac OS

    Follow these steps to export the certificate from Apple web-site to the P12-file:

    1. Open "Keychain access" application

    2. If the certificate hasn't been added to keychain access yet, choose "File" → "Import". Find the certificate file (CER-file) provided by Apple

    3. Choose "Keys" section in "Keychain access" application

    4. Choose personal key associated with your iPhone developer certificate. Personal key is identified by open certificate associated with it "iPhone developer: ". Choose "File" → Export objects. Save key as .p12

    Convert iPhone developer certificate into a P12 file on Windows OS

    Convert Apple certificate file to the PEM-file. Start the following command-line operation from bin catalog OpenSSL.

    Convert personal key from Mac OS keychain to the PEM-key:

    Now you are able to create P12-file using PEM-key and iPhone developer certificate:

    If you are using key from Mac OS keychain than choose PEM-version created at previous step.

    Otherwise you can use OpenSSL key for Windows OS.

    Upload the certificate to the site

    Upload the .p12-file into Integration section of application settings panel (Settings -> PUSH NOTIFICATIONS):

    After the certificates has been generated you can start to integrate Push SDK into you app.

    SDK Integration

    1. Add the following to your application's manifest. Don't forget that the minimum supported version is iOS 7:

      Attention! If you use Production Certificate for signing application package, don't forget to change "development" value to "production".

    2. Add the following imports to your source:

    3. Add the push notifications initialization before the DevToDev.init(appKey:String, appSecret:String) method was called:

    Xcode will automatically choose new provisioning profile. If an error occurred during the launch make sure that there is a correct profile set in the Code Signing Identity. You'll be asked to confirm push notifications. An app will request permission only once, if user confirm it - notifications will be accepted otherwise he wont get any push messages from your app. User can change it in device settings.

    Creating a new push notification in devtodev interface

    1. Open PUSH NOTIFICATIONS section and click on "Add new campaign" button.

    2. Fill in campaign name

    You can create a campaign only after at least one push token comes from devtodev SDK integrated to your application. Otherwise the app will not be displayed in the list.

    3. Choose user group to send a message. You can choose existing segment or create a new one.

    4. Enter notification details

    5. Test push notification (or skip this step)

    6. Confirm push gateway

    7. Schedule the delivery

    8. That's it!

    Unreal Engine

    Plugin installation

    The SDK can be found in the devtodev . Download the latest release of the . Unzip the archive and copy the DTDMessaging folder to the Plugins folder of your project.

    If you have a C++ type project, add DTDMessaging to the list of dependency names in the

    Subscription API

    Do not forget to configure subscriptions settings in devtodev to successfully receive data!

    General provisions

    Endpoint:

    ​https://www.devtodev.com/api/v1/push/send?user_token=USER_API_TOKEN
    ​https://devtodev.com/api/v1/push/send
    {
        "user_token": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
        "app_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
        "campaign_tag": "campaign name",
        "pack_id": "uniqueid1234",
        "audience": [{
            "idfa": "XXXXX-XXXXX-XXXXXX-XXXXXX"
        },
        {
            "idfa": "YYYYY-YYYYY-YYYYYY-YYYYYY"
        }],
        "ios": {
            "payload": {
                "text": "Hello world!"
            },
            "options": {
                "priority": "normal",
                "expire": 36000
            }
        }
    }
    {
        "status_code": 200,
        "data": {
            "status": "complete",
            "result": {
                "audience": 100500,
                "successful": 100477,
                "erroneous": 23,
                "error_details": []
            }
        }
    }
    {
        "status_code": 400,
        "errors": [{
            "code": 3,
            "msg": "Error description"
        }]
    }
    using DevToDev.Analytics;
    using UnityEngine;
    
    public class DTDObject : MonoBehaviour
    {
        void Start()
        { 
    #if UNITY_ANDROID
            DTDAnalytics.Initialize("androidAppID");
    #elif UNITY_IOS
            DTDAnalytics.Initialize("IosAppID");
    #elif UNITY_WEBGL
            DTDAnalytics.Initialize("WebAppID");
    #elif UNITY_STANDALONE_WIN
            DTDAnalytics.Initialize("winAppID");
    #elif UNITY_STANDALONE_OSX
            DTDAnalytics.Initialize("OsxAppID");
    #elif UNITY_WSA
            DTDAnalytics.Initialize("UwpAppID");
    #endif
        }
    }
    var config = new DTDAnalyticsConfiguration
    {
        ApplicationVersion = "1.2.3",
        LogLevel = DTDLogLevel.No,
        TrackingAvailability = DTDTrackingStatus.Enable,
        CurrentLevel = 1,
        UserId = "unique_userId"
    };
    using DevToDev.Analytics;
    using UnityEngine;
    
    public class DTDObject : MonoBehaviour
    {
        void Start()
        { 
    #if UNITY_ANDROID
            DTDAnalytics.Initialize("androidAppID", config);
    #elif UNITY_IOS
            DTDAnalytics.Initialize("iOSAppID", config);
    #elif UNITY_WEBGL
            DTDAnalytics.Initialize("WebAppID", config);
    #elif UNITY_STANDALONE_WIN
            DTDAnalytics.Initialize("winAppID", config);
    #elif UNITY_STANDALONE_OSX
            DTDAnalytics.Initialize("OSXAppID", config);
    #elif UNITY_WSA
            DTDAnalytics.Initialize("UwpAppID", config);
    #endif
        }
    }
    -keep class com.devtodev.** { *; }
    -dontwarn com.devtodev.**
    DTDAnalytics.CoppaControlEnable();
    DTDAnalytics.Initialize("App ID", config);
    DTDMessaging.IOS.StartNotificationService();
    public class MyPushListener : IDTDPushListener
    {
        public void OnPushServiceRegistrationSuccessful(string deviceId)
        {
            Debug.Log(deviceId);
        }
    
        public void OnPushServiceRegistrationFailed(string error)
        {
            Debug.Log(error);
    
        }
    
        public void OnPushNotificationReceived(DTDPushMessage message)
        {
            Debug.Log(message);
        }
    
        public void OnInvisibleNotificationReceived(DTDPushMessage message)
        {
            Debug.Log(message);
        }
    
        public void OnPushNotificationOpened(DTDPushMessage pushMessage, DTDActionButton actionButton)
        {
            Debug.Log(pushMessage.ToString());
            Debug.Log(actionButton.ToString());
        }
    }
    DTDMessaging.IOS.SetNotificationOptions(DTDNotificationOptions.Alert | DTDNotificationOptions.Badge | DTDNotificationOptions.Sound);
    public class MyPushListener : IDTDPushListener
    {
        public void OnPushServiceRegistrationSuccessful(string deviceId)
        {
            Debug.Log(deviceId);
        }
    
        public void OnPushServiceRegistrationFailed(string error)
        {
            Debug.Log(error);
    
        }
    
        public void OnPushNotificationReceived(DTDPushMessage message)
        {
            Debug.Log(message);
        }
    
        public void OnInvisibleNotificationReceived(DTDPushMessage message)
        {
            Debug.Log(message);
        }
    
        public void OnPushNotificationOpened(DTDPushMessage pushMessage, DTDActionButton actionButton)
        {
            Debug.Log(pushMessage.ToString());
            Debug.Log(actionButton.ToString());
        }
    }
    
    public class NotificationExample : MonoBehaviour
    {
        private const string APP_KEY = "***************"
        void Start()
        {
            DTDAnalytics.Initialize(APP_KEY);
            DTDMessaging.IOS.SetNotificationOptions(DTDNotificationOptions.Alert | DTDNotificationOptions.Badge | DTDNotificationOptions.Sound);
            DTDMessaging.IOS.SetPushListener(new PushListener());
            DTDMessaging.IOS.StartNotificationService();
        }
    }
    import UserNotifications
    import DTDMessagingUnity
    
    class NotificationService: DTDMediaAttachmentExtension {
    
    }
    $ aws configure
    AWS Access Key ID [None]: AKIAIOSFODNN7EXAMPLE
    AWS Secret Access Key [None]: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
    Default region name [None]: us-west-2
    Default output format [None]: json
    devtodev_id — numeric user id
    main_id — string user id
    crossplatform_id — cross platform user id, only for projects with set user id identification
    uc_createtime — user registration date
    uc_first_paymenttime — first payment date
    uc_last_paymenttime — last payment date
    uc_payment_cnt — number of payments
    uc_payment_sum — sum of payments
    uc_level — user level
    uc_country — country
    uc_language — language
    uc_age — age
    uc_gender — gender
    uc_cheater — mark a cheater
    uc_tester — mark a tester
    // 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(
        this.appKey, 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")
            }
        }
    )
     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());
            }
        }
    // 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(
        this.appKey, 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")
    
    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());
        }
    }
     DTDRemoteConfig.groupDefinitionWaiting = 2
    ​https://devtodev.com/api/v1/push/send
    {
        "user_token": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
        "app_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
        "campaign_tag": "campaign name",
        "pack_id": "uniqueid1234",
        "audience": [{
            "token": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
            "advertisingId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
            "androidId": "xxxxxxxxxxxxxxxxxxxxxxxxx",
            "userId": "xxxxxxxxxxxx"
        }],
        "android": {
            "payload": {
                "title": "Title of the notification",
                "text": "Notification content.",
                "data": {
                    "key1": "value",
                    "key2": "15"
                },
                "small_icon": "smallicon",
                "icon": "midicon",
                "image": "https://domain.com/pic.png",
                "sound": "bingbong",
                "vibration": true,
                "led_color": "#ff0000",
                "color": "#ff0000",
                "action": {
                    "url": "http://www.domain.com"
                },
                "interactive": {
                    "buttons": [{
                        "id": "accept",
                        "text": "Accept",
                        "handling": "foreground",
                        "action": {
                            "url": "http://www.domain.com/accept"
                        }
                    }, {
                        "id": "decline",
                        "text": "Decline",
                        "handling": "background"
                    }]
                }
            },
            "options": {
                "hidden": false,
                "priority": "normal",
                "expire": 36000,
                "collapse_key": "collapse1"
            }
        }
    }
    ​https://devtodev.com/api/v1/push/send
    {
        "user_token": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
        "app_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
        "campaign_tag": "campaign name",
        "pack_id": "uniqueid1234",
        "audience": [{
            "token": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
            "advertisingId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
            "androidId": "xxxxxxxxxxxxxxxxxxxxxxxxx",
            "userId": "xxxxxxxxxxxx"
        }],
        "android": {
            "payload": {
                "data": {
                    "key1": "value",
                    "key2": "15"
                }
            },
            "options": {
                "hidden": true,
                "priority": "normal",
                "expire": 36000
            }
        }
    }
    public void PushReceived(IDictionary<string, string> pushAdditionalData) {
         //pushAdditionalData - push-notification data that you send to your app
    }
    
    public void PushOpened(DevToDev.PushMessage pushMessage, DevToDev.ActionButton actionButton) {
         //pushMessage - DevToDev.PushMessage. Represents toast notification message
         //actionButton - DevToDev.ActionButton. Represents toast button that was clicked. Could be null if toast body was clicked
    }
    
    public void PushTokenFailed(string error) {
         //handle push-notifications error here
    }
    
    public void PushTokenReceived(string pushToken) {
         //pushToken - your push token
    }

    string

    Optional. You may use an icon from resources of your app. You should use resource name of the icon in this field.

    color

    string

    Optional. The parameter recolors the small icon of an app displayed in a notification in the specified color ("#RRGGBB"). This N parameter on Android also recolors the name of an app and texts on notification buttons.

    Example:

    "color": "#ff0000"

    icon

    string

    Optional. Use the URL to a notification icon or a resource name.

    image

    string

    Optional. The value is image URL. Images should be ≤ 450dp wide, ~2:1 aspect. The image will be center cropped.

    sound

    string

    Optional. A media file to play in place of a default sound. If a sound file doesn’t exist or default is specified as the value, the default notification sound is played. The audio must be in one of the audio data formats that are compatible with system sounds. Supports “default” or the filename of a sound resource bundled in an app. Android sound files must reside in /res/raw/ If the option is not used, a notification comes silently.

    badge

    int

    Optional. This property was added in Android O. The value of the badge on the home screen app icon. If not specified, the badge is not changed. If set to 0, the badge is removed.

    vibration

    boolean

    Optional. In case of "true" during notification delivery a device will vibrate (if permission VIBRATION is received)

    led_color

    string

    Optional. LED hex color ("#RRGGBB"), a device will do its best approximation.

    Example:

    "led_color": "#ff0000"

    action

    object

    Optional. The action after a click on the body of a notification. By default, a click opens an app. It is also possible to perform the following actions:

    • deeplink (string) - Direct a user to a specific resource either within your app or on the web.

    • url (string) - Open a web page in a mobile browser, or any valid device-level URL such as Google Play or app protocol links.

    • share (string) - The Share Action drives a user to share your message when they interact with your push notification.

    Examples:

    "action": { "url": "http://www.domain.com" } "action": { "deeplink": "your-url-scheme://host/path" } "action": { "share": "Happy holidays!" }

    interactive

    object

    Optional. It is possible to specify an array of notification buttons in an “interactive” object. Each button should contain the following properties:

    • id (string) - The button identifier. It is transferred to an app.

    • text (string) - A text on a button

    • handling (string) - The type of processing

      • background - The button closes a notification. The parameters of a notification are transferred to an app, but an app does not open. It is impossible to tie an action to a button in this regime.

      • foreground - The button opens an app. The parameters of a notification are transferred to an app, but an app does not open. It is possible to tie an action to a button in this regime. The list of actions and the principle is analogical to actions with the body of a message.

    Example:

    "interactive": { "buttons": [{ "id": "accept", "text": "Accept", "handling":"foreground", "action": { "url": "http://www.domain.com/accept" }, { "id": "decline", "text": "Decline", "handling":"background" }] }

    string

    Optional. Notifications are not collapsible by default.

    If multiple messages are sent with this key, the most recent message will suppress all previous unread messages with the same key.

    Collapsible messages are a better choice from a performance standpoint provided your application doesn't need to use non-collapsible messages. However, if you use collapsible messages, remember that GCM only allows a maximum of 4 different collapse keys to be used by the GCM connection server per registration token at any given time. You must not exceed this number, or it could cause unpredictable consequences.

    channel_id

    string

    Optional. The notification's channel id (this property was added in Android O). The app must create a channel with this ID before any notification with this key is received. If you don't send this key in the request, or if the channel id provided has not yet been created by your app, devtodev SDK uses the channel id specified by default ("channel_id": "_devtodev" - name: General notifications).

    badge_icon_type

    int

    Optional. This property was added in Android O (API level 26). 0 - Default. If this notification is being shown as a badge, always show as a number. 1 - If this notification is being shown as a badge, use the getSmallIcon() to represent this notification. 2 - If this notification is being shown as a badge, use the getLargeIcon() to represent this notification.

    offset — user timezone offset

    di

    device_version

    device_os

    display_resolution

    display_dpi

    display_diagonal

    manufacturer

    model

    offset — user timezone offset

    androidid

    openudid

    idfa

    idfv

    advertisingid

    serialid

    install_source

    user_agent

    EventRealPaymentEntry

    rp

    currency

    product

    payment_id

    price_usd

    payment_status

    EventGamePurchase

    ip

    amount

    item_type

    item

    inapp_currencies — structure with info on currency type and its amount spent on item purchase

    EventCustomColumnar

    ce

    event_name

    event_params — structure with parameter names and values

    EventProgression

    pe

    location

    spent

    earned

    source

    difficulty

    success

    duration

    EventTester

    tstr

    tester

    EventCheater

    ch

    cheater

    EventRegistrations

    rg

    this event only has basic fields

    EventGender

    gr

    gender

    EventAge

    ag

    age

    EventGameSessionStart

    ss

    amount

    EventUserEngagement

    ue

    duration

    EventPeople

    pl

    name

    email

    phone

    photo

    event_params — other custom fields

    EventSocialNetworkPost

    sp

    network reason

    EventSocialNetworkConnect

    sc

    network

    EventTutorial

    tr

    step

    EventLevelUp

    lu

    local_duration

    absolut_duration

    spent

    earned

    balance

    bought

    EventApplicationInfo

    ai

    sdk_version

    app_version

    bundle_id

    engine

    EventWipe

    wipe

    save_cheater_tester

    save_custom_props

    save_paying_status

    save_registration

    EventAlive

    al

    this event only has basic fields

    EventReferal

    rf

    publisher

    sub_publisher

    sub_ad

    sub_ad_group

    sub_campaign

    sub_placement

    sub_site

    cost

    EventSubscription

    sbs

    source payment_type start_time expiry_time event_type price_usd product original_payment_id

    payment_id purchase_type promo_code promo_type payment_status eventlevel

    EventAdRevenue

    adrv

    source ad_unit ad_network

    placement

    revenue

    this manual
    Remote config value from devtodev server.
  • Least priority – Default value defined in the application code.

  • int64Value

    Int32

    Gets the value as a Int32.

    integerValue

    Int

    Gets the value as a Int.

    boolValue

    Bool

    Gets the value as a Bool.

    Initialize devtodev SDK with a specific method
    apply
    iOS platform.
    Android platform
    Windows platform
    Windows
    settings page

    If you are using DTDHuawei.unitypackage, remove the following files:

    DTDLogLevel (enum)

    The level of logging the SDK activity. The DTDLogLevel.no value is used by default. For troubleshooting during integration it is recommended to set it to DTDLogLevel.Debug, and either switch it off DTDLogLevel.No. Use DTDLogLevel.No in the release version.

    ApplicationVersion

    String

    The app version during the devtodev SDK initialization. Use the property on the WinStandalone platform only. For all other platforms, data is collected automatically.

    https://github.com/devtodev-analytics/package_Huawei.git
    (COPPA)
    https://github.com/devtodev-analytics/package_Huawei.git
    COPPA
    COPPA section
    (COPPA)
    COPPA
    COPPA section
    DTDHuawei package
    proguard
    Scripting Define Symbols
  • Add the following imports to your source:

  • Add the push notifications initialization before the DevToDev.init(appKey:String, appSecret:String) method was called:

    onPushToken, onPushTokenFailed, onPushReceived and onPushOpened are the functions that take following arguments:

  • Make some tests and correct the message if it's required

  • Schedule the delivery

  • That's it!

  • Add several lines of the code to switch in the push notification to the SDK

  • Create a campaign for sending push notifications in “Push” section

  • Activate "Push Notifications" option and click on "Create Certificate"
  • At first, you need to generate Certificate Signing Request. We have already done it, so you can just click "Continue".

  • At next step you need to upload the CSR to the Apple server. Choose the CSR-file and click "Generate".

  • Certificate generation takes just a few seconds. After generation is finished click "Download" and then "Done".

  • You'll need to repeat this process to generate the Production Certificate when your app will be ready for release. All steps are the same.

  • You'll be suggested to create a password which is used when you need to import the key to another computer.

  • onPushToken, onPushTokenFailed, onPushReceived and onPushOpened are the functions that take following arguments:
  • Compile and run the app. You will need a device, because simulator does not support push notifications.

  • Firebase console
    <module_name>.Build.cs
    file of the module in which you plan to use the plugin.

    Example:

    Android

    Add your google-services.json file to the project's root directory. It will be used to configure notifications during the project-building process.

    iOS

    In the case your Unreal Engine is built from GitHub source code:

    Enable notifications in the settings of your project: Edit → Project Settings → iOS → Enable Remote Notifications Support

    In the case your Unreal Engine is not built from GitHub source code:

    Add the parameter to the engine configuration file (<proj_dir>/Config/DefaultEngine.ini):

    Data types

    class UDTDMessagingBPLibrary

    A class that implements analytic methods.

    Header file:

    enum class EDTDNotificationActionType : uint8

    Notification action type

    Header file:

    Values:

    • App = 0 - default value

    • Url = 1 - external link opening

    • Share = 2 - share contentc

    • DeepLink = 2 - an in-app link opening

    struct FDTDNotification

    Notification data container.

    Header file:

    Member
    Type
    Description

    ActionType

    EDTDNotificationActionType

    Тип действия уведомления

    ActionString

    FString

    Идентификатор действия уведомления

    Data

    TMap<FString, FString>

    Данные уведомления

    struct FDTDNotificationAction

    Notification action data container.

    Header file:

    Member
    Type
    Description

    ActionType

    EDTDNotificationActionType

    Тип действия уведомления

    ActionString

    FString

    Идентификатор действия уведомления

    ButtonId

    FString

    Идентификатор нажатой кнопки

    enum class EDTDIOSNotificationOptions : uint8

    iOS only

    Push Notification display settings are Bitflags that are managed by the developer and allow for selecting the method of user notification. It can be altered by the end user.

    By default, it has the value: Badge|Sound|Alert

    Header file:

    Values:

    • None = 0 - nothing

    • Badge = 1 << 0 - can display a badge on the app icon

    • Sound = 1 << 1 - can play a sound

    • Alert = 1 << 2 - can display an alert

    • CarPlay = 1 << 3 - can display a push notification on CarPlay

    • CriticalAlert = 1 << 4 - critical alerts can play a sound even if Do Not Disturb is enabled (critical alerts require a special entitlement issued by Apple). Available from iOS 12 onwards.

    • AppNotificationSettings = 1 << 5 - this option defines that the system should display a notification settings button in the app. Available from iOS 12.0 onwards.

    • Provisional = 1 << 6 - an option for sending provisional notifications to the Notification Center without interrupting functioning processes. Available from iOS 12.0 onwards.

    Provisional - Provisional push notifications are shown in the User Notification Center but not on the lock screen. This type of push notification doesn’t require an explicit opt-in from the user. You can start sending them as soon as the user installs and launches your app. However, the user can also explicitly enable/disable your notifications.

    Use this setting to avoid the permission request on app launch because it is seen as intrusive, and most users opt out of it.

    It’s also important that when using the Provisional setting, the user needs to go to Notification Center settings to allow explicit notifications.

    Delegates

    Header file:

    Delegates:

    Initialization

    Notification module initialization:

    Methods

    SetAvailability

    A method responsible for enabling/disabling push notifications. When the state changes, it sends an event that includes the availability status (true or false). The availability status flag is stored in the SDK.

    GetAvailability

    Get the current availability status flag (true or false):

    Argument
    Type
    Description

    onResult

    • FDTDMessagingDynamicBoolParamsDelegate

    • FDTDMessagingBoolParamsDelegate

    Callback

    GetToken

    Get a current unique device ID used in the notification system:

    Argument
    Type
    Description

    onResult

    • FDTDMessagingDynamicStringParamsDelegate

    • FDTDMessagingStringParamsDelegate

    Callback.

    SetTokenListener

    Set a token listener. The listener will be executed when the SDK updates a unique device ID in the notification system.

    Argument
    Type
    Description

    listener

    • FDTDMessagingDynamicStringParamsDelegate

    • FDTDMessagingStringParamsDelegate

    Listener.

    SetTokenErrorListener

    Set a listener for errors in token reception. The listener will be executed when an error occurs while the SDK updates a unique device ID in the notification system.

    Argument
    Type
    Description

    listener

    • FDTDMessagingDynamicStringParamsDelegate

    • FDTDMessagingStringParamsDelegate

    Listener.

    SetNotificationReceiveListener

    Set a listener for notification reception. The listener will be executed when the SDK receives a notification.

    Argument
    Type
    Description

    listener

    • FDTDMessagingDynamicNotificationParamsDelegate

    • FDTDMessagingNotificationParamsDelegate

    Listener.

    SetInvisibleNotificationReceiveListener

    Set a listener for invisible notification reception. The listener will be executed when the SDK receives an invisible notification.

    Argument
    Type
    Description

    listener

    • FDTDMessagingDynamicNotificationParamsDelegate

    • FDTDMessagingNotificationParamsDelegate

    Listener.

    SetNotificationActionListener

    Set a listener for notification activation. The listener will be executed when the notification gets activated.

    Argument
    Type
    Description

    listener

    • FDTDMessagingDynamicNotificationActionParamsDelegate

    • FDTDMessagingNotificationActionParamsDelegate

    Listener.

    IOSSetNotificationOptions

    iOS only

    Set notification parameters.

    Argument
    Type
    Description

    options

    int32

    Options.

    An int32 type value is used as an argument of this method. However, this argument should be calculated by using enumerators of EDTDIOSNotificationOptions and bitwise OR operator.

    GitHub repository
    Source code (zip)
    Query type: POST

    The query body is a JSON object with fields described in the table below.

    Fields

    Name
    Description

    notificationType

    Type of notification

    originalTransactionId

    ID of the original transaction

    transactionId

    Transaction ID

    startDateMs

    Subscription start date in milliseconds in UTC

    expiresDateMs

    Subscription expiration date in milliseconds in UTC

    Notification types

    Notification type
    Description

    purchase

    First-time subscription purchase

    renewal

    Active subscription renewal

    refund

    Money refund

    cancellation

    Cancellation of an active subscription and proportional refund

    Server response

    If the notification is accepted, the response status is 200. If the query is incorrect, the response status is 400. In case of an internal error, the response status is 500.

    In case the status is 400 or 500, the error description will be indicated in the query body as a JSON object:

    Notification types and corresponding fields

    1. Available notification types for a trial (all postbacks should be flagged as ‘isTrial = true’):

    • purchase – trial subscription

    • cancellation – subscription cancellation before the end of that trial

    2. Available notification types for subscription purchase:

    • purchase – subscription purchase

    • renewal – subscription renewal

    • cancellation – premature termination of a subscription and proportional refund

    • refund – money refund

    Trial purchase

    Field name
    Description

    notificationType

    Notification type = purchase

    originalTransactionId

    ID of the original transaction

    transactionId

    Transaction ID

    startDateMs

    Trial start date in milliseconds in UTC

    expiresDateMs

    Trial expiration date in milliseconds in UTC

    Trial cancellation

    Field name
    Description

    notificationType

    Notification type = cancellation

    originalTransactionId

    ID of the original transaction

    transactionId

    Transaction ID

    expiresDateMs

    Trial expiration date in milliseconds in UTC

    product

    Subscription SKU

    Subscription purchase

    Field name
    Description

    notificationType

    Notification type = purchase

    originalTransactionId

    ID of the original transaction

    transactionId

    Transaction ID

    startDateMs

    Subscription start date in milliseconds in UTC

    expiresDateMs

    Subscription expiration date in milliseconds in UTC

    Subscription renewal

    Field name
    Description

    notificationType

    Notification type = renewal

    originalTransactionId

    ID of the original transaction

    transactionId

    Transaction ID

    startDateMs

    Subscription start date in milliseconds in UTC

    expiresDateMs

    Subscription expiration date in milliseconds in UTC

    Subscription cancellation

    Field name
    Description

    notificationType

    Notification type = cancellation

    originalTransactionId

    ID of the original transaction

    transactionId

    Transaction ID

    expiresDateMs

    Subscription expiration date in milliseconds in UTC

    product

    Subscription SKU

    Subscription refund

    Field name
    Description

    notificationType

    Notification type = refund

    originalTransactionId

    ID of the original transaction

    transactionId

    Transaction ID

    expiresDateMs

    Subscription expiration date in milliseconds in UTC

    product

    Subscription SKU

    Query example

    Android setup
    iOS setup

    void DTDMessaging.IOS.GetPushState(Action<bool> onGetPushState)

    The method that returns the push module state to onGetPushState callback

    Windows

    devtodev Push API supports two different formats of notification sending to Windows operational systems:

    • Universal format described in this section works on the basis of Legacy tiles and toast schema. It can be used on Windows Phone 8.1, Windows Phone 10, Windows 8.1, Windows 10. It is described by the "win" object.

    • UWP format - can be used only for Windows 10 and Windows Phone 10. It is described by the "uwp" object.

    User identifiers

    In order to find a user to whom you need to send a notification, you can specify one or several available identifiers:

    Notification settings

    Object "windows" can contain two properties :

    1. The property that describes a message can be represented by one of 4 possible objects:

    • toast (object) - the result of sending is the delivery of toast-notification

    • raw (object) - sends a hidden toast-notification

    • tile (object) - changes tile content of an app

    • badge (object) - changes the value of a badge field displayed in a tile of an app

    1. The property that describes additional parameters of a message represented by the object “options”. Optional.

    Toast-notification content ("toast" object properties):

    Example

    Hidden notification ("raw" object):

    Example

    Tile-notification content ("tile" object):

    Example

    Badge ("badge" object):

    Example

    Additional settings of notification delivery and display ("options" object)

    Anticheat methods

    Recommended sequence of actions when working with transactions

    1. Get response about a completed transaction from the payment system.

    Labels API

    Obtaining the User API token

    In order to be able to use Labels API you must have an individual User API token, which can be found in space settings in devtodev system.

    Assets\Plugins\Android\settingsTemplate.gradle
    Assets\Plugins\DevToDev\Android\DTDHuaweiAndroid.aar
    Assets\DevToDev\Analytics\Editor\HuaweiDependencies.xml
    import java.nio.file.Files
    
    static void enableJetifier(Project project) {
        project.ext['android.useAndroidX'] = true
        project.ext['android.enableJetifier'] = true
    }
    
    static void addBuildscript(Project project) {
        project.buildscript {
            repositories {
                maven { url 'https://plugins.gradle.org/m2/' } 
                maven { url 'https://developer.huawei.com/repo/' }
            }
    
            dependencies {
                classpath 'com.huawei.agconnect:agcp:1.6.5.300'
            }
        }
    }
    
    static void applyPlugins(Project project) {
        if (project.name != 'launcher') return
    
        project.afterEvaluate {
            it.apply plugin: 'com.huawei.agconnect'
        }
    }
    
    static void copyAppGalleryJson(Project project) {
        if (project.name != 'launcher') return
    
        def destinationFile = new File("${project.rootDir}/launcher/agconnect-services.json")
        if (destinationFile.exists()) return
    
        def sourceFile = new File("${project.rootDir}/unityLibrary/devtodev.plugin/agconnect-services.json")
        Files.copy(sourceFile.toPath(), destinationFile.toPath())
    }
    
    gradle.rootProject {
        it.afterEvaluate {
            it.allprojects {
                enableJetifier(it)
                addBuildscript(it)
                applyPlugins(it)
                copyAppGalleryJson(it)
            }
        }
    }
    
    include ':launcher', ':unityLibrary'
    **INCLUDES**
    -keep class com.devtodev.** { *; }
    -dontwarn com.devtodev.**
    -keep class com.huawei.hms.**{*;}
    Assets\DevToDev\Analytics\Editor\GoogleDependencies.xml
    Assets\Plugins\DevToDev\Android\DTDGoogleAndroid.aar
    import com.devtodev.sdk.push.DevToDevPushManager;
    import com.devtodev.sdk.push.logic.ActionButton;
    import com.devtodev.sdk.push.logic.PushMessage;
    DevToDevPushManager.setOnFailedToRegisteredForPushNotifications(onPushTokenFailed);
    DevToDevPushManager.setOnRegisteredForPushNotifications(onPushToken);
    DevToDevPushManager.setOnPushNotificationsReceived(onPushReceived);					
    DevToDevPushManager.setOnPushNotificationOpened(onPushOpened);					
    DevToDevPushManager.setPushNotificationsEnabled(true);
    /**
    * @param token - push token
    */
    protected function onPushToken(param:String):void {
    }
    			
    /**
    * @param error - error message  
    */
    protected function onPushTokenFailed(param:String):void {
    }
    			
    /**
    * @param pushData - Dictionary with push message and custom push fields
    */
    protected function onPushReceived(pushData:Dictionary):void {
    }
    			
    /**
    * @param message - PushMessage. Represents toast notification message
    * @param button - ActionButton. Represents toast notification button that was clicked. Could be null if notification body was clicked
    */
    protected function onPushOpened(message:PushMessage, button:ActionButton):void {
    }
    <application>
        <extensions>
            <extensionID>com.devtodev.air.extensions.gps-auth</extensionID>
            <extensionID>com.devtodev.air.extensions.gps-base</extensionID>
            <extensionID>com.devtodev.air.extensions.gps-basement</extensionID>
            <extensionID>com.devtodev.air.extensions.supportv4</extensionID>
            <extensionID>com.devtodev.air.extensions.firebase-auth</extensionID>
            <extensionID>com.devtodev.air.extensions.firebase-common</extensionID>
            <extensionID>com.devtodev.air.extensions.firebase-iid</extensionID>
            <extensionID>com.devtodev.air.extensions.firebase-messaging</extensionID>
            <!-- this library should be already added for analytics -->
            <extensionID>com.devtodev.SDK</extensionID>
            ...
        </extensions>
    </application>
    openssl x509 -in developer_identity.cer -inform DER -out developer_identity.pem -outform PEM
    openssl pkcs12 -nocerts -in mykey.p12 -out mykey.pem
    openssl pkcs12 -export -inkey mykey.key -in developer_identity.pem -out iphone_dev.p12
    <iPhone>
          <InfoAdditions><![CDATA[
             <key>MinimumOSVersion</key>
             <string>7.0</string>
    		 <key>UIDeviceFamily</key>
    		 <array>
    		    <string>1</string>
    			<string>2</string>
    		 </array>
             ...
    	 ]]></InfoAdditions>
    
         <Entitlements>
     	    <![CDATA[
            	<key>aps-environment</key>
             	<string>development</string>
            ]]>
         </Entitlements>  
    </iPhone>
    import com.devtodev.sdk.push.DevToDevPushManager;
    import com.devtodev.sdk.push.logic.ActionButton;
    import com.devtodev.sdk.push.logic.PushMessage;
    DevToDevPushManager.setOnFailedToRegisteredForPushNotifications(onPushTokenFailed);
    DevToDevPushManager.setOnRegisteredForPushNotifications(onPushToken);
    DevToDevPushManager.setOnPushNotificationsReceived(onPushReceived);					
    DevToDevPushManager.setOnPushNotificationOpened(onPushOpened);					
    DevToDevPushManager.setPushNotificationsEnabled(true);
    <!--Replace 'com.example.application' to your package -->
    <manifest package="com.example.application" ...>
        <uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
        <permission android:name="com.example.gcm.permission.C2D_MESSAGE" android:protectionLevel="signature" />
        <uses-permission android:name="com.example.gcm.permission.C2D_MESSAGE" />
    	<application>
    		<meta-data android:name="com.google.android.gms.version" android:value="@integer/google_play_services_version" />
                        
            <receiver android:name="com.google.firebase.iid.FirebaseInstanceIdReceiver" android:exported="true" android:permission="com.google.android.c2dm.permission.SEND" >
                <intent-filter>
                    <action android:name="com.google.android.c2dm.intent.RECEIVE" />
                    <action android:name="com.google.android.c2dm.intent.REGISTRATION" />
                    <category android:name="com.example.application" />
                </intent-filter>
            </receiver>
                        
            <service android:name="com.devtodev.push.logic.DTDFcmMessagingService" android:exported="false">
                <intent-filter>
                    <action android:name="com.google.firebase.MESSAGING_EVENT"/>
                </intent-filter>
            </service>
                        
            <service android:name="com.devtodev.push.logic.DTDFcmInstanceIdService" android:exported="false">
                <intent-filter>
                    <action android:name="com.google.firebase.INSTANCE_ID_EVENT"/>
                </intent-filter>
            </service>
                        
    		<receiver android:name="com.devtodev.push.logic.PushClickReceiver" android:enabled="true" android:exported="true">
    		    <intent-filter>
    			    <action android:name="com.devtodev.android.push.CLICKED" />
    			</intent-filter>
    		</receiver>
    					
    		<activity android:name="com.devtodev.push.logic.PushHandlerActivity"/>
    	</application>
    </manifest>
    /**
    * @param token - push token
    */
    protected function onPushToken(param:String):void {
    }
    			
    /**
    * @param error - error message  
    */
    protected function onPushTokenFailed(param:String):void {
    }
    			
    /**
    * @param pushData - Dictionary with push message and custom push fields
    */
    protected function onPushReceived(pushData:Dictionary):void {
    }
    			
    /**
    * @param message - PushMessage. Represents toast notification message
    * @param button - ActionButton. Represents toast notification button that was clicked. Could be null if notification body was clicked
    */
    protected function onPushOpened(message:PushMessage, button:ActionButton):void {
    }
    PublicDependencyModuleNames.Add("DTDMessaging");
    // Some code/Script/IOSRuntimeSettings.IOSRuntimeSettings]
    bEnableRemoteNotificationsSupport=True
    #include "DTDMessaging/Public/DTDMessagingBPLibrary.h"
    #include "DTDMessaging/Public/DTDNotificationActionType.h"
    #include "DTDMessaging/Public/DTDNotification.h"
    #include "DTDMessaging/Public/DTDNotificationAction.h
    #include "DTDMessaging/Public/DTDIOSNotificationOptions.h"
    #include "DTDMessaging/Public/DTDMessagingDelegates.h"
    DECLARE_DELEGATE_OneParam(FDTDMessagingBoolParamsDelegate, bool);
    DECLARE_DELEGATE_OneParam(FDTDMessagingStringParamsDelegate, const FString&);
    DECLARE_DELEGATE_OneParam(FDTDMessagingNotificationParamsDelegate, const FDTDNotification&);
    DECLARE_DELEGATE_TwoParams(FDTDMessagingNotificationActionParamsDelegate, const FDTDNotification&, const FDTDNotificationAction&);
    UDTDMessagingBPLibrary::Initialize();
    UDTDMessagincgBPLibrary::SetAvailability(true);
    auto onResult = new FDTDMessagingBoolParamsDelegate();
    onResult->BindLambda([](bool value)
    {
      // Your code...
    });
    UDTDMessagingBPLibrary::GetAvailability(*onResult);
    auto onResult = new FDTDMessagingStringParamsDelegate();
    onResult->BindLambda([](const FString& value)
    {
      // Your code...
    });
    UDTDMessagingBPLibrary::GetToken(*onResult);
    auto listener = new FDTDMessagingStringParamsDelegate();
    listener->BindLambda([](const FString& value)
    {
      // Your code...
    });
    UDTDMessagingBPLibrary::SetTokenListener(*listener);
    auto listener = new FDTDMessagingStringParamsDelegate();
    listener->BindLambda([](const FString& value)
    {
      // Your code...
    });
    UDTDMessagingBPLibrary::SetTokenErrorListener(*listener);
    auto listener = new FDTDMessagingNotificationParamsDelegate();
    listener->BindLambda([](const FDTDNotification& notification)
    {
      // Your code...
    });
    UDTDMessagingBPLibrary::SetNotificationReceiveListener(*listener);
    auto listener = new FDTDMessagingNotificationParamsDelegate();
    listener->BindLambda([](const FDTDNotification& notification)
    {
      // Your code...
    });
    UDTDMessagingBPLibrary::SetInvisibleNotificationReceiveListener(*listener);
    auto listener = new FDTDMessagingNotificationActionParamsDelegate();
    listener->BindLambda([](const FDTDNotification& notification, const FDTDNotificationAction& action)
    {
      // Your code...
    });
    UDTDMessagingBPLibrary::SetNotificationActionListener(*listener);
    int32 options = EDTDIOSNotificationOptions::Badge |
      EDTDIOSNotificationOptions::Sound |
      EDTDIOSNotificationOptions::Alert;
    UDTDMessagingBPLibrary::IOSSetNotificationOptions(options);
    https://statgw.devtodev.com/subscriptions/api?apikey={project’s API key}
    {
      "title": "Bad request",
      "error": "Not set parameter api-key"
    }
    POST https://statgw.devtodev.com/subscriptions/api?apikey=ak-cDNRQl0Lypq4AOUrx8aGGMnmJT1FSebd
    {
       "notificationType":"PURCHASE",
       "transactionId":"transactionId",
       "startDateMs":1640072573468,
       "expiresDateMs":1640245373468,
       "product":"com.demo.bundle.weekly",
       "price":90.9,
       "currency":"RUB",
       "isTrial":false,
       "devtodevId":4064192
    }
    

    product

    Subscription SKU

    productType

    Subscription type (line)

    price

    Subscription price

    currency

    Currency of the subscription payment

    isTrial

    The event is flagged if it is trial and is not flagged if it’s a subscription

    gracePeriod

    Extension of subscription renewal date in days. If this field is filled in and the user did not renew the subscription before the expiration date, then wait for gracePeriod days value to flag it as expired.

    Fields for user identification (Attention! Regardless of the type of notification, at least one of the user IDs must be specified in the query)

    idfa

    Available platforms: iOS

    idfv

    Available platforms: iOS

    advertisingId

    Available platforms: Android, Windows

    androidId

    Available platforms: Android

    userId

    The same as Main ID in user card

    customId

    String custom user ID

    Available platforms: all platforms

    devtodevId

    d2d numeric user id. Available platforms: all platforms

    product

    Subscription SKU

    productType

    Subscription type (line)

    isTrial

    true

    productType

    Subscription type (line)

    isTrial

    true

    product

    Subscription SKU

    productType

    Subscription type (line)

    isTrial

    false

    price

    Subscription price

    currency

    Currency of the subscription payment

    gracePeriod

    Extension of subscription renewal date in days

    product

    Subscription SKU

    productType

    Subscription type (line)

    isTrial

    false

    price

    Subscription price

    currency

    Currency of the subscription payment

    gracePeriod

    Extension of subscription renewal date in days

    productType

    Subscription type (line)

    isTrial

    false

    productType

    Subscription type (line)

    isTrial

    false

    price

    The amount of money refunded to the user

    currency

    Currency of the subscription payment

    ButtonText

    FString

    Текст нажатой кнопки

    ButtonIcon

    FString

    Иконка нажатой кнопки

    IsBackground

    bool

    Режим открытия приложения кнопкой

    User id is applicable if an internal identifier (cross-platform user identifier) is used in your app.

    devtodevId

    number

    Numeric user identifier in devtodev database.

    Optional. You can pass custom parameters with messages and use them within an app. For instance, you can activate advertising campaign or any other functionality for the user who has received this message.

    Example:

    "data": { "my_key": "value", "my_another_key": "15" }

    icon

    string

    Optional. To replace the application icon (that shows up on the top left corner of the toast) use the URL or the resource name.

    Any toast shown on Windows Phone 8.1 does not display the images (the app icon only). In Windows 10 the image is expressed using the URI of the image source, using one of these protocol handlers:

    • A web-based image: http:// or https://

    • An image included in the app package: ms-appx:///

    sound

    string

    Optional. A media file to play in place of a default sound. If the option is not used, a notification comes silently. This property can have one of the following string values:

    • Default

    • IM

    • Mail

    action

    object

    Optional. Available for Windows 10 and WP 10 only. Action after a click on the body of a notification. By default, a click simply opens an app. It is also possible to perform the following actions:

    • deeplink (string) - Direct the user to a specific resource, either within your app or on the web.

    • url (string) - Open a web page in a mobile browser, or any valid device-level URL such as Windows Store or app protocol links.

    • share (string) - The Share Action drives a user to share your message when they interact with your push notification.

    interactive

    object

    Optional. Available for Windows 10 and WP 10 only. It is possible to specify an array of notification buttons in the “interactive” object. Each button must contain the following properties:

    • id (string) - Button identifier. It is transferred to an app

    • text (string) - Text on a button

    • handling (string) - The type of processing

    Third string on the tile.

    text4

    string

    Fourth string on the tile.

    image

    string

    Optional.Application-relative or Internet URI of the image.

    Example:

    • "red.jpg" (if image is stored in an app’s installation directory or local storage folder)

    • "http://my.domain.com/img/red.jpg" (for Internet URIs).

    We recommend using images with size 336 pixels by 336 pixels.

    Important Note:

    If you need to use remote Internet URIs for Tile images, you must take the following steps:

    Property

    Type

    Description

    token

    string

    Push token. If you use push token, all fields with other identifiers will be ignored!

    advertisingId

    string

    Advertising ID

    serialId

    string

    Hardware serial number

    userId

    Property

    Type

    Description

    title

    string

    Required. A short string describing the purpose of a notification.

    text

    string

    Required. Main text of a notification message .

    text2

    string

    Optional. Additional text of a notification.

    data

    Property

    Type

    Description

    data

    object

    Required. You can pass custom parameters with messages and use them within an app. For instance, you can activate advertising campaign or any other functionality for user who has received this message.

    Example:

    "data": { "my_key": "value", "my_another_key": "15" }

    badge

    string

    Optional. A number from 1 to 99. A value of 0 is equivalent to the glyph value "none" and will clear a badge.

    Instead of a number, a badge can display one of a non-extensible set of status glyphs:

    • activity

    • none

    • alarm

    • alert

    • attention

    • available

    • away

    • busy

    • error

    • newMessage

    • paused

    • playing

    • unavailable

    It is also possible to send values incrementing or decrementing the current value in a “+2”, “-1” format. In case the previous value was the glyph or 0, the value will be increment. Decrement does not influence the zero value and the glyph.

    Property

    Type

    Description

    text1

    string

    First string on the tile.

    with_header

    boolean

    Set True to mark "text1" field as a heading to show it larger.

    text2

    string

    Second string on the tile.

    text3

    Property

    Type

    Description

    badge

    string

    A number from 1 to 99. A value of 0 is equivalent to the glyph value "none" and will clear a badge.

    Instead of a number, a badge can display one of a non-extensible set of status glyphs:

    • activity

    • none

    • alarm

    • alert

    • attention

    • available

    • away

    • busy

    • error

    • newMessage

    • paused

    • playing

    • unavailable

    Property

    Type

    Description

    expire

    number

    This option identifies the date when the tile or toast is no longer valid and can be discarded. It is possible to use either relative time in seconds passed since sending, or to specify an exact date in UNIX epoch format date expressed in seconds (UTC). Default value is 7 days (604800 seconds) after sending.

    string

    object

    string

    Either send data about the received transaction for verification by calling devtodev anti-cheat methods or use your own tools for transaction verification.
  • If the transaction has successfully passed verification, perform the Payment event. If the transaction has not passed verification, do not perform the Payment event.

  • Payment Validation

    The devtodev service allows you to validate transactions to prevent fraud from influencing your statistics. For this, you need to integrate DTDAntiCheat module.

    We strongly discourage you from using verification results for deciding on allowing or denying users to receive their purchases! We do not recommend to mark users as cheaters based on the results of this verification! Employ this method exclusively for preventing fraud transaction data from being sent to devtodev!

    To validate the transaction you can use the verifyPayment(completionHandler: @escaping (DTDVerifyResponse) -> Void) method immediately during the transaction processing, e.g.:

    extension Purchases: SKPaymentTransactionObserver {
        func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
            for transaction in transactions {
                switch transaction.transactionState {
                    case
    

    DTDVerifyResponse

    The DTDVerifyResponse object returned while validating the transaction has two properties:

    DTDReceiptStatus

    The enum type returned as a result of validation can receive the following values:

    We recommend calling the Real Currency Payment method in all cases except when you receive receiptNotValid or receiptSandbox as a result of the validation.

    To validate the transaction you can use the (void)verifyPaymentCompletion:( void (^ _Nonnull)(DTDVerifyResponse * _Nonnull))completionHandler; method immediately during the transaction processing, e.g.:

    DTDVerifyResponse

    The DTDVerifyResponse object returned while validating the transaction has two properties:

    When Google Play sends the transaction back to your onActivityResult, validate it by calling the following method: verifyPayment(receipt: String, signature: String, publicKey: String, completionHandler:(DTDVerifyResponse) -> Unit) immediately during the transaction processing, e.g.:

    DTDVerifyResponse

    The DTDVerifyResponse object returned while validating the transaction has two properties:

    When Google Play sends the transaction back to your onActivityResult, validate it by calling the following method: verifyPayment(receipt: String, signature: String, publicKey: String, completionHandler:(DTDVerifyResponse) -> Unit) immediately during the transaction processing, e.g.:

    DTDVerifyResponse

    The DTDVerifyResponse object returned while validating the transaction has two properties:

    devtodev sends a request for transaction verification to the payment platform and then forwards the answer to the app. To validate the transaction you can use the Task<DTDReceiptStatus> VerifyPayment(string: receipt) method. As an argument pass the PurchaseResults.ReceiptXml property. More information about it .

    Example of verification:

    DTDVerifyResponse

    Google Play

    If you use Unity IAP for payment validation, call the following method: void VerifyPayment(string publicKey, string receipt, Action completionHandler)

    Example:

    To validate data received from Google Play, use void VerifyPayment(string publicKey, string receipt, string signature,Action<DTDVerifyResponse> completionHandler) when handling the transaction.

    You will be able to see the interface unit with User API token in space settings only in case your price plan and access rights (role) allow you to use devtodev API. You can discard User API token or create it again on the same page.

    Pay your attention that if you use several spaces, each of them will have an individual User API token.

    Obtaining the list of categories

    The result of the request is the obtaining of the list of all existing labels categories for the app.

    Request:

    POST

    Response:

    Removing categories

    The request is used to remove or rename a category. The category can be removed only if its full name is specified. One request can remove only one category. In case the parameter move_labels_to_category is specified, labels that belong to the category that is going to be removed will be transfered to the specified category. If the specified category doesn't exist, it will be created. If move_labels_to_category is not specified, all the labels that belong to the category will be removed.

    Request:

    POST

    Response:

    Obtaining the list of labels

    The result of the request is the obtaining of the list of labels that are presented as objects.

    You can use filters to get some specific labels you need. In case no filter is specified, you will receive the whole list of labels. It is possible to use any combination of fields in a filter.

    Request:

    POST

    Response:

    List of comparison operators that can be applied in a filter

    The filter can be described with an object, where the following comparison operators can be the properties:

    API operator

    Math operator

    Description

    gt

    >

    Greater than

    lt

    <

    Less than

    eq

    =

    Equal

    gte

    The empty object describes the filter that selects all the events in which the parameter value is not null or empty string.

    Adding labels

    In case the specified category doesn't exist, it will be created. If string values of fields exceed the maximum, they will be cut off.

    If you need to specify an event without time length, start_date must be equal to end_date or end_date must not be specified.

    Request:

    POST

    Response:

    Editing labels

    To edit a label you need to specify its identifier (id). All the specified fields for the label will be replaced. In case the specified category doesn't exist, it will be created.

    If it is necessary to change the dates of an event, you must specify start_date. It is impossible to specify end_date without specifying start_date. If the label is used in relation to a continuous event (start_data and end_date are different timestamps), but in an edited version only the start_date is received, the label becomes a point that characterizes an instant event.

    Request:

    POST

    Response:

    Deleting labels

    The request is used to delete one or several labels. To delete a label its identifier is required. The request without specified identifiers is not valid.

    Request:

    POST

    Response:

    Error handling

    In case there is an error in a request, the response is made in the following format:

    where

    • status_code (number) - the general status of an error

    • errors (array) - an array of error descriptions

    • code (number) - the exact code of an error from the table of errors

    • msg (string) - a brief description of an error

    The list of possible errors is given in a table.

    List of possible errors

    Status code

    Code

    Value of "msg" field

    Error description

    500

    1

    Unknown Error

    Unknown error. In case it repeats, please contact technical support.

    400

    2

    Request body is empty

    Request body is empty. There is no POST data in the request.

    400

    Limitations

    Limitations on the lenght of values

    Name

    Field

    Limitation

    Label name

    name

    not more than 10 symbols

    Short description of a label

    description

    not more than 512 symbols

    Category name

    category

    not more than 30 symbols

    Limitations on the number of actions

    • Not more than 30 labels in one request on labels creation

    • Not more than 500 labels to be created in a calendar day for User API token

    • Not more than 200 labels to be created in a calendar day for an app

    Android

    Android Push Notifications

    How to create a project in Firebase and integrate Firebase Services into your application

    Push Notifications on Android are sent with the help of the FCM service.

    Register and open the project creation window in Firebase.

    Click Add project.

    Write the name of your project. At this stage, you can also set your own unique project identifier or use the one that Firebase will generate for you automatically.

    After creating the project card, create an Android application by clicking on the Android icon.

    Register your android package name.

    Download the google-services.json file and use it according to the instructions of firebase.

    Add firebase dependencies according to the firebase documentation.

    Complete the application registration, you will see the project overview section.

    Select your application by clicking on it, you will see a gear on the right side, click on it to go to project settings.

    Go to the cloud messages section, make sure that the Firebase Cloud Messaging API (V1) is active (if not, activate it).

    Go to the general section and copy the value of the Project ID field and paste it into the devtodev web interface.

    The Project ID is also available from the Firebase main screen in the project cards. After setting up the application, the card should look like this:

    It should have a Project ID and an android icon.

    Next, open the push notifications integration settings panel in the application settings section in devtodev service (App → Settings → Push notifications → Push notifications panel). Push edit button (pencil symbol).

    You will need to specify the Firebase Project ID and authorize devtodev to send messages and manage messaging subscriptions for your Firebase application. To authorise the application, you must use Google login and password of a user with sufficient access rights to the project on Firebase. After that click Save button.

    Messaging Module Integration

    The Messaging module is available as an AAR (recommended) and JAR library. The library is available in the MavenCentral and .

    1. If you use Gradle for the applications build, add mavenCentral() into gradle.build file of your application and specify the following relationship in dependencies block:

    2. To the app manifest add the following:

    3. To add a user icon to your push notification and change its color, add the following strings to the manifest file code:

    To add a large user icon to your push notifications, add:

    Example:

    4. After the DTDAnalytics initializer, add the DTDMessaging initializer.

    Example:

    5. Subscribe a DTDPushListener to receive information about the DTDMessaging functioning.

    Example:

    6. Call the DTDMessaging.startPushService() method to activate the Messaging module.

    Android 13 or higher

    When using Android 13 or higher, notifications are disabled by default. The app won’t receive notifications until you request a new permission (POST_NOTIFICATIONS) and the user grants this permission to your app.

    For notifications to work properly, add the following line to the manifest file:

    You can check the operation of the POST_NOTIFICATIONS permission in your app by inserting this example in the code:

    If the check results in a negative answer (access is not granted), call this method:

    Its execution will call a dialog that in turn will ask the user to opt in:

    This code is going to help you get the user’s decision:

    Note. In SDK ver. 2.1.5 or higher, if the permission is not granted, you will see this message in the log from DTDMessaging: “Notifications don’t work. Permission android.permission.POST_NOTIFICATIONS is not granted”.

    External interface of the DTDMessaging module

    DTDPushListener Interface Methods

    A Class for Receiving Notification Data (DTDPushMessage).

    Basic Class Properties

    A Class for Handling the Notification Button Taps (DTDActionButton)

    25.jpg
    {
        "user_token": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
        "app_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
        "campaign_tag": "campaign name",
        "pack_id": "uniqueid1234",
        "audience": [{
            "token": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
            "advertisingId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
            "serialId": "xxxxxxxxx",
            "customUid": "xxxxxxxxxxxx"
        }],
        "win": {
            "toast": {
                "title": "Toast title",
                "text": "Toast message",
                "data": {
                    "key1": "value",
                    "key2": "15"
                },
                "icon": "https://domain.com/pic.png",
                "sound": "default",
                "action": {
                    "url": "http://www.domain.com"
                },
                "interactive": {
                    "buttons": [{
                        "id": "accept",
                        "text": "Accept",
                        "handling": "foreground",
                        "action": {
                            "url": "http://www.domain.com/accept"
                        }
                    }, {
                        "id": "decline",
                        "text": "Decline",
                        "handling": "background"
                    }]
                }
    
            },
            "options": {
                "expire": 36000
            }
        }
    }
    {
        "user_token": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
        "app_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
        "campaign_tag": "campaign name",
        "pack_id": "uniqueid1234",
        "audience": [{
            "token": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
            "advertisingId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
            "serialId": "xxxxxxxxx",
            "customUid": "xxxxxxxxxxxx"
        }],
        "win": {
            "raw": {
                "data": {
                    "key1": "value",
                    "key2": "15"
                },
                "badge": "+1"
            }
        }
    }
    {
        "user_token": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
        "app_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
        "campaign_tag": "campaign name",
        "pack_id": "uniqueid1234",
        "audience": [{
            "token": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
            "advertisingId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
            "serialId": "xxxxxxxxx",
            "customUid": "xxxxxxxxxxxx"
        }],
        "win": {
            "tile": {
                "text1": "Text 1",
                "text2": "Text 2",
                "text3": "Text 3",
                "text4": "Text 4",
                "with_header": "true",
                "image": "https://domain.com/pic.png"
            },
            "options": {
                "expire": 36000
            }
        }
    }
    {
    	"user_token": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
    	"app_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
    	"campaign_tag": "campaign name",
    	"pack_id": "uniqueid1234",
    	"audience": [{
    		"token": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
    		"advertisingId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
    		"serialId": "xxxxxxxxx",
    		"customUid": "xxxxxxxxxxxx"
    	}],
    	"win": {
    		"badge": {
    			"badge": "99"
    		}
    	}
    }
    https://www.devtodev.com/api/v1/labels/categories/get
    {
    	"user_token": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", // devtodev user API token
    	"app_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"  // devtodev App ID
    }
    {
    	"status_code": 200,
    	"data": {
    		"status": "complete",
    		"categories": [
    			"category name",
    			"category name 2"
    		]
    	}
    }
    https://www.devtodev.com/api/v1/labels/categories/delete
    {
    	"user_token": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
    	"app_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
    	"category": "category to be removed",
    	"move_labels_to_category": "category to which the labels will be moved"
    }
    {
    	"status_code": 200,
    	"data": {
    		"status": "deleted",
    		"category": "category to be removed"
    	}
    }
    https://www.devtodev.com/api/v1/labels/get
    {
    	"user_token": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
    	"app_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
    	"filters": {
    		"id": {
    			"in": [100500, 100501]
    		},
    		"category": {
    			"in": ["category name", "category name 2"]
    		},
    		"start_date": {
    			"gte": 1478625206
    		},
    		"end_date": {
    			"lte": 1478625206
    		}
    	}
    }
    {
    	"status_code": 200,
    	"data": {
    		"status": "complete",
    		"labels": [{
    			"id": 100500,
    			"name": "label name",
    			"description": "short label description",
    			"category": "category name",
    			"start_date": 1478625206,
    			"end_date": 1478625206
    		}]
    	}
    }
    https://www.devtodev.com/api/v1/labels/add
    {
    	"user_token": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
    	"app_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
    	"labels": [{
    		"name": "label name",
    		"description": "short label description",
    		"category": "category name",
    		"start_date": 1478625206,
    		"end_date": 1478625206
    	}]
    }
    {
    	"status_code": 201,
    	"data": {
    		"status": "created",
    		"labels": [{
    			"id": 100500,
    			"name": "label name",
    			"description": "short label description",
    			"category": "category name",
    			"start_date": 1478625206,
    			"end_date": 1478625206
    		}]
    	}
    }
    https://www.devtodev.com/api/v1/labels/edit
    {
    	"user_token": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
    	"app_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
    	"id": 100500,
    	"name": "label name",
    	"description": "short label description",
    	"category": "category name",
    	"start_date": 1478625206,
    	"end_date": 1478625206
    }
    {
    	"status_code": 200,
    	"data": {
    		"status": "complete",
    		"labels": [{
    			"id": 100500,
    			"name": "label name",
    			"description": "short label description",
    			"category": "category name",
    			"start_date": 1478625206,
    			"end_date": 1478625206
    		}]
    	}
    }
    https://www.devtodev.com/api/v1/labels/delete
    {
    	"user_token": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
    	"app_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
    	"ids": [100500, 100501]
    }
    {
    	"status_code": 200,
    	"data": {
    		"status": "deleted",
    		"labels": [{
    			"id": 100500,
    			"name": "label name",
    			"description": "short label description",
    			"category": "category name",
    			"start_date": 1478625206,
    			"end_date": 1478625206
    		}]
    	}
    }
    {
    	"status_code": 500,
    	"errors": [{
    		"code": 3,
    		"msg": "Malformed json"
    	}]
    }
    An image saved to local storage: ms-appdata:///local/
  • A local image (Only supported for desktop apps.): file:///

  • Reminder
  • SMS

  • Looping.Alarm

  • Looping.Alarm2

  • Looping.Alarm3

  • Looping.Alarm4

  • Looping.Alarm5

  • Looping.Alarm6

  • Looping.Alarm7

  • Looping.Alarm8

  • Looping.Alarm9

  • Looping.Alarm10

  • Looping.Call

  • Looping.Call2

  • Looping.Call3

  • Looping.Call4

  • Looping.Call5

  • Looping.Call6

  • Looping.Call7

  • Looping.Call8

  • Looping.Call9

  • Looping.Call10

  • On mobile platform, this property can also contain the path to a local audio file, with one of the following prefixes:

    • ms-appx:///

    • ms-appdata:///

    Examples:

    "action": { "url": "http://www.domain.com" } "action": { "deeplink": "your-url-scheme://host/path" }

    "action": { "share": "Happy holidays!" }

    background - A button closes an app. Notification parameters are transferred to an app, but an app doesn’t open. It is impossible to tie an action to a button in this regime.

  • foreground - A button opens an app. Notification parameters are transferred to an app. It is possible to tie an action to a button in this regime. The list of actions and the principle is analogical to actions with the body of a message.

  • Example:

    "interactive": { "buttons": [{ "id": "accept", "text": "Accept", "handling":"foreground", "action": { "url": "http://www.domain.com/accept" }, { "id": "decline", "text": "Decline", "handling":"background", }] }

    • Ensure the remote image file size is less than 150 KB.

    • Ensure the image can be downloaded in 60 seconds or less.

    >=

    Greater than or equal

    lte

    <=

    Less than or equal

    neq

    !=

    Not equal

    eq

    In the list

    neq

    Not in the list

    3

    Malformed json

    There is an error of JSON format in the request body. Fix the error of format.

    400

    4

    Field not found: %field_name%

    The required field is not specified in the request. Please add it to the request.

    400

    5

    Field %field_name% has type %received_type% but %expected_type% expected

    The type of data doesn't match the expected type. Follow the recommendation and change the data type to the expected one. It is possible to use the following types of data: boolean, integer, float, number (integer+float), string.

    400

    6

    Invalid app id %app id value%

    The requested app is not found. The app is unknown. The error can appear in case the specified app identifier is wrong or when the app has been removed.

    401

    11

    Authorization error. Wrong user token %user_token value%

    Authorization error. The specified User API token is wrong. Check the value of the specified key.

    401

    12

    Authorization error. User_token is not set.

    Authorization error. User API token is not specified in the request parameters. It is necessary to specify User API token ("user_token" field) in every request.

    403

    13

    Access denied. You have no access to the app %app id value%

    Access error. The owner specified in the User API token request has no access rights to the app specified in the same request.

    403

    15

    Access denied. You have no access to API.

    Access error. You have no access to devtodev API. The error can appear when User API token owner's access rights to the service API are changed as a result of the change of the user's role or the change of the price plan.

    403

    23

    Access denied. You have no access to Labels API.

    Access error. The error can appear in case the User API token owner has no access to the Labels API service due to the limitations of the user's role or limitations of the price plan of the space.

    400

    28

    Unexpected value for field %field%. Received value: %value%. Expected values: %values%.

    Unexpected value for the field. Follow the recommendations and correct the request.

    400

    29

    Unexpected field %field%

    The request contains the field that is not specified in the documentation. The field must be excluded from the request.

    400

    30

    Invalid value for field %field%. Received value: %value%. Expected: %description%

    Invalid value has been attributed to the field. Follow the recommendations and correct the request.

    404

    34

    No results matched the request

    The requested data is missing.

    429

    35

    Quota exceeded. %% new labels per day per application quota has been exceeded.

    The daily limit is exceeded. Tomorrow you will be able to add labels to the app again.

    400

    37

    New labels array should not contain more than %% elements.

    The array of labels contains more than 30 elements. Reduce the number of labels created in one request.

    429

    36

    Quota exceeded. %% new labels per day per user quota has been exceeded.

    The daily limit is exceeded. You will be able to add a new set of labels tomorrow.

    400

    38

    Labels feature is not available for cross-platform projects

    The labels feature is not available for cross-platform projects. Labels can be applied only to ordinary apps or branch-apps of cross-platform projects.

    Description

    receiptStatus

    Enum type DTDReceiptStatus that represents the result of the transaction validation.

    verificationResult

    Additional information from the validation server.

    DTDReceiptStatus

    The enum type returned as a result of validation can receive the following values:

    Value

    Description

    receiptValid

    The payment is valid, the transaction is genuine.

    receiptNotValid

    The payment is invalid, the transaction may be a duplicate or fraud.

    receiptServerError

    Server error when validating the payment.

    receiptSandbox

    Test payment.

    receiptInternalError

    Internal SDK error.

    We recommend calling the Real Currency Payment method in all cases except when you receive receiptNotValid or receiptSandbox as a result of the validation.

    Property

    Description

    receiptStatus

    Enum type DTDReceiptStatus that represents the result of the transaction validation.

    verificationResult

    Additional information from the validation server.

    DTDReceiptStatus

    The enum type returned as a result of validation can receive the following values:

    Value

    Description

    receiptValid

    The payment is valid, the transaction is genuine.

    receiptNotValid

    The payment is invalid, the transaction may be a duplicate or fraud.

    receiptServerError

    Server error when validating the payment.

    receiptInternalError

    Internal SDK error.

    We recommend calling the Real Currency Payment method in all cases except when you receive receiptNotValid as a result of the validation.

    Property

    Description

    receiptStatus

    Enum type DTDReceiptStatus that represents the result of the transaction validation.

    verificationResult

    Additional information from the validation server.

    DTDReceiptStatus

    The enum type returned as a result of validation can receive the following values:

    Value

    Description

    receiptValid

    The payment is valid, the transaction is genuine.

    receiptNotValid

    The payment is invalid, the transaction may be a duplicate or fraud.

    receiptServerError

    Server error when validating the payment.

    receiptInternalError

    Internal SDK error.

    We recommend calling the Real Currency Payment method in all cases except when you receive receiptNotValid as a result of the validation.

    The DTDVerifyResponse object returned while validating the transaction has two properties:

    Property

    Description

    ReceiptStatus

    Enum type DTDReceiptStatus that represents the result of the transaction validation.

    VerificationResult

    Additional information from the validation server.

    DTDReceiptStatus

    The enum type returned as a result of validation can receive the following values:

    Value

    Description

    Valid = 0L

    The payment is valid, the transaction went through successfully

    Invalid = 1L

    The payment is invalid, the transaction may be a duplicate or fraud

    ServerError = 2L

    Server error when validating the payment

    InternalError = 4L

    Internal SDK error

    We recommend calling the Real Currency Payment method in all cases except when you receive Invalid as a result of the validation.

    Here's how to find your app's public key for licensing (for Google Play platform only, for other platforms the publicKey is not used):
    1. Go to the Google Play Console and sign in. Make sure that you sign in to the account from which the app you are licensing is published (or will be published).

    2. In the app details page, locate the Services & APIs link and click it.

    3. In the Services & APIs page, locate the Licensing & In-App Billing section. Your public key for licensing is given in the Your License Key For This Application field.

    App Store

    If you use Unity IAP for payment validation, call the following method: void VerifyPayment(string receipt, Action completionHandler)

    Windows Store (UWP)

    If you use Unity IAP for payment validation, call the following method: void VerifyPayment(string receipt, Action completionHandler)

    N.B. You can pass a native XML recipe to the receipt argument.

    DTDVerifyResponse

    The DTDVerifyResponse object returned while validating the transaction has two properties:

    Property

    Description

    receiptStatus

    Enum type DTDReceiptStatus that represents the result of the transaction validation.

    verificationResult

    Additional information from the validation server.

    DTDReceiptStatus

    The enum type returned as a result of validation can receive the following values:

    Value

    Description

    receiptValid

    The payment is valid, the transaction is genuine.

    receiptNotValid

    The payment is invalid, the transaction may be a duplicate or fraud.

    receiptServerError

    Server error when validating the payment.

    receiptSandbox

    Test payment.

    receiptInternalError

    Internal SDK error.

    We recommend calling the Real Currency Payment method in all cases except when you receive receiptNotValid or receiptSandbox as a result of the validation.

    .purchased
    :
    DTDAntiCheat.verifyPayment { response in
    switch response.receiptStatus {
    case .receiptInternalError:
    // your code
    break
    case .receiptValid:
    // your code
    break
    case .receiptSandbox:
    // your code
    break
    case .receiptServerError:
    // your code
    break
    case .receiptNotValid:
    // your code
    break
    @unknown default: break
    }
    SKPaymentQueue.default().finishTransaction(transaction)
    }
    case .restored:
    SKPaymentQueue.default().finishTransaction(transaction)
    case .failed:
    SKPaymentQueue.default().finishTransaction(transaction)
    default:
    break
    }
    }
    }
    }

    Property

    Description

    receiptStatus

    Enum type DTDReceiptStatus that represents the result of the transaction validation.

    verificationResult

    Additional information from the validation server.

    Value

    Description

    receiptValid

    The payment is valid, the transaction is genuine.

    receiptNotValid

    The payment is invalid, the transaction may be a duplicate or fraud.

    receiptServerError

    Server error when validating the payment.

    receiptSandbox

    Test payment.

    receiptInternalError

    Internal SDK error.

    here

    Property

    - (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions{
        for(SKPaymentTransaction *transaction in transactions) {
            switch(transaction.transactionState){
                case SKPaymentTransactionStatePurchasing: {
                    // Your code ...
                    break;
                }
    
                case SKPaymentTransactionStatePurchased: {
                    // Your code ...
                    [DTDAntiCheat verifyPaymentCompletion:^(DTDVerifyResponse * _Nonnull response) {
                        switch ([response receiptStatus]) {
                            case ReceiptStatusReceiptInternalError: {
    
                                break;
                            }
                            case ReceiptStatusReceiptValid: {
    
                                break;
                            }
                            case ReceiptStatusReceiptSandbox: {
    
                                break;
                            }
                            case ReceiptStatusReceiptServerError: {
    
                                break;
                            }
                            case ReceiptStatusReceiptNotValid: {
    
                                break;
                            }
                        }
                    }];
                    break;
                }
    
                case SKPaymentTransactionStateRestored: {
                    // Your code ...
                    break;
                }
    
                case SKPaymentTransactionStateFailed: {
                    // Your code ...
                    break;
                }
    
                case SKPaymentTransactionStateDeferred: {
                    // Your code ...
                    break;
                }
            }
        }
    }
    DTDAntiCheat.verifyPayment(
        receipt = "receipt", 
        signature = "signature", 
        publickKey = "publickKey"
    ) { dtdVerifyResponse ->
        val verificationResult = dtdVerifyResponse.verificationResult
        /* your code here */
        when (dtdVerifyResponse.receiptStatus) {
            DTDReceiptStatus.ReceiptValid -> { /* your code here */ }
            DTDReceiptStatus.ReceiptNotValid -> { /* your code here */ }
            DTDReceiptStatus.ReceiptServerError -> { /* your code here */ }
            DTDReceiptStatus.ReceiptInternalError -> { /* your code here */ }
        }
    }
    DTDAntiCheat.INSTANCE.verifyPayment("receipt", "signature", "publickKey",
                    dtdVerifyResponse -> {
                    // your code
                        switch (dtdVerifyResponse.getReceiptStatus()) {
                            case ReceiptValid:
                                // your code
                                break;
                            case ReceiptNotValid:
                                // your code
                                break;
                            case ReceiptInternalError:
                                // your code
                                break;
                            case ReceiptServerError:
                                // your code
                                break;
                        }
                        return null;
                    });
    var result = await DTDAntiCheat.VerifyPayment("receipt");
    switch (result)
    {
        case DTDReceiptStatus.Valid:
            break;
        case DTDReceiptStatus.Invalid:
            break;
        case DTDReceiptStatus.ServerError:
            break;
        case DTDReceiptStatus.InternalError:
            break;
        default:
            throw new ArgumentOutOfRangeException();
    }
    public PurchaseProcessingResult ProcessPurchase (PurchaseEventArgs e)
    {
        DTDAntiCheat.VerifyPayment(yourPublicKey, e.purchasedProduct.receipt, result =>
        {
            if (result.ReceiptStatus == DTDReceiptVerificationStatus.ReceiptValid ||
                result.ReceiptStatus == DTDReceiptVerificationStatus.ReceiptInternalError || 
                result.ReceiptStatus == DTDReceiptVerificationStatus.ReceiptServerError)
            {
                // Code for valid result.
            }
            else
            {
                // Code for invalid result.
            }
        });
    }
    public void MyNativeCallback (string publicKey, string receipt, string signature)
    {
        DTDAntiCheat.VerifyPayment(publicKey, receipt, signature, result =>
        {
            if (result.ReceiptStatus == DTDReceiptVerificationStatus.ReceiptValid ||
                result.ReceiptStatus == DTDReceiptVerificationStatus.ReceiptInternalError || 
                result.ReceiptStatus == DTDReceiptVerificationStatus.ReceiptServerError)
            {
                // Code for valid result.
            }
            else
            {
                // Code for invalid result.
            }
        });
    }
    public PurchaseProcessingResult ProcessPurchase (PurchaseEventArgs e)
    {
        DTDAntiCheat.VerifyPayment(e.purchasedProduct.receipt, result =>
        {
            if (result.ReceiptStatus == DTDReceiptVerificationStatus.ReceiptValid ||
                result.ReceiptStatus == DTDReceiptVerificationStatus.ReceiptInternalError || 
                result.ReceiptStatus == DTDReceiptVerificationStatus.ReceiptServerError)
            {
                // Code for valid result.
            }
            else
            {
                // Code for invalid result.
            }
        });
    }
    public PurchaseProcessingResult ProcessPurchase (PurchaseEventArgs e)
    {
        DTDAntiCheat.VerifyPayment(e.purchasedProduct.receipt, result =>
        {
            if (result.ReceiptStatus == DTDReceiptVerificationStatus.ReceiptValid ||
                result.ReceiptStatus == DTDReceiptVerificationStatus.ReceiptInternalError || 
                result.ReceiptStatus == DTDReceiptVerificationStatus.ReceiptServerError)
            {
                // Code for valid result.
            }
            else
            {
                // Code for invalid result.
            }
        });
    }

    Sets a small custom user icon.

    Written in the manifest file <meta-data android:name="com.devtodev.default_small_icon_color" android:resource="@color/colorPrimary" />

    Sets a color of the small custom user icon.

    Written in the manifest file <meta-data android:name="com.devtodev.push.default_large_icon" android:resource="@mipmap/largeIcon" />

    Sets a large custom user icon.

    DTDMessaging.getToken()

    Returns a push notification registration token (firebaseToken).

    DTDMessaging.processPushNotification(Context context, RemoteMessage remoteMessage)

    Used to pass the push notification to the FirebaseMessagingService if it was implemented by the client but not by the SDK.

    DTDMessaging.setPushListener(DTDPushListener pushListener)

    (DTDPushListener pushListener) - sets a listener for push notification event trapping.

    The text body.

    group

    String?

    A group of messages.

    getSound(context: Context)

    Uri?

    Returns the storage path of an audio file.

    getSoundName()

    String?

    The notification sound name.

    tag

    String?

    The notification tag.

    color

    String?

    The notification color.

    bigPicture

    String?

    The notification banner if specified.

    actionType

    DTDActionType

    The property that returns an enum’s DTDActionType value.

    Possible values:

    • Url - an external link opening

    • Share - content sharing

    • Deeplink

    actionString

    String?

    The property that returns an optional action ID.

    getIcon(context: Context, userIcon: Int)

    Int

    The icon resource identifier specified by the user (if specified).

    largeIcon

    String?

    The large notification icon name.

    actions

    List<DTDActionButton>

    The list of action buttons used in the push notification.

    isApiSource

    Boolean

    Specifies whether the push notification was sent using the devtodev API.

    The property that returns the button’s icon name.

    isBackground

    Boolean

    The button-click app open mode.

    text

    String?

    The property that returns the text of the tapped button.

    Object

    Description

    DTDMessaging

    The main object for push notification initialization.

    DTDMessaging.initialize(Context context)

    The push notification initialization method.

    DTDMessaging.startPushService()

    The push notification activation method. It passes the isAllowed current state.

    DTDMessaging.pushNotificationsAllowed = true or false

    A property responsible for the activation/deactivation of push notifications.

    Functions as a getter (describes the current state) and a setter (sets the current state).

    When the state transitions, it sends a pt with isAllowed (true or false) status to the server.

    The isAllowed flag status is stored in the SDK.

    DTDMessaging.setIntent(Intent intent)

    A method of passing a user intent to the SDK using PushMessage.

    DTDPushListener Interface Methods

    Description

    onPushServiceRegistrationSuccessful(String deviceId)

    Returns a push notification registration token (firebaseToken).

    onPushServiceRegistrationFailed(String error)

    Returns errors during push notification registration.

    onPushNotificationReceived(Map<String, String> message)

    Returns a directory with data for improving your push notifications.

    onPushNotificationOpened(DTDPushMessage pushMessage, @Nullable DTDActionButton actionButton)

    Returns pushMessage and actionButton if they were tapped.

    Property

    Type

    Description

    getData()

    Map<String, String>

    Complete information sent with the use of a remote push notification.

    systemId

    Int

    The notification ID used in the devtodev system.

    getTitle(context:Context)

    String?

    Returns the selected message title or app name if the former is unavailable.

    body

    Property

    Type

    Description

    Id

    String?

    The property that returns the tapped button ID.

    actionString

    String?

    The property that returns the optional action ID.

    actionType

    DTDActionType

    The property that returns an enum’s DTDActionType value.

    Possible values:

    • App - a default value

    • Url - an external link opening

    • Share - content sharing

    • Deeplink - an in-app link opening

    icon

    GitHub repository

    Written in the manifest file <meta-data android:name="com.devtodev.push.default_small_icon" android:resource="@drawable/smallIcon" />

    String?

    String?

    21.jpg
    22.jpg
    23.jpg
    defaultConfig {
        //other existing fields 
        versionName = "1.0" //your app version (required)
    }
    
    dependencies {
        // Requirement
        implementation("androidx.appcompat:appcompat:*.*.*")
        implementation("com.google.code.gson:gson:*.*.*")
        implementation("com.google.firebase:firebase-messaging:*.*.*")
        implementation("com.google.android.gms:play-services-ads-identifier:*.*.*")
        
        // if you use AAR (recommended) or JAR downloaded from GitHub, please add:
        implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.aar"))))
        
        // or just add the dependency, get the latest version from
        // https://mvnrepository.com/artifact/com.devtodev/android-analytics
        implementation("com.devtodev:android-analytics:*.*.*")
        // https://mvnrepository.com/artifact/com.devtodev/android-messaging
        implementation("com.devtodev:android-messaging:*.*.*")
        
        // Optional (recommended)
        implementation("com.android.installreferrer:installreferrer:*.*")
    }
    defaultConfig {
        //other existing fields 
        versionName "1.0" //your app version (required)
    }
    
    dependencies {
        // Requirement
        implementation 'androidx.appcompat:appcompat:*.*.*'
        implementation 'com.google.code.gson:gson:*.*.*'
        implementation 'com.google.android.gms:play-services-ads-identifier:*.*.*'
        implementation 'com.google.firebase:firebase-messaging:*.*.*'
        
        // if you use AAR (recommended) or JAR downloaded from GitHub, please add:
        implementation fileTree(dir: "libs", include: ["*.aar"]) 
        
        // or just add the dependency, get the latest version from
        // https://mvnrepository.com/artifact/com.devtodev/android-analytics
        implementation 'com.devtodev:android-analytics:*.*.*'
        // https://mvnrepository.com/artifact/com.devtodev/android-messaging
        implementation 'com.devtodev:android-messaging:*.*.*'
        
        // Optional (recommended)
        implementation 'com.android.installreferrer:installreferrer:*.*'
    }
    <!-- permission.POST_NOTIFICATIONS for API level 33 or higher -->
    <uses-permission android:name=“android.permission.POST_NOTIFICATIONS”/>
    
    <application>
            <service
                android:name="com.devtodev.push.internal.logic.DTDFcmMessagingService"
                android:exported="true">
                <intent-filter>
                    <action android:name="com.google.firebase.MESSAGING_EVENT"/>
                </intent-filter>
            </service>
    
            <receiver
                android:name="com.devtodev.push.internal.logic.PushClickReceiver"
                android:enabled="true"
                android:exported="true">
                <intent-filter>
                    <action android:name="com.devtodev.android.push.CLICKED" />
                </intent-filter>
            </receiver>
    </application>
    <meta-data
     android:name="com.devtodev.push.default_small_icon"
     android:resource="@drawable/ic_icon_name" />
    
    <meta-data
     android:name="com.devtodev.push.default_small_icon_color"
     android:resource="@color/icon_color" />
    <meta-data
    android:name="com.devtodev.push.default_large_icon"
    android:resource="@mipmap/ic_large_icon_name"/>
    <application
        <!-- Your tags -->
        <service
            android:name="com.devtodev.push.internal.logic.DTDFcmMessagingService">
            <intent-filter>
                <action android:name="com.google.firebase.MESSAGING_EVENT" />
            </intent-filter>
        </service>
    
        <receiver
            android:name="com.devtodev.push.internal.logic.PushClickReceiver"
            android:enabled="true"
            android:exported="true">
            <intent-filter>
                <action android:name="com.devtodev.android.push.CLICKED" />
            </intent-filter>
        </receiver>
    
        <meta-data
            android:name="com.devtodev.push.default_small_icon"
            android:resource="@drawable/ic_baseline" />
    
        <meta-data
            android:name="com.devtodev.push.default_small_icon_color"
            android:resource="@color/colorPrimary" />
    
        <meta-data
            android:name="com.devtodev.push.default_large_icon"
            android:resource="@mipmap/baseline_accessibility_black_18"/>
    </application>
    val analyticsConfiguration = DTDAnalyticsConfiguration()
    analyticsConfiguration.logLevel = DTDLogLevel.Error
    
    DTDAnalytics.initialize(
        appKey = "projectKey",
        analyticsConfiguration = analyticsConfiguration,
        context = this
    )
    DTDMessaging.initialize(context = this)
    DTDAnalyticsConfiguration analyticsConfiguration = new DTDAnalyticsConfiguration();
    analyticsConfiguration.setLogLevel(DTDLogLevel.Error);
    DTDAnalytics.INSTANCE.initialize("projectKey",analyticsConfiguration,context);
    DTDMessaging.INSTANCE.initialize(context);
    DTDMessaging.setPushListener(object : DTDPushListener {
          override fun onPushServiceRegistrationSuccessful(deviceId: String) {
              //do something
          }
          override fun onPushServiceRegistrationFailed(error: String) {
              //do something
          }
          override fun onPushNotificationReceived(message: Map<String, String?>?) {
              //do something
          }
          override fun onPushNotificationOpened(pushMessage: DTDPushMessage, actionButton: DTDActionButton?) {
              //do something
          }
    })
    DTDMessaging.INSTANCE.setPushListener(new DTDPushListener() {
        @Override
        public void onPushServiceRegistrationSuccessful(@NonNull String deviceId) {
            //do something            
        }
    
        @Override
        public void onPushServiceRegistrationFailed(@NonNull String error) {
            //do something
        }
    
        @Override
        public void onPushNotificationReceived(@Nullable Map<String, String> message) {
            //do something
        }
    
        @Override
        public void onPushNotificationOpened(@NonNull DTDPushMessage dtdPushMessage, @Nullable DTDActionButton dtdActionButton) {
            //do something
        }
    });
    <uses-permission android:name=“android.permission.POST_NOTIFICATIONS”/>
    @RequiresApi(33)
    private fun notificationPermissionIsGranted(): Boolean {
        val res = context.checkCallingOrSelfPermission(POST_NOTIFICATIONS)
        return res == PackageManager.PERMISSION_GRANTED
    }
    @RequiresApi(33)
    private Boolean notificationPermissionIsGranted() {
        int res = context.checkCallingOrSelfPermission(POST_NOTIFICATIONS);
        return res == PackageManager.PERMISSION_GRANTED;
    }
    requestPermissions(arrayOf(Manifest.permission.POST_NOTIFICATIONS), yourCode)
    String[] permissionsArr = {Manifest.permission.POST_NOTIFICATIONS};
    requestPermissions(permissionsArr, yourCode);
    override fun onRequestPermissionsResult(
        requestCode: Int,
        permissions: Array<String?>,
        grantResults: IntArray
    ) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)
        if (requestCode == yourCode) {
            if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                //permission is granted
            } else {
                //permission is not granted, notifications are not available
            }
        }
    }
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        if (requestCode == yourCode) {
            if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                //permission is granted
            } else {
                //permission is not granted, notifications are not available
            }
        }
    }
    - an in-app link opening

    iOS

    Platform integration

    Creating a Universal Push Notification Client SSL Certificate

    You use Member Center to generate a push notification client SSL certificate that allows your notification server to connect to the APNs. Each App ID is required to have its own client SSL certificate. The client SSL certificate Member Center generates is a universal certificate that allows your app to connect to both the development and production environments.

    Only a team agent or admin can generate Apple Push Notification service SSL certificates.

    To generate a universal client SSL certificate

    1. In , select Certificates.

    2. Click the Add button (+)

    3. Under Production, select the “Apple Push Notification service SSL (Sandbox & Production)” checkbox, and click Continue.

    Follow these steps to export the certificate from Apple web-site to the P12-file:

    1. Open "Keychain access" application

    2. If the certificate hasn't been added to keychain access yet, choose "File" → "Import". Find the certificate file (CER-file) provided by Apple

    3. Choose "Keys" section in "Keychain access" application

    4. Choose a personal key associated with your iPhone developer certificate. Personal key is identified by open certificate associated with it "iPhone developer: ". Choose "File" → Export objects. Save key as .p12

    Upload the certificate to the site

    Upload the .p12-file into Integration section of application settings panel (Settings -> Push Notifications):

    Messaging Module Integration (CocoaPods)

    is the easiest way to add devtodev into your iOS project.

    1. Firstly, install CocoaPods using:

    2. In the project directory execute the command:

    3. In the created Podfile add the dependency:

    4. Finally, run the command in your Xcode project directory:

    CocoaPods should download and install the devtodev library, and create a new Xcode workspace. Open this workspace in Xcode.

    Messaging Module Integration (Manual Installation)

    To connect the module for processing push notifications, you need to:

    1. .

    2. Add DTDAnalytics.xcframework to the project (check Do Not Embed).

    The DTDMessaging plugin will not work without the DTDAnalytics main analytics plugin.

    1. Add DTDMessaging.xcframework to the project (check Do Not Embed)

    1. In the Xcode project settings, open the tab, and add: "Push Notifications" and "Background Modes" respectively.

    1. In the "Background Modes" section, enable "Remote notifications".

    1. Add initialization to didFinishLaunchingWithOptions method:

    1. To handle SDK delegate methods, you need to add the implementation of the DTDMessagingDelegate protocol.

    The DTDMessaging module provides support for notifications with attachments. These notifications are available since iOS 10. Attachments support images, animated gifs and videos. To use this function, you will need to create a “Notification Service Extension“, for this create a new target in your application settings:

    • Open Xcode (File -> New -> Target). Select Notification Service Extension.

    The next step is to modify the Notification Extension class as follows:

    • Delete all auto-generated code.

    • Inherit Notification Extension class from DTDMediaAttachmentExtension

    Example:

    External interface of the DTDMessaging module

    Optional DTDMessagingDelegate methods

    Class for receiving Notification data (DTDMessage)

    Class for handling pressed buttons on a notification (DTDActionButton)

    Notes

    Foreground Notification

    Displaying push notifications in Foreground state is available since iOS 10.0.

    By default, according to Apple Push Notification Guidelines, the display of push notification in the Foreground state is disabled. In order to set the display method, you must:

    • Assign delegate for UNUserNotificationCenter

    • Or pass a parameter in the push notification constructor (_fg = 1). In this case, the display method will be formed from the Notification settings.

    • Or delegate the system method userNotificationCenter willPresent notification

    An example of a delegated method implementation:

    DTDNotificationOptions

    It is an OptionSet that is used to pass push notification authorization and set up interactions with users.

    The user can change the allowed parameters at any time in the notification settings.

    Possible values:

    DTDNotificationOptionProvisional - Provisional push notifications appear in the user's Notification Center, but not on the lock screen. This type of push notification does not have to be explicitly allowed by the user. Start submitting them as soon as the user installs and runs your application. However, the user can also opt in/opt out of notifications, but they will need to do it explicitly.

    This setting is used to prevent receiving push notification permission requests at the start, which is intrusive and most users refuse to receive them.

    It is also important that when using the DTDNotificationOptionProvisional setting, the user will be able to subscribe to explicit notifications only from the notification center settings.

    DTDMediaAttachmentExtension

    To handle the application attitude to displaying push notifications, you need to add a Notifications service Extension. And inherit NotificationService from DTDMediaAttachmentExtension

    Custom sounds

    To use custom sounds for push notifications, you need to copy the necessary sound files to the Libraries folder of your Xcode project. You can read more about supported formats .

    When creating a push company, it is important to specify the full name of the sound with file extension (for example, sound.wav).

    DTDMessaging integration with swizzling disabled

    DTDMessaging automatically swizzles notification tracking and APNS-token usage methods by default. If you want to disable the function, add the flag DTDMessagingSwizzlingEnabled (boolean) in the app’s Info.plist file and set it to NO (0). However, if you disable notification swizzling, you will need additional integration for accurate analytics:

    For sending APNS-token to DTDMessaging:

    For sending information about the application:

    Note: If you disable automatic notification swizzling, DTDMessagingDelegate methods will not be called.

    Windows UWP

    devtodev Push API supports two different formats of notification sending to Windows operational systems:

    • UWP format described in this section can be used only for Windows 10 and Windows Phone 10. It is described by the "uwp" object.

    • The universal format works on a basis of Legacy tiles and toast schema. It can be used for Windows Phone 8.1, Windows Phone 10, Windows 8.1, Windows 10. It is described by the "windows" object.

    Use this object (“uwp”) if your clients are users of OS Windows 10 and Windows Phone 10 only. With the help of this method it is possible to realize more opportunities offered by Windows 10.

    User identifiers

    In order to find an addressee to whom you need to send a notification, it is possible to specify one or several available identifiers:

    Notification settings

    Object "uwp" can contain 2 properties:

    1. The property that describes a message can be represented by one of 4 possible objects: - toast (object) - the result of sending is the delivery of toast-notification - raw (object) - sends a hidden toast-notification - tile (object) - changes the tile content of an app - badge (object) - changes the value of a badge field displayed on a tile

    2. The property that describes additional parameters of a message is represented by the object “options”. Optional.

    Toast-notification content ("toast" object properties):

    Example

    Tile ("tile" object properties)

    In UWP format every of tile’s size must be described separately, therefore, the object “tile” can contain up to 4 properties:

    • small

    • medium

    • wide

    • large (only for desktop)

    Each of these sizes is described by an object with the following properties:

    Example

    Hidden notification ("raw" object properties):

    Example

    Badge ("badge" object properties):

    Example

    24.jpg
    Choose an App ID from the App ID pop-up menu, and click Continue. Choose the explicit App ID that matches your bundle ID.
  • Create a certificate request on your Mac.

  • Click Choose File.

  • In the dialog that appears, select the certificate request file (with a .certSigningRequest extension), and click Choose File.

  • Click Generate.

  • Click Download.

  • You'll be suggested to create a password which is used when you need to import the key to another computer

    Property for assigning a delegate to handle events from the SDK

    pushNotificationsOptions

    DTDNotificationOptions

    Configuring the display of Push Notifications, is an OptionSet, is set by the developer to select the method for notifying the user. May be changed by the end user.

    By default it has the following value: [.DTDNotificationOptionBadge, .DTDNotificationOptionSound, .DTDNotificationOptionAlert]

    The value that is passed to display a badge on the application icon.

    category

    String?

    Property returning an optional identifier of a push notification category

    Property returning the text of the pressed button.

    . In this case, the display properties will be taken from the developer.

    an option specifying that the system should display a button for notification settings in the application. Available since iOS 12.0

    DTDNotificationOptionProvisional

    pre-send notifications to the Action Center without interrupting work. Available since iOS 12.0

    DTDNotificationOptionAnnouncement

    for Siri to automatically read messages through AirPods. Available since iOS 13.0

    Object

    Type

    Description

    startPushService

    Method responsible for activating push notifications:

    • Requests permission from the user to receive Push Notifications

    • Sends a pushToken and the current state isAllowed

    apnsToken

    Data?

    Getter giving the current pushToken, represented by a Data

    apnsTokenString

    String?

    Getter giving the current pushToken, represented by a String

    delegate

    Delegate method

    Description

    didFailToRegisterForRemoteNotifications(with error: Error)

    The method is called when an error occurs at the time of receiving a pushToken, represented by the Error base class.

    didOpenRemoteNotification(with message: DTDMessage, and buttonClicked: DTDActionButton?)

    The method is called when a remote push notification is opened by an end user. A DTDMessage object and an optional DTDActionButton object are passed.

    didReceiveForegroundNotification(with message: DTDMessage)

    The method is called when a remote push notification is received when the application is in the Foreground state. The DTDMessage object is passed.

    didReceiveInvisibleNotification(with message: DTDMessage)

    The method is called when an invisible remote push notification is received. The DTDMessage object is passed.

    didRegisterForRemoteNotifications(with deviceToken: Data)

    The method is called in case the pushToken successfully arrives; represented by a Data.

    Property

    Type

    Description

    payload

    [AnyHashable:Any]

    Full information sent using remote push notification.

    actionType

    DTDActionType

    Property returning the enum DTDActionType value. Possible values:

    • App - default value

    • Url - open an external link

    • Share - share content

    • Deeplink - open a link inside the application

    actionString

    String?

    Property returning an optional action identifier.

    badge

    Property

    Type

    Description

    actionType

    DTDActionType

    Property returning the enum DTDActionType value. Possible values:

    • App - default value

    • Url - open an external link

    • Share - share content

    • Deeplink - open a link inside the application

    actionString

    String?

    Property returning an optional action identifier.

    buttonId

    String

    Property returning the identifier of the pressed button.

    text

    Value

    Description

    DTDNotificationOptionBadge

    display a badge on the application icon

    DTDNotificationOptionSound

    play sound

    DTDNotificationOptionAlert

    display an alert

    DTDNotificationOptionCarPlay

    display push notification in CarPlay

    DTDNotificationOptionCriticalAlert

    play sound for critical notifications regardless of the “Do Not Disturb” setting (Critical alerts require special permission from Apple.) Available since iOS 12.0

    Certificates, Identifiers & Profiles
    CocoaPods
    Download the latest devtodev SDK from the repository
    here

    DTDMessagingDelegate?

    Int

    String

    DTDNotificationOptionProvidesSettings

    User id is applicable if an internal identifier (cross-platform user identifier) is used in your app.

    devtodevId

    number

    Numeric user identifier in devtodev database.

    Optional. Additional text of a notification.

    data

    object

    Optional if notification is not hidden. You can pass custom parameters with messages and use them within an app. For instance, you can activate advertising campaign or any other functionality for user who has received this message.

    Example:

    "data": { "my_key": "value", "my_another_key": "15" }

    icon

    string

    Optional. To replace the application icon (that shows up on the top left corner of the toast) use the URL or the resource name.

    In Windows 10 the image is expressed using the URI of the image source, using one of these protocol handlers:

    • A web-based image: http:// or https://

    • An image included in the app package: ms-appx:///

    • An image saved to local storage: ms-appdata:///local/

    image

    string

    Optional. Image inside the toast body, below the text. Use the URL or the resource name.

    sound

    string

    Optional. The media file to play in place of the default sound. If the option is not used, the notification comes silently. This property can have one of the following string values:

    • Default IM

    • Mail

    • Reminder

    action

    object

    Optional. Action after a click on the body of a notification. By default, a click simply opens an app. It is also possible to perform the following actions:

    • deeplink - Direct the user to a specific resource, either within your app or on the web.

    • url - Open a web page in a mobile browser, or any valid device-level URL such as Windows Store or app protocol links.

    • share - The Share Action drives a user to share your message when they interact with your push notification.

    interactive

    object

    Optional. It is possible to specify an array of notification buttons in the “interactive” object. Each button must contain the following properties:

    • id (string) - Button identifier. It is transferred to an app

    • text (string) - Text on a button

    • handling (string) - The type of processing

    Optional. You can set a black overlay on your background image using this property, which accepts integers from 0-100 with 0 being no overlay and 100 being full black overlay.

    If you don't specify an overlay, the background image opacity defaults to 20% and the peek image opacity defaults to 0%.

    Property

    Type

    Description

    token

    string

    Push token. If you use push token, all fields with other identifiers will be ignored!

    advertisingId

    string

    Advertising ID

    serialId

    string

    Hardware serial number

    userId

    Property

    Type

    Description

    scenario

    string

    Optional. Available values: "default", "alarm", "reminder", "incomingCall". Default value is "default". You do not need this unless your scenario is to pop an alarm, reminder, or incoming call. Do not use this just for keeping your notification persistent on screen.

    title

    string

    Required. A short string describing the purpose of a notification.

    text

    string

    Required. Main text of a notification.

    text2

    Property

    Type

    Description

    content

    array

    As long as tile’s content is dynamic, it is described by an array, the elements of which can be objects describing either text strings or illustrations.

    Text element object properties:

    • text (string) - Required. The displayed string.

    • wrap (boolean) - Optional. By default, text doesn't wrap and will continue off the edge of the tile. Set true to set text wrapping on a text element.

    • style (string ) - Optional. Styles control the font size, color, and weight of text elements. There is a number of available styles including a "subtle" variation of each style that sets the opacity to 60%, which usually makes the text color a shade of light gray. Basic text styles:

      • caption (12 effective pixels height, regular weight)

      • body (15 epx, regular)

      • base (15 epx, semibold)

      • subtitle (20 epx, regular)

      Numeral text style variations: (These variations reduce the line height so that content above and below come much closer to the text.)

      • titleNumeral

      • subheaderNumeral

      • headerNumeral

      Subtle text style variations: (Each style has a subtle variation that gives the text a 60% opacity, which usually makes the text color a shade of light gray.)

      • captionSubtle

      • bodySubtle

      • baseSubtle

      • subtitleSubtle

    Image element object properties

    • image (string) - Required. Image URI

    • remove_margin (boolean ) - Optional. By default, inline images have an 8-pixel margin between any content above or below the image. Set true to remove margin.

    • align (string) - Optional. Images can be set to align "left", "center", or "right". This will also cause images to display at their native resolution instead of stretching to fill width.

    Group element object properties

    • group (array) - Required. Array of subgroup elements.

    Subgroup element object (used inside groups only)

    • subgroup (array) - Required. Array of text or/and image elements.

    branding

    object

    You can control the branding on the bottom of a live tile (the display name and corner logo) by using this property. Branding object properties:

    • type (string) - You can choose to display "none", only the "name", only the "logo", or both with "nameAndLogo". Windows Mobile doesn't support a corner logo, so "logo" and "nameAndLogo" default to"name" on mobile.

    • display_name (string) - Optional. You can override the display name of a notification by entering the text string of your choice.

    v_align

    string

    You can control the vertical alignment of content on your tile by using this property. By default, everything is vertically aligned to the "top", but you can also align content to the "bottom" or "center".

    overlay

    Property

    Type

    Description

    data

    object

    Required. You can pass custom parameters with messages and use them within an app. For instance, you can activate advertising campaign or any other functionality for user who has received this message.

    Example:

    "data": { "my_key": "value", "my_another_key": "15" }

    badge

    string

    Optional. A number from 1 to 99. A value of 0 is equivalent to the glyph value "none" and will clear a badge.

    Instead of a number, a badge can display one of a non-extensible set of status glyphs:

    • activity

    • none

    • alarm

    • alert

    • attention

    • available

    • away

    • busy

    • error

    • newMessage

    • paused

    • playing

    • unavailable

    It is also possible to send values incrementing or decrementing the current value in a “+2”, “-1” format. In case the previous value was the glyph or 0, the value will be increment. Decrement does not influence the zero value and the glyph.

    Property

    Type

    Description

    badge

    string

    A number from 1 to 99. A value of 0 is equivalent to the glyph value "none" and will clear a badge.

    Instead of a number a badge can display one of a non-extensible set of status glyphs:

    • activity

    • none

    • alarm

    • alert

    • attention

    • available

    • away

    • busy

    • error

    • newMessage

    • paused

    • playing

    • unavailable

    string

    string

    number

    IOS

    User identifiers

    In order to find a user to whom you need to send a notification, it is possible specify one or several available identifiers:

    sudo gem install cocoapods
    pod init
    platform :ios, '9.0'
    
    target 'TargetName' do
      use_frameworks!
      pod 'DTDAnalytics', '~> 2.0.0'
      pod 'DTDMessaging', '~> 2.0.0'
    end
    pod install
    let config = DTDAnalyticsConfiguration()
    config.logLevel = .error
    DTDAnalytics.initialize(applicationKey: "App ID", configuration: config)
    
    DTDMessaging.delegate = self
    DTDMessaging.pushNotificationsOptions = [.DTDNotificationOptionAlert,
                                             .DTDNotificationOptionSound,
                                             .DTDNotificationOptionBadge]
    DTDMessaging.startPushService()
    DTDNotificationOptions *options = [[DTDNotificationOptions alloc] initWithRawValue:
                                      [DTDNotificationOptions.DTDNotificationOptionAlert rawValue] |
                                      [DTDNotificationOptions.DTDNotificationOptionSound rawValue] |
                                      [DTDNotificationOptions.DTDNotificationOptionBadge rawValue]];
    [DTDMessaging setPushNotificationsOptions:options];
    [DTDMessaging startPushService];
    extension AppDelegate: DTDMessagingDelegate {
        func didRegisterForRemoteNotifications(with deviceToken: Data) {
            // your code
        }
    
        func didFailToRegisterForRemoteNotifications(with error: Error) {
            // your code
        }
    
        func didReceiveInvisibleNotification(with message: DTDMessage) {
            // your code
        }
    
        func didReceiveForegroundNotification(with message: DTDMessage) {
            // your code
        }
    
        func didOpenRemoteNotification(with message: DTDMessage, and buttonClicked: DTDActionButton?) {
            // your code
        }
    }
    @interface AppDelegate () <DTDMessagingDelegate>
    
    - (void)didRegisterForRemoteNotificationsWith:(NSData *)deviceToken {
        // your code
    }
    
    - (void)didFailToRegisterForRemoteNotificationsWith:(NSError *)error {
        // your code
    }
    
    -(void)didReceiveInvisibleNotificationWith:(DTDMessage *)message {
        // your code
    }
    
    - (void)didReceiveForegroundNotificationWith:(DTDMessage *)message {
        // your code
    }
    
    -(void)didOpenRemoteNotificationWith:(DTDMessage *)message and:(DTDActionButton *)buttonClicked {
        // your code
    }
    import UserNotifications
    import DTDMessaging
    
    class NotificationService: DTDMediaAttachmentExtension {
    
    }
    func userNotificationCenter(_ center: UNUserNotificationCenter, 
                                willPresent notification: UNNotification, 
                                withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
      completionHandler([.alert, .sound])
    }
    @available(iOSApplicationExtension 10.0, *)
    class NotificationService: DTDMediaAttachmentExtension {
      // nothing
    }
    func application(_ application: UIApplication, 
                    didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
      DTDMessaging.apnsToken = deviceToken
    }i
    - (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
        [DTDMessaging setApnsToken:deviceToken];
    }
    func application(_ application: UIApplication,
                    didReceiveRemoteNotification userInfo: [AnyHashable: Any]) {
      DTDMessaging.didReceiveMessage(userInfo: userInfo, actionIdentifier: nil)
    }
    
    @available(iOS 10.0, *)
    extension AppDelegate: UNUserNotificationCenterDelegate {
      func userNotificationCenter(_ center: UNUserNotificationCenter, 
                                  didReceive response: UNNotificationResponse, 
                                  withCompletionHandler completionHandler: @escaping () -> Void) {
        let userInfo = response.notification.request.content.userInfo
        let actionIdentifier = response.actionIdentifier
        DTDMessaging.didReceiveMessage(userInfo: userInfo, actionIdentifier: actionIdentifier)
      }
    
      func userNotificationCenter(_ center: UNUserNotificationCenter, 
                                  willPresent notification: UNNotification, 
                                  withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
        let userInfo = notification.request.content.userInfo
        DTDMessaging.willPresentMessage(userInfo: userInfo)
      }
    }
    - (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
        [DTDMessaging didReceiveMessageWithUserInfo:userInfo actionIdentifier:nil];
    }
    
    - (void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler {
        NSDictionary *userInfo = notification.request.content.userInfo;
        [DTDMessaging willPresentMessageWithUserInfo:userInfo];
    }
    
    - (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)(void))completionHandler {
        NSDictionary *userInfo = response.notification.request.content.userInfo;
        NSString *actionIdentifier = response.actionIdentifier;
    
        [DTDMessaging didReceiveMessageWithUserInfo:userInfo actionIdentifier:actionIdentifier];
    }
    {
        "user_token": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
        "app_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
        "campaign_tag": "campaign name",
        "pack_id": "uniqueid1234",
        "audience": [{
            "token": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
            "advertisingId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
            "serialId": "xxxxxxxxx",
            "customUid": "xxxxxxxxxxxx"
        }],
        "uwp": {
            "toast": {
                "scenario": "default",
                "title": "Notification title",
                "text": "First notification string",
                "text2": "Second notification string",
                "data": {
                    "key1": "value",
                    "key2": "value"
                },
                "icon": "https://domain.com/pic.png",
                "image": ["https://domain.com/pic.png", ""],
                "sound": "Default",
                "action": {
                    "url": "http://www.domain.com"
                },
                "interactive": {
                    "buttons": [{
                        "id": "accept",
                        "text": "Accept",
                        "handling": "foreground",
                        "action": {
                            "url": "http://www.domain.com/accept"
                        }
                    }, {
                        "id": "decline",
                        "text": "Decline",
                        "handling": "background"
                    }]
                }
            },
            "options": {
                "expire": 3600
            }
        }
    }
    {
        "user_token": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
        "app_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
        "campaign_tag": "campaign name",
        "pack_id": "uniqueid1234",
        "audience": [{
            "token": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
            "advertisingId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
            "serialId": "xxxxxxxxx",
            "customUid": "xxxxxxxxxxxx"
        }],
        "uwp": {
            "tile": {
                "medium": {
                    "content": [{
                        "text": "First sting",
                        "wrap": "true",
                        "style": "base",
                        "align": "center"
                    }, {
                        "image": "https://domain.com/pic.png",
                        "remove_margin": "true",
                        "align": "center",
                        "crop": "circle",
                        "placement": "background"
                    }],
                    "v_align": "center",
                    "overlay": "60",
                    "branding": {
                        "type": "none"
                    }
                },
                "wide": {
                    "content": [{
                        "text": "First string",
                        "wrap": "true",
                        "style": "base",
                        "h_align": "center"
                    }, {
                        "image": "Assets\\Apps\\Weather\\MostlyCloudy.png",
                        "remove_margin": "true",
                        "align": "center",
                        "crop": "circle",
                        "placement": "background"
                    }],
                    "v_align": "center",
                    "branding": {
                        "type": "nameAndLogo",
                        "display_name": "brandname"
                    }
                }
            },
            "options": {
                "expire": 3600
            }
        }
    }
    {
        "user_token": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
        "app_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
        "campaign_tag": "campaign name",
        "pack_id": "uniqueid1234",
        "audience": [{
            "token": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
            "advertisingId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
            "serialId": "xxxxxxxxx",
            "customUid": "xxxxxxxxxxxx"
        }],
        "uwp": {
            "raw": {
                "data": {
                    "key1": "value",
                    "key2": "value"
                },
                "badge": "+1"
            }
        }
    }
    {
        "user_token": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
        "app_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
        "campaign_tag": "campaign name",
        "pack_id": "uniqueid1234",
        "audience": [{
            "token": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
            "advertisingId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
            "serialId": "xxxxxxxxx",
            "customUid": "xxxxxxxxxxxx"
        }],
        "uwp": {
            "badge": {
                "badge": "99"
            }
        }
    }

    title (24 epx, semilight)

  • subheader (34 epx, light)

  • header (46 epx, light)

  • titleSubtle

  • titleNumeralSubtle

  • subheaderSubtle

  • subheaderNumeralSubtle

  • headerSubtle

  • headerNumeralSubtle

  • crop (string ) - Optional. Default value is "none". Images can be cropped into a circle in case of "circle" value.
  • placement (string) - Optional. You can specify an image that "peeks” in from the top of a tile or set a "background" image.

  • A local image (Only supported for desktop apps.): file://

    SMS

  • Looping.Alarm

  • Looping.Alarm2

  • Looping.Alarm3

  • Looping.Alarm4

  • Looping.Alarm5

  • Looping.Alarm6

  • Looping.Alarm7

  • Looping.Alarm8

  • Looping.Alarm9

  • Looping.Alarm10

  • Looping.Call

  • Looping.Call2

  • Looping.Call3

  • Looping.Call4

  • Looping.Call5

  • Looping.Call6

  • Looping.Call7

  • Looping.Call8

  • Looping.Call9

  • Looping.Call10

  • On mobile platform this property can also contain the path to a local audio file with one of the following prefixes:

    • ms-appx:///

    • ms-appdata:///

    Examples:

    "action": { "url": "http://www.domain.com" } "action": { "deeplink": "your-url-scheme://host/path" }

    "action": { "share": "Happy holidays!" }

    background - A button closes an app. Notification parameters are transferred to an app, but an app doesn’t open. It is impossible to tie an action to a button in this regime.

  • foreground - A button opens an app. Notification parameters are transferred to an app, but an app doesn’t open. It is possible to tie an action to a button in this regime. The list of actions and the principle is analogical to actions with the body of a message.

  • Example:

    "interactive": { "buttons": [{ "id": "accept", "text": "Accept", "handling":"foreground", "action": { "url": "http://www.domain.com/accept" }, { "id": "decline", "text": "Decline", "handling":"background", }] }

    Push token. If you use a push token, all fields with other identifiers will be ignored!

    idfa

    string

    Ad identifier IDFA

    idfv

    string

    Device identifier under vendor IDFV

    userId

    string

    User id is applicable if an internal identifier (cross-platform user identifier) is used in your app

    devtodevId

    number

    Numeric user identifier in devtodev database.

    Notification settings

    The object of a message for the iOS platform contains 2 fields:

    • payload (object) - describes the main content of a notification

    • options (object) - describes the optional settings of notification delivery

    Notification content settings ("payload" object)

    Property

    Type

    Description

    title

    string

    Optional. A short string describing the purpose of a notification. Apple Watch displays this string as a part of the notification interface. This string is displayed only briefly and should be crafted so that it can be understood quickly. This key was added in iOS 8.2.

    text

    string

    The text of an alert message. Required if a notification is not hidden.

    title-loc-key

    string

    Optional. The key to a title string in the Localizable.strings file for the current localization. The key string can be formatted with %@ and %n$@ specifiers to take the variables specified in the title-loc-args array. See for more information. This key was added in iOS 8.2.

    title-loc-args

    Notification delivery and display settings (“options” object)

    Property

    Type

    Description

    sandbox

    boolean

    Default value is false ("Production" gateway). Set to true if you need to send notification through the "Sandbox" gateway (for builds signed with a developer certificate).

    hidden

    boolean

    Switching a notification to the hidden mode. If “true” - a notification will not be displayed to a user, but will be transferred to an app. Such a message must not contain any properties except “data” in the “payload” object.

    Including this key and value means that when your app is launched in the background or resumed, is called

    priority

    string

    Optional. The priority of a notification. Default value is "normal". Specify one of the following values:

    • "high" – Send a push message immediately. Notifications with this priority must trigger an alert, sound, or badge on a target device. It is an error to use this priority for a push notification that contains only the content-available key.

    • "normal" — Send a push message at a time that takes into account power considerations for a device. Notifications with this priority might be grouped and delivered in bursts. They are throttled, and in some cases are not delivered.

    expire

    Button templates

    Name

    Description

    Template ID

    Button 1

    Button 2

    Label

    id

    Label

    id

    The text on the buttons, which are on the list of templates, is translated into N languages and displayed to users individually according to the location of a device.

    Examples

    Push

    POST

    Hidden push

    POST

    Property

    Type

    Description

    token

    string

    ​https://devtodev.com/api/v1/push/send
    {
        "user_token": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
        "app_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
        "campaign_tag": "campaign name",
        "pack_id": "uniqueid1234",
        "audience": [{
            "token": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
            "idfa": "XXXXX-XXXXX-XXXXXX-XXXXXX",
            "idfv": "XXXXX-XXXXX-XXXX-XXXXX-XXXX",
            "userId": "xxxxxxxxxxxx"
        }],
        "ios": {
            "payload": {
                "title": "Title of the notification",
                "text": "Notification content.",
                "data": {
                    "key1": "value",
                    "key2": "15"
                },
                "badge": 11,
                "sound": "bingbong.aiff",
                "attachment": {
                    "type": "image",
                    "url": "https://domain.com/pic.png"
                },
                "action": {
                    "url": "http://www.domain.com"
                },
                "interactive": {
                    "template": "dtd_accept.open_decline.dismiss",
                    "buttons": [{
                        "id": "accept",
                        "action": {
                            "url": "http://www.domain.com/accept"
                        }
                    }]
                }
            },
            "options": {
                "hidden": false,
                "priority": "normal",
                "expire": "36000",
                "collapse_key": "qwerqwer"
            }
        }
    }
    ​https://devtodev.com/api/v1/push/send
    {
        "user_token": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
        "app_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
        "campaign_tag": "campaign name",
        "pack_id": "uniqueid1234",
        "audience": [{
            "token": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
            "idfa": "XXXXX-XXXXX-XXXXXX-XXXXXX",
            "idfv": "XXXXX-XXXXX-XXXX-XXXXX-XXXX",
            "userId": "xxxxxxxxxxxx"
        }],
        "ios": {
            "payload": {
                "data": {
                    "key1": "value",
                    "key2": "15"
                },
                "badge": "11"
            },
            "options": {
                "hidden": true,
                "priority": "normal",
                "expire": "36000"
            }
        }
    }

    string

    Optional. Variable string values to appear in place of format specifiers in title-loc-key. See Localized Formatted Strings for more information. This key was added in iOS 8.2.

    action-loc-key

    string

    Optional. If a string is specified, the system displays an alert that includes the “Close” and “View” buttons. The string is used as a key to get a localized string in the current localization to use for the right button’s title instead of “View”. See Localized Formatted Strings for more information.

    loc-key

    string

    Optional. The key to an alert message string in a Localizable.strings file for the current localization (which is set by the user’s language preferences). The key string can be formatted with %@ and %n$@ specifiers to take the variables specified in the loc-args array. See Localized Formatted Strings for more information.

    loc-args

    array

    Optional. Variable string values to appear in place of format specifiers in loc-key. See Localized Formatted Strings for more information.

    launch-image

    string

    Optional. The filename of an image file in the app bundle, with or without the filename extension. The image is used as a launch image when users tap the action button or move the action slider. If this property is not specified, the system either uses the previous snapshot, uses the image identified by the UILaunchImageFile key in the app’s Info.plist file, or falls back to Default.png. This property was added in iOS 4.0.

    data

    object

    Optional if notification is not hidden. You can pass custom parameters with messages and use them within an app. For instance, you can activate advertising campaign or any other functionality for the user who has received this message.

    Example:

    "data": { "my_key": "value", "my_another_key": 15 }

    badge

    number

    Optional. The number to display as the badge of the app icon. If this property is absent, the badge is not changed. To remove the badge, set the value of this property to 0.

    sound

    string

    Optional. The name of a sound file in the app bundle or in the Library/Sounds folder of the app’s data container. The sound in this file is played as an alert. If the sound file doesn’t exist or "default" is specified as the value, the default alert sound is played. The audio must be in one of the audio data formats that are compatible with system sounds; see Preparing Custom Alert Sounds for details.

    attachment

    object

    Optional. Available from devtodev iOS SDK 1.9, Cordova SDK 1.9, Unity SDK 2.3, UE4 SDK 1.9, Air SDK 1.8 Added in iOS 10. The possibility to attach images, videos, audio, and GIFs is available in iOS 10. Specify the type of an attachment ("image", "audio" or "video") and URL that leads to a media file. Attention, iOS uses only “https” protocol.

    Example:

    "attachment": { "type": "image", "url": "https://domain.com/pic.gif" }

    action

    object

    Optional. Available from devtodev iOS SDK 1.9, Cordova SDK 1.9, Unity SDK 2.3, UE4 SDK 1.9, Air SDK 1.8 The action after a click on the body of a notification. By default, a click simply opens an app. It is also possible to perform the following actions:

    • deeplink (string) - Direct the user to a specific resource, either within your app or on the web.

    • url (string) - Open a web page in a mobile browser, or any valid device-level URL such as App Store or app protocol links.

    • share (string) - The Share Action drives a user to share your message when they interact with your push notification.

    Examples:

    "action": { "url": "http://www.domain.com" }

    "action": { "deeplink": "your-url-scheme://host/path" }

    "action": { "share": "Happy holidays!" }

    interactive

    object

    Optional. Available from devtodev iOS SDK 1.9, Cordova SDK 1.9, Unity SDK 2.3, UE4 SDK 1.9, Air SDK 1.8 It is possible to specify one of the existing button templates in an “interactive” object, as well as assign the necessary actions to template buttons. It is possible to assign additional actions only to the button that opens an app. The same set of actions is available: deeplink, url and share. The list of accessible button templates is below in the text.

    Example:

    "interactive": { "template": "dtd_accept.open_decline.dismiss", "buttons": [{ "id": "accept", "action": { "url": "http://www.domain.com/accept" } }] }

    number

    This option identifies the date when the notification is no longer valid and can be discarded. It is possible to use either relative time in seconds passed since the moment of sending, or to specify the exact date in UNIX epoch format date expressed in seconds (UTC).

    Default value is 7 days (604800 seconds) after sending.

    If this value is nonzero, APNs stores a notification and tries to deliver it at least once repeating the attempt as needed if it is unable to deliver a notification for the first time. If the value is 0, APNs treats the notification as if it expires immediately and does not store the notification or attempt to redeliver it.

    collapse_key

    string

    Multiple notifications with the same collapse identifier are displayed to a user as a single notification. The value should not exceed 64 bytes.

    Yes or No (Open an app)

    Yes option takes user into an app. No dismisses the notification.

    dtd_yes.open_no.dismiss

    Yes

    yes

    No

    no

    Yes or No (Dismiss notification)

    Yes and No options dismiss the notification when clicked without taking a user into an app.

    dtd_yes.dismiss_no.dismiss

    Yes

    yes

    No

    no

    Accept or Decline (Open the app)

    Accept option takes a user into an app. No dismisses a notification.

    dtd_accept.open_decline.dismiss

    Accept

    accept

    Decline

    decline

    Accept or Decline (Dismiss notification)

    Yes and No options dismiss a notification when clicked without taking a user into an app.

    dtd_accept.dismiss_decline.dismiss

    Accept

    accept

    Decline

    decline

    Shop Now

    Shop Now takes user into an app. Should be a different location from a notification action.

    dtd_shop_now.open

    Shop Now

    shop_now

    Buy Now

    Buy Now takes user into an app. Should be a different location from a notification action.

    dtd_buy_now.open

    Buy Now

    buy_now

    Tell me more

    Deep link to more details about a specific offer or program

    dtd_more_info.open

    Tell me more

    more_info

    Download

    Download deep links users directly to media e.g. wallpaper images, apps, music downloads, file downloads, etc.

    dtd_download.open

    Download

    download

    Share

    Pass sharing text through to native OS apps like Facebook and Twitter using the Share action.

    dtd_share.open

    Share

    share

    Download or Share

    Download deep links users directly to media e.g. wallpaper images, apps, music downloads, file downloads, etc. Share the same media socially.

    dtd_download.open_share.open

    Download

    download

    Share

    share

    Shop Now or Share

    Shop Now takes a user into an app; should be a different location from a notification action.

    dtd_shop_now.open_share.open

    Shop Now

    shop_now

    Share

    share

    Buy Now or Share

    Buy Now takes a user into an app; should be a different location from a notification action.

    dtd_buy_now.open_share.open

    Buy Now

    buy_now

    Share

    share

    Like or Dislike

    Both options take a user into an app.

    dtd_like.open_dislike.open

    Like

    like

    Dislike

    dislike

    Like or Dislike

    Like option takes user into an app. Dislike dismisses a notification.

    dtd_like.open_dislike.dismiss

    Like

    like

    Dislike

    dislike

    Like

    Option takes a user into an app.

    dtd_like.open

    Like

    like

    Like and Share

    Capture user sentiment by allowing users to like a message or share it. Both options take user into an app.

    dtd_like.open_share.open

    Like

    like

    Share

    share

    Add

    Add an item: most often a wallet pass or card to your digital Wallet.

    dtd_add.open

    Add

    add

    Save and No

    Save something for future reference.

    dtd_save.open

    Save

    save

    Rate now

    Drive users to rate an app in the app store by deep linking from this button.

    dtd_rate.open

    Rate now

    rate

    Search

    Deep link to a search functionality within an app

    dtd_search.open

    Search

    search

    Book now

    Deep link to booking flow within an app.

    dtd_book.open

    Book now

    book

    Localized Formatted Strings
    application:didReceiveRemoteNotification:fetchCompletionHandler:

    Secondary methods

    This generation of SDK is deprecated and is no longer supported. Information about the .

    Initial referrer tracking

    Unfortunately, Apple does not provide any capability to pass a referrer string through to your app from a link to the App Store. But if you have a referral info, you can set it using the method below:

    The list of predefined keys:

    Unfortunately, Windows Store does not provide any capability to pass a referrer string through to your app from a link to the store. But if you have a referral info, you can set it using the method below:

    The list of predefined keys:

    Automated referral parameters are available on Android platform. Unfortunately, other platforms do not provide any capability to pass a referrer string through to your app from a link to the store. But if you have a referral info, you can set it using the method below:

    The list of predefined keys:

    Unfortunately, Apple does not provide any capability to pass a referrer string through to your app from a link to the app store. But if you have a referral info, you can set it using the method below:

    The list of predefined keys:

    Automated referral parameters is available on Android platform. Unfortunately, other platforms do not provide any capability to pass a referrer string through to your app from a link to the store. But if you have a referral info, you can set it using the method below:

    Unfortunately, Apple does not provide any capability to pass a referrer string through to your app from a link to the app store. But if you have a referral info, you can set it using the method below:

    Blueprint

    Field

    Type

    Description

    Source

    FString

    To identify a search engine, newsletter name, or other source. (for example 'AdWords', 'Bing', 'E-Mail Newsletter')

    Medium

    FString

    To identify a medium such as email or cost-per-install. (for example 'CPI')

    Code

    Connecting to social networks

    Use the current constants to specify a social network:

    Otherwise, create an object with the social network name you need.

    Use the current constants to specify a social network:

    • SocialNetwork.Facebook

    • SocialNetwork.Twitter

    • SocialNetwork.GooglePlus

    • SocialNetwork.Vk

    • and so on...

    Otherwise, create your own social network object.

    Example:

    Use the current constants to specify social network:

    SocialNetwork.Facebook SocialNetwork.Twitter SocialNetwork.GooglePlus SocialNetwork.Vk and so on...

    Otherwise, create social network the object of your own:

    We recommend using the following values for the most popular social networks:

    Use the current constants to specify social network:

    Otherwise, create your own social network object.

    Use the current constants to specify social network:

    Otherwise, create social network the object of your own.

    Use the current constants to specify social network:

    Otherwise, create your own social network object:

    Blueprint

    Posting to social networks

    Track publications in social networks and analyze the effectiveness of viral messages. The event is sent after a social network confirms the publication.

    As a 'reason' parameter we recommend you indicate actions which encourage users to make a publication.

    For example:

    • Start playing

    • New level reached

    • New building

    • New ability

    • Asking for help

    • New Record

    • Achievement

    • URL sharing

    Otherwise, create an object with the social network name you need.

    As a «reason» parameter we recommend that you indicate actions which encourage users to make a publication.

    For example:

    • Start playing

    • New level reached

    • New building

    • New ability

    • Quest completed

    • New item

    • Collection completed

    • Invitation

    • Asking for help

    • New Record

    • Acheivement

    • URL sharing

    • Recommendation

    • Review

    • and so on...

    Use the current constants to specify a social network:

    • SocialNetwork.Facebook

    • SocialNetwork.Twitter

    • SocialNetwork.GooglePlus

    • SocialNetwork.Vk

    Otherwise, create your own social network object.

    The social network ID is the same as with DevToDev.SDK.SocialNetworkConnect(). It is possible to use pre-defined or custom values as the reason (pReason parameter) .

    Example:

    As a «reason» parameter we recommend that you indicate actions which encourage users to make publication.

    For example:

    • Start playing

    • New level reached

    • New building

    As a «reason» parameter we recommend that you indicate actions which encourage users to make publication.

    As a «reason» parameter we recommend that you indicate actions which encourage users to make publication.

    For example:

    • Start playing

    • New level reached

    • New building

    As a «reason» parameter we recommend that you indicate actions which encourage users to make publication.

    For example:

    • Start playing

    • New level reached

    • New building

    As a «reason» parameter we recommend that you indicate actions which encourage users to make publication.

    For example:

    • Start playing

    • New level reached

    • New building

    Blueprint

    Code

    As a «reason» parameter we recommend that you indicate actions which encourage users to make publication.

    OpenUdid

    Property allows to get UDID:

    ODIN1

    Property allows to get ODIN:

    UUID

    Property allows to get UUID:

    Debug mode

    To enable the debug mode and make SDK notifications displayed in the console use this method:

    Forced sending

    To send events pack before it is filled or before its formation period, you can use immediate dispatch:

    Code

    To identify a specific product promotion or strategic campaign. (for example 'Snow Boots')

    Current SDK version

    To get the version of integrated SDK, use the following method:

    Set app version

    To set set current application version in WEB and Windows Standalone apps use this property:

    Tracking state (GDPR)

    The method of limiting the processing of user data. The right to be forgotten.

    This method is implemented in accordance with the GDPR requirements.

    In case a user doesn’t want their data to be sent and processed in the devtodev system, a developer must send a ’false’ value to this method.

    When calling the method setTrackingAvailability with a ‘false’ value, SDK sends a command to the server to delete all user’s personal data that has been collected by devtodev from this app and a command to block the collection of any data of this user in future, and then stops sending any messages to the devtodev system.

    The user will remain listed as an impersonal unit in previously aggregated metrics.

    When sending a ‘true’ value, the permission to block data collection is removed.

    When calling the method setTrackingAvailability with a ‘false’ value, SDK sends a command to the server to delete all user’s personal data that has been collected by devtodev from this app and a command to block the collection of any data of this user in future, and then stops sending any messages to the devtodev system.

    The user will remain listed as an impersonal unit in previously aggregated metrics.

    When sending a ‘true’ value, the permission to block data collection is removed.

    In the case of using TrackingAvailability property with a ‘false’ value, SDK sends a command to the server to delete all user’s personal data that has been collected by devtodev from this app and a command to block the collection of any data of this user in future, and then stops sending any messages to the devtodev system.

    The user will remain listed as an impersonal unit in previously aggregated metrics.

    In the case of using TrackingAvailability property with a ‘true’ value, the permission to block data collection is removed.

    When calling the method setTrackingAvailability with a ‘false’ value, SDK sends a command to the server to delete all user’s personal data that has been collected by devtodev from this app and a command to block the collection of any data of this user in future, and then stops sending any messages to the devtodev system.

    The user will remain listed as an impersonal unit in previously aggregated metrics.

    When sending a ‘true’ value, the permission to block data collection is removed.

    In the case of using TrackingAvailability property with a ‘false’ value, SDK sends a command to the server to delete all user’s personal data that has been collected by devtodev from this app and a command to block the collection of any data of this user in future, and then stops sending any messages to the devtodev system.

    The user will remain listed as an impersonal unit in previously aggregated metrics.

    In the case of using TrackingAvailability property with a ‘true’ value, the permission to block data collection is removed.

    When calling the method setTrackingAvailability with a ‘false’ value, SDK sends a command to the server to delete all user’s personal data that has been collected by devtodev from this app and a command to block the collection of any data of this user in future, and then stops sending any messages to the devtodev system.

    The user will remain listed as an impersonal unit in previously aggregated metrics.

    When sending a ‘true’ value, the permission to block data collection is removed.

    When calling the method setTrackingAvailability with a ‘false’ value, SDK sends a command to the server to delete all user’s personal data that has been collected by devtodev from this app and a command to block the collection of any data of this user in future, and then stops sending any messages to the devtodev system.

    The user will remain listed as an impersonal unit in previously aggregated metrics.

    When sending a ‘true’ value, the permission to block data collection is removed.

    current version can be found here
    /**
     * Tracks user's referral data
     * ### Usage:
     *    [DevToDev referrer:@{
     *       RFSource: @"adwords",
     *       RFMedium: @"cpi",
     *       RFContent: @"Snow Boots",
     *       RFCampaign: @"Warm Snow Boots",
     *       RFTerm: @"snow boots"
     *    }];
     * 
     * @param NSDictionary<ReferralProperty*, NSString*> utm - Dictionary with referrer values
     */
    
    //To identify a search engine, newsletter name, or other source.
    // (for example 'AdWords', 'Bing', 'E-Mail Newsletter')
    ReferralProperty * RFSource;
    
    //To identify a medium such as email or cost-per-install.
    // (for example 'CPI')
    ReferralProperty * RFMedium;
    
    //To identify a specific product promotion or strategic campaign.
    //(for example 'Snow Boots')
    ReferralProperty * RFCampaign;
    
    
    /**
     * ### Usage:
     *     Dictionary<ReferralProperty, string> referralData = new Dictionary<ReferralProperty, string>();
     *     referralData.Add(ReferralProperty.Source, "source");
     *     referralData.Add(ReferralProperty.Medium, "medium");
     *     referralData.Add(ReferralProperty.Content, "content");
     *     referralData.Add(ReferralProperty.Campaign, "campaign");
     *     referralData.Add(ReferralProperty.Term, "term");
     *     referralData.Add(ReferralProperty.Custom("site"), "site");
     *     DevToDev.SDK.Referral(referralData);
     *
     * <param name="referralData">Dictionary with referrer values</param>
     */
    DevToDev.SDK.Referral(Dictionary<ReferralProperty, string> referralData);
    //To identify a search engine, newsletter name, or other source.
    // (for example 'AdWords', 'Bing', 'E-Mail Newsletter')
    ReferralProperty.Source;
    
    //To identify a medium such as email or cost-per-install.
    // (for example 'CPI')
    ReferralProperty.Medium;
    
    //To identify a specific product promotion or strategic campaign.
    //(for example 'Snow Boots')
    ReferralProperty.Campaign;
    
    //To differentiate ads or links that point to the same URL.
    //(for example some ads might advertise 'Warm Snow Boots' and others might advertise 'Durable Snow Boots')
    ReferralProperty.Content;
    
    //To note the keywords for this ad.
    // for example 'shoes+boots')
    ReferralProperty.Term;
    
    //To add a custom key
    ReferralProperty.Custom("your_key_name");
    /// <summary> Initial referrer tracking <summary>
    /// <example> Usage:
    /// 
    ///     Dictionary<ReferralProperty, string> referralData = new Dictionary<ReferralProperty, string>();
    ///     referralData.Add(ReferralProperty.Source, "source");
    ///     referralData.Add(ReferralProperty.Medium, "medium");
    ///     referralData.Add(ReferralProperty.Content, "content");
    ///     referralData.Add(ReferralProperty.Campaign, "campaign");
    ///     referralData.Add(ReferralProperty.Term, "term");
    ///     referralData.Add(ReferralProperty.Custom("site"), "site");
    ///     DevToDev.Analytics.Referral(referralData);
    /// 
    /// </example>
    /// <param name="referralData"> Dictionary with referrer values </param>
    DevToDev.Analytics.Referral(Dictionary<ReferralProperty, string> referralData);
    // To identify a search engine, newsletter name, or other source.
    // (for example 'AdWords', 'Bing', 'E-Mail Newsletter')
    ReferralProperty.Source;
    
    // To identify a medium such as email or cost-per-install.
    // (for example 'CPI')
    ReferralProperty.Medium;
    
    // To identify a specific product promotion or strategic campaign.
    // (for example 'Snow Boots')
    ReferralProperty.Campaign;
    
    // To differentiate ads or links that point to the same URL.
    //(for example some ads might advertise 'Warm Snow Boots' and others might advertise 'Durable Snow Boots')
    ReferralProperty.Content;
    
    // To note the keywords for this ad.
    // (for example 'shoes+boots')
    ReferralProperty.Term;
    
    // To add a custom key
    ReferralProperty.Custom("your_key_name");
    /**
     * Tracks user's referral data
     * ### Usage:
     *    [DevToDev referrer:@{
     *       RFSource: @"adwords",
     *       RFMedium: @"cpi",
     *       RFContent: @"Snow Boots",
     *       RFCampaign: @"Warm Snow Boots",
     *       RFTerm: @"snow boots"
     *    }];
     * 
     * @param NSDictionary<ReferralProperty*, NSString*> utm - Dictionary with referrer values
     */
    [DevToDev referrer: (NSDictionary<ReferralProperty*, NSString*> *) utm];
    //To identify a search engine, newsletter name, or other source.
    // (for example 'AdWords', 'Bing', 'E-Mail Newsletter')
    ReferralProperty * RFSource;
    
    //To identify a medium such as email or cost-per-install.
    // (for example 'CPI')
    ReferralProperty * RFMedium;
    
    //To identify a specific product promotion or strategic campaign.
    //(for example 'Snow Boots')
    ReferralProperty * RFCampaign;
    
    //To differentiate ads or links that point to the same URL.
    //(for example some ads might advertise 'Warm Snow Boots' and others might advertise 'Durable Snow Boots')
    ReferralProperty * RFContent;
    
    //To note the keywords for this ad.
    // for example 'shoes+boots')
    ReferralProperty * RFTerm;
    
    //To add a custom key
    [ReferralProperty Custom:@"your_key_name"];
    /**
    * ### Usage:
    *     var referralData:Dictionary = new Dictionary();
    *     referralData[ReferralProperty.Source] = "source";
    *     referralData[ReferralProperty.Medium] = "medium";
    *     referralData[ReferralProperty.Content] = "content";
    *     referralData[ReferralProperty.Campaign] = "campaign";
    *     referralData[ReferralProperty.Term] = "term";
    *     referralData[ReferralProperty.Custom("site")] = "site";
    *     DevToDev.referral(referralData);
    *
    * @ param referralData - dictionary with referrer values
    */
    DevToDev.referral(referralData:Dictionary);
    The list of predefined keys:
    // To identify a search engine, newsletter name, or other source.
    // (for example 'AdWords', 'Bing', 'E-Mail Newsletter')
    ReferralProperty.Source;
    
    // To identify a medium such as email or cost-per-install.
    // (for example 'CPI')
    ReferralProperty.Medium;
    
    // To identify a specific product promotion or strategic campaign.
    // (for example 'Snow Boots')
    ReferralProperty.Campaign;
    
    // To differentiate ads or links that point to the same URL.
    // (for example some ads might advertise 'Warm Snow Boots' and others might advertise 'Durable Snow Boots')
    ReferralProperty.Content;
    
    // To note the keywords for this ad.
    // (for example 'shoes+boots')
    ReferralProperty.Term;
    
    // To add a custom key
    ReferralProperty.Custom("your_key_name");
    /**
    * Tracks the existence of a connection with a social network. 
    * Use pre-defined or custom values as an identifier.
    * @param SocialNetwork socialNetwork - social network id
    */
    [DevToDev socialNetworkConnect: (SocialNetwork *) socialNetwork];javascript:void(0)
    Facebook
    Twitter
    GooglePlus
    VK
    //and so on...
    SocialNetwork  socialNetwork = [SocialNetwork Custom: (NSString *) networkName]; (max. 24 symbols)
    /**
    * Tracks the existence of a connection with a social network.
    * Use pre-defined or custom values as an identifier.
    * @param SocialNetwork socialNetwork - social network id
    */
    DevToDev.socialNetworkConnect(SocialNetwork socialNetwork);
    /**
    * Tracks the existence of posts to a social network.
    * @param socialNetwork - social network Id
    * @param NSString reason - the reason of posting (max. 32 symbols)
    */
    [DevToDev socialNetworkPost: (SocialNetwork *) socialNetwork withReason: (NSString *) reason];
    Facebook
    Twitter
    GooglePlus
    VK
    //and so on...
    SocialNetwork  socialNetwork = [SocialNetwork Custom: (NSString *) networkName];
    // networkName (max. 24 symbols)
    /**
    * Tracks the existence of posts to a social network.
    * @param socialNetwork - social network Id
    * @param reason - the reason of posting (max. 32 symbols)
    */
    DevToDev.socialNetworkPost(SocialNetwork socialNetwork, String reason);
    /**
    * @return OpenUdid
    */
    [DevToDev getOpenUdid];
    /**
    * @return Open Udid
    */
    DevToDev.getOpenUdid();
    DevToDev.SDK.OpenUdid
    DevToDev.Analytics.OpenUdid
    /**
    * @return Open Udid
    */
    [DevToDev getOpenUdid];
    /**
    * @return ODIN1
    */
    [DevToDev getOdin1];
    /**
    * @return ODIN1
    */
    DevToDev.getOdin1();
    DevToDev.SDK.ODIN
    DevToDev.Analytics.Odin1
    /**
    * @return ODIN1
    */
    [DevToDev getOdin1];
    /**
    * @return ODIN1
    */
    DevToDev.getOdin1();
    /**
    * @return UUID
    */
    [DevToDev getUUID];
    /**
    * @return UUID
    */
    DevToDev.getUUID();
    /**
    * @return UUID
    */
    [DevToDev getUUID];
    /**
    * @param BOOL isActive
    */
    [DevToDev setActiveLog: (BOOL) isActive];
    /**
    * @param logLevel
    */
    DevToDev.setLogLevel(LogLevel logLevel);
    //to enable logging
    DevToDev.SDK.LogEnabled = true;
    
    //to disable loging
    DevToDev.SDK.LogEnabled = false;
    /**
    * Activates console log
    * @param {boolean} status
    */
    
    devtodev.setDebugLog(status);
    /// <summary> Enable/Disable log</summary>
    /// <param name="isEnabled">Enabled/Disabled log</param>
    DevToDev.Analytics.SetActiveLog(bool isEnabled);
    /**
    * @param BOOL isActive
    */
    [DevToDev setActiveLog: (BOOL) isActive];
    /**
    * @param logLevel (set logLevel=1 to enable log, 0 to disable)
    */
    DevToDev.setLogLevel(logLevel:int);
    [DevToDev sendBufferedEvents];
    DevToDev.sendBufferedEvents();
    DevToDev.SDK.sendBufferedEvents();
    /**
    * Sends event packet immediately
    */
    
    devtodev.sendBufferedEvents();
    DevToDev.Analytics.SendBufferedEvents();
    [DevToDev sendBufferedEvents];
    DevToDev.sendBufferedEvents();
    // Sends events pack before it is filled or before its formation period
    
    FAnalytics::Get().GetDefaultConfiguredProvider()->FlushEvents();
    /**
    * @return SDKVersion
    */
    [DevToDev sdkVersion];
    /**
    * @return SDKVersion
    */
    DevToDev.getSdkVersion();
    DevToDev.SDK.GetSdkVersion();
    /**
    * Returns SDK version
    */
    
    devtodev.getSdkVersion();
    /**
    * @return SDKVersion
    */
    [DevToDev sdkVersion];
    /**
    * @return SDKVersion
    */
    DevToDev.getSdkVersion();
    /// <summary>  Property allows to set current application version.
    /// Attention! This property is necessary for WEB and Windows Standalone apps only.
    /// It will be ignored on other platforms.
    /// </summary>
    /// <param name="version"> Current version of your application </param>
    DevToDev.Analytics.ApplicationVersion = version;
    /**
    * The method of limiting the processing of user data. The right to be forgotten.
    * @param BOOL trackingAvailable - use 'false' to erase user's personal data and stop collecting data of this user.
    * 'true' if you want to resume data collection.
    */
    [DevToDev setTrackingAvailability: (BOOL) trackingAvailable];
    /**
    * The method of limiting the processing of user data. The right to be forgotten.
    * @param isAvailable - send 'false' to erase user's personal data and stop collecting data of this user.
    * Send 'true' if you want to resume data collection.
    */
    DevToDev.setTrackingAvailability(boolean isAvailable);

    gm

    Google Mail

    tb

    Tumblr

    gp

    Google+

    tw

    Twitter

    in

    LinkedIn

    vk

    VK

    ok

    Odnoklassniki

    vb

    Viber

    pi

    Pinterest

    wp

    WhatsApp

    qq

    Qzone

    Code

    We recommend using the following values for the most popular social networks:

    Value

    Social Network

    Value

    Social Network

    en

    Evernote

    rt

    Reddit

    fb

    Facebook

    rr

    Renren

    gm

    and so on...
  • New ability

  • Quest completed

  • New item

  • Collection completed

  • Invitation

  • Asking for help

  • New Record

  • Acheivement

  • URL sharing

  • Recommendation

  • Review

  • and so on...

    Use the current constants to specify social network:

    SocialNetwork.Facebook SocialNetwork.Twitter SocialNetwork.GooglePlus SocialNetwork.Vk and so on...

    Otherwise, create social network the object of your own:

    Facebook

    rr

    Renren

    gm

    Google Mail

    tb

    Tumblr

    gp

    Google+

    tw

    Twitter

    in

    LinkedIn

    vk

    VK

    ok

    Odnoklassniki

    vb

    Viber

    pi

    Pinterest

    wp

    WhatsApp

    qq

    Qzone

    New ability
  • Quest completed

  • New item

  • Collection completed

  • Invitation

  • Asking for help

  • New Record

  • Acheivement

  • URL sharing

  • Recommendation

  • Review

  • and so on...

    Use the current constants to specify social network:

    Otherwise, create your own social network object.

    New ability
  • Quest completed

  • New item

  • Collection completed

  • Invitation

  • Asking for help

  • New Record

  • Achievement

  • URL sharing

  • Recommendation

  • Review

  • and so on...

    Use the current constants to specify social network:

    Otherwise, create social network the object of your own.

    New ability
  • Quest completed

  • New item

  • Collection completed

  • Invitation

  • Asking for help

  • New Record

  • Acheivement

  • URL sharing

  • Recommendation

  • Review

  • and so on...

  • Use the current constants to specify social network:

    Otherwise, create your own social network object:

    For example:

    • Start playing

    • New level reached

    • New building

    • New ability

    • Asking for help

    • New Record

    • Achievement

    • URL sharing

    Value

    Social Network

    Value

    Social Network

    en

    Evernote

    rt

    Reddit

    fb

    Facebook

    rr

    Renren

    gm

    Quest completed

  • New item

  • Collection completed

  • Invitation

  • Recommendation
  • Review

  • and so on...

    [DevToDev referrer: (NSDictionary<ReferralProperty*, NSString*> *) utm];
    //To differentiate ads or links that point to the same URL.
    //(for example some ads might advertise 'Warm Snow Boots' and others might advertise 'Durable Snow Boots')
    ReferralProperty * RFContent;
    //To note the keywords for this ad.
    // for example 'shoes+boots')
    ReferralProperty * RFTerm;
    //To add a custom key
    [ReferralProperty Custom:@"your_key_name"];

    Campaign

    FString

    To identify a specific product promotion or strategic campaign. (for example 'Snow Boots')

    Content

    FString

    To differentiate ads or links that point to the same URL. (for example some ads might advertise 'Warm Snow Boots' and others might advertise 'Durable Snow Boots')

    Term

    FString

    To note the keywords for this ad. for example 'shoes+boots')

    Value

    Social Network

    Value

    Social Network

    en

    Evernote

    rt

    Reddit

    fb

    Facebook

    rr

    Renren

    Field

    Type

    Description

    Social Name

    FString

    Social network Id

    For example:

    • Start playing

    • New level reached

    • New building

    • New ability

    • Quest completed

    • New item

    • Collection completed

    • Invitation

    • Asking for help

    • New Record

    • Achiеvement

    • URL sharing

    • Recommendation

    • Review

    and so on...

    Value

    Social Network

    Value

    Social Network

    en

    Evernote

    rt

    Reddit

    Field

    Type

    Description

    Social Name

    FString

    Social network Id

    Reason

    FString

    The reason of posting (max. 32 symbols)

    fb

    UDevToDevBlueprintFunctionLibrary::Referrer(const TArray<FAnalyticsEventAttr>& Attributes);
    /**
    * Custom social network object
    * @param networkName - social network name (max. 24 symbols)
    */
    SocialNetwork socialNetwork = SocialNetwork.Custom(String networkName);
    /**
    * Tracks the existence of a connection with a social network.
    * Use pre-defined or custom values as an identifier.
    * <param name="socialNetwork"> Social network ID </param>
    */
    DevToDev.SDK.SocialNetworkConnect(SocialNetwork socialNetwork);
    DevToDev.SDK.SocialNetworkConnect(SocialNetwork.Facebook);
    SocialNetwork socialNetwork = SocialNetwork.Custom(string networkName); //(max. 24 symbols)
    /**
    * Tracks the existence of a connection with a social network.
    * Use pre-defined or custom values as an identifier.
    * @param {string} socialNetwork - social network id (max. 24 symbols)
    **/
    
    devtodev.socialNetworkConnect(socialNetwork);
    /// <summary> Track the existence of a connection with a social network. 
    /// Use pre-defined or custom values as an identifier.</summary>
    /// <param name="socialNetwork"> Social network ID </param>
    DevToDev.Analytics.SocialNetworkConnect(DevToDev.SocialNetwork socialNetwork);
    DevToDev.SocialNetwork.Facebook
    DevToDev.SocialNetwork.Twitter
    DevToDev.SocialNetwork.GooglePlus
    DevToDev.SocialNetwork.Vk
    // and so on...
    DevToDev.SocialNetwork socialNetwork = DevToDev.SocialNetwork.Custom(string networkName); //(max. 24 symbols)
    /**
    * Tracks the existence of a connection with a social network. Use pre-defined or custom values as an identifier.
    * @param SocialNetwork socialNetwork - social network id
    */
    [DevToDev socialNetworkConnect: (SocialNetwork *) socialNetwork];
    Facebook
    Twitter
    GooglePlus
    VK
    //and so on...
    SocialNetwork  socialNetwork = [SocialNetwork Custom: (NSString *) networkName]; //max. 24 symbols
    /**
    * Tracks the existence of a connection with a social network.
    * Use pre-defined or custom values as an identifier.
    * @param socialNetwork - social network id
    */
    DevToDev.socialNetworkConnect(socialNetwork:SocialNetwork);
    SocialNetwork.VK;
    SocialNetwork.TWITTER;
    SocialNetwork.FACEBOOK;
    SocialNetwork.GOOGLE_PLUS;
    SocialNetwork.WHATS_APP;
    SocialNetwork.VIBER;
    SocialNetwork.EVERNOTE;
    SocialNetwork.GOOGLE_MAIL;
    SocialNetwork.LINKED_IN;
    SocialNetwork.PINTEREST;
    SocialNetwork.QZONE;
    SocialNetwork.REDDIT;
    SocialNetwork.RENREN;
    SocialNetwork.TUMBLR;
    /**
    * Custom social network object
    * @param networkName - social network name (max. 24 symbols)
    */
    var socialNetwork:SocialNetwork = SocialNetwork.Custom(networkName:String);
    /**
    * Custom social network object
    * @param networkName - social network name (max. 24 symbols)
    */
    SocialNetwork socialNetwork = SocialNetwork.Custom(String networkName);
    /**
    *  <param name="networkName"> Social network ID </param>
    *  <param name="reason"> The reason of posting. (max. 32 symbols)</param>
    */
    DevToDev.SDK.SocialNetworkPost(SocialNetwork socialNetwork, String reason)
    DevToDev.SDK.SocialNetworkPost(SocialNetwork.Facebook, "newLevelReached");
    /**
    * Tracks the existence of posts to a social network.
    * @param {string} socialNetwork - social network Id (max. 24 symbols)
    * @param {string} reason - the reason of posting (max. 32 symbols)
    */
    
    devtodev.socialNetworkPost(socialNetwork, reason);
    /// <summary> Track the existence of posts to a social network. </summary>
    /// <param name="networkName"> Social network ID </param>
    /// <param name="reason"> The reason of posting. (max. 32 symbols)</param>
    DevToDev.Analytics.SocialNetworkPost(DevToDev.SocialNetwork networkName, string reason);
    /**
    * Tracks the existence of posts to a social network.
    * @param socialNetwork - social network Id
    * @param reason - the reason of posting (max. 32 symbols)
    */
    [DevToDev socialNetworkPost: (SocialNetwork *) socialNetwork withReason: (NSString *) reason];
    /**
    * Tracks the existence of posts to a social network.
    * @param socialNetwork - social network Id
    * @param reason - the reason of posting (max. 32 symbols)
    */
    DevToDev.socialNetworkPost(socialNetwork:SocialNetwork, reason:String);
    // Tracks the existence of posts to a social network.
    // FString socialNetwork - social network Id
    // FString reason - the reason of posting (max. 32 symbols)
    
    UDevToDevBlueprintFunctionLibrary::SocialNetworkPost(const FString& socialNetwork, const FString& reason);
    /// <summary> The Property of limiting the processing of user data. The right to be forgotten.
    /// Use 'false' to erase user's personal data and stop collecting data of this user or 'true'
    /// if you want to resume data collection.</summary>
    
    DevToDev.SDK.TrackingAvailability = false/true;
    /**
    * The method of limiting the processing of user data. The right to be forgotten.
    * @param {boolean} status - send 'false' to erase user's personal data and stop collecting data of this user.
    * Send 'true' if you want to resume data collection.
    */
    devtodev.setTrackingAvailability(status);
    /**
    * The property of limiting the processing of user data. The right to be forgotten.
    * Use 'false' to erase user's personal data and stop collecting data of this user or 'true'
    * if you want to resume data collection.</summary>
    */
    DevToDev.Analytics.TrackingAvailability = false/true;
    /**
    * The method of limiting the processing of user data. The right to be forgotten.
    * @param BOOL trackingAvailable - use 'false' to erase user's personal data and stop collecting data of this user.
    * 'true' if you want to resume data collection.
    */
    [DevToDev setTrackingAvailability: (BOOL) trackingAvailable];
    /**
    * The method of limiting the processing of user data. The right to be forgotten.
    * @param isTrackingAvailable - send 'false' to erase user's personal data and stop collecting data of this user.
    * Send 'true' if you want to resume data collection.
    */
    DevToDev.setTrackingAvailability(isTrackingAvailable:Boolean)
    // Tracks the existence of a connection with a social network.
    // Use pre-defined or custom values as an identifier.
    // FString socialNetwork - social network id (max. 24 symbols)
    
    UDevToDevBlueprintFunctionLibrary::SocialNetworkConnect(const FString& socialNetwork);
    SocialNetwork socialNetwork = SocialNetwork.Custom(string networkName); //(max. 24 symbols)
    DevToDev.SocialNetwork.Facebook
    DevToDev.SocialNetwork.Twitter
    DevToDev.SocialNetwork.GooglePlus
    DevToDev.SocialNetwork.Vk
    // and so on...
    DevToDev.SocialNetwork socialNetwork = DevToDev.SocialNetwork.Custom(string networkName); //(max. 24 symbols)
    Facebook
    Twitter
    GooglePlus
    VK
    //and so on...
    SocialNetwork  socialNetwork = [SocialNetwork Custom: (NSString *) networkName]; //max. 24 symbols
    SocialNetwork.VK;
    SocialNetwork.TWITTER;
    SocialNetwork.FACEBOOK;
    SocialNetwork.GOOGLE_PLUS;
    SocialNetwork.WHATS_APP;
    SocialNetwork.VIBER;
    SocialNetwork.EVERNOTE;
    SocialNetwork.GOOGLE_MAIL;
    SocialNetwork.LINKED_IN;
    SocialNetwork.PINTEREST;
    SocialNetwork.QZONE;
    SocialNetwork.REDDIT;
    SocialNetwork.RENREN;
    SocialNetwork.TUMBLR;
    /**
    * Custom social network object
    * @param networkName - social network name (max. 24 symbols)
    */
    var socialNetwork:SocialNetwork = SocialNetwork.Custom(networkName:String);

    Quest completed

  • New item

  • Collection completed

  • Invitation

  • Recommendation
  • Review

  • and so on...

    Google Mail

    tb

    Tumblr

    gp

    Google+

    tw

    Twitter

    in

    LinkedIn

    vk

    VK

    ok

    Odnoklassniki

    vb

    Viber

    pi

    Pinterest

    wp

    WhatsApp

    qq

    Qzone

    Google Mail

    tb

    Tumblr

    gp

    Google+

    tw

    Twitter

    in

    LinkedIn

    vk

    VK

    ok

    Odnoklassniki

    vb

    Viber

    pi

    Pinterest

    wp

    WhatsApp

    qq

    Qzone

    Data API

    Server API integration manual

    This API version is deprecated, please use the latest version.

    Request format

    The request should be sent to: https://api.devtodev.com/stat/v1/?api=ak-cDNRQl0Lypq4AOUrx8aGGMnmJT1FSebd where:

    • api - individual devtodev API app key, which can be found on the page of app integration;

    • v1 - the current version of the API aggregator.

    All the transferred data must be in UTF8 encoding.

    Contents must be sent as POST in gzip. The archive must contain JSON with one or more events for one or several users.

    The size of a package can't exсeed 1 MB before compression. Packages that exсeed this size can't be processed.

    If there are several events they must be formed inside a user object by type (name).

    Response format

    The list of events

    Information about a source of user appearance

    (traffic source, referral) It is used when it is necessary to track data about a source of user appearance. The event is sent when an app is first launched by a user if a user came from a tracked source. Maximum of the fields from the following accessible data must be transferred.

    Information about a user

    It is not sent in case there is some data missing. The event can't be submitted as a package.

    Attention! We strongly recommend that you do not use these properties to transfer and store data that fits the definition of !

    Information about an app

    The recommended interval of sending is not more than once per 24h.

    Information about a device

    The recommended interval of sending is not more than once per 24h. Information is the most relevant for mobile devices.

    Session

    In case it is possible to fix the length of a session, it is sent during the end of a session or during the next session. If there is no such possibility, send the date of the start of a session, the length of a session must be placed in the middle in case it is known. The absence of a parameter of a session's length makes it impossible to count some metrics.

    Real Payment

    In case you transfer data related to several transactions, group them by item name.

    Onboarding (tutorial) steps

    The event allows to learn how a user goes through a tutorial. The event is created after every completed step.

    If a tutorial that hasn't been completed is programmed to reset to the beginning, the repeated reference of identical steps will be counted and influence the statistical metrics.

    Use the following constants for basic actions:

    In all other cases use the number of steps above 0.

    Custom event

    If it is necessary to count events that are absent from the main types of events, use user events. An event must have a unique name and can include up to 20 parameters. You can use up to 300 unique names.

    We strongly recommend that you do not use custom event properties to transfer and store data that fits the definition of !

    New level

    This event is for games only.

    Allows you to analyze players' distribution by game levels. The event is created when a player moves to the next level. In order to track the average state of game currency accounts, the amount of spendings and earnings of a currency, the amount of a game currency bought while completing a level, you can also send this data in this event.

    Virtual Currency Payment

    This event is for games only.

    It is used to track the ways in which an in-game currency is spent and the popularity of in-game items.

    In case one item is sold in several currencies, a purchase is divided into several events, the amount of which corresponds to the number of currencies. Wherein the number of items is specified only in one event.

    Progression event

    This event is for games only.

    First of all, the event is used for games with short locations (game levels) that are completed during one game session. The event allows to gather data about the success in location completion and receive statistics by parameters that are changeable during location completion.

    Ad impression

    The event is used for individual tracking of ad revenue.

    Do not use this event if you use ad networks that utilize the server-server protocol for sending ad revenue data (ironSource, AppLovin MAX, and Fyber networks) and you already set up this method of data collection because if you use both data sources, your revenue data may be duplicated.

    Example:

    Connection to social networks

    Allows you to track existing connections to social networks.

    Constants that are supported by the system:

    Publication in social networks

    Allows you to track publications in social networks. It also allows you to analyze viral channels to optimize marketing efficiency.

    It is sent after a publication has been approved by a social network.

    We recommend to specify actions that encourage to make a publication as a reason:

    Clearing user data

    The "Wipe" function can help when you test your app and/or when you need to clear user data as a part of the gameplay.

    By default, when you send an event without any parameters, a user-specified will be "forgotten". We will keep user data in the database, but you won’t be able to find them by their main identifier. Respectively, the following event batch with the same identifier will result in creating a new user. Also, in certain circumstances, you can’t just ‘forget’ some of the users. Therefore, we have provided several flags that might be helpful.

    Tracking state (GDPR)

    Limiting the processing of user data. The right to erasure.

    This event is implemented in accordance with the GDPR requirements.

    A developer must use this event in case a user doesn’t want their data to be sent and processed in the devtodev system.

    When calling "ts" event with the parameter "isTrackingAllowed": false, it is a command to the server to delete all user’s personal data that has been collected by devtodev from this app and a command to block the collection of any data of this user in future.

    The user will remain listed as an impersonal unit in previously aggregated metrics.

    When sending a true value, the permission to block data collection is removed.

    An example of a package

    'POST' https://api.devtodev.com/stat/v1/?api=ak-npADyEmjxc0usQR52k6it38zUPSloGT7 with gzipped body:

    Utility for testing

    An example of sending to PHP

    An example of the implementation of a request to PHP with the use of Curl. The sending of real payment.

    The error of JSON format

    {

    "error_message":"Wrong JSON format"

    }

    200

    Package is received

    200

    The package is received, but there are errors found during the validation of events syntax.

    The errors found are specified in an answer in the following format:

    1.

    { "errors": { "rg": { "expected": "[timestamp]", "received": [ "[timestamps]", "[timestaTTmp]" ] } } } The message indicates that there have been received fields listed under the tag "received", but there are no fields that are listed under the tag "expected" among them. Such an event will not be processed.

    2.

    { "errors": { "rg": { "expected": "[timestamp]", "useless": [ "[timestamps]", "[timestaTTmp]" ] } } }

    The message indicates that there have been received extra fields listed under the tag "useless", however, fields listed under the tag "expected" are enough. Such an event will be processed.

    TW

    Twitter

    IN

    LinkedIn

    VK

    VK

    OK

    Odnoklassniki

    VB

    Viber

    PI

    Pinterest

    WP

    WhatsApp

    QQ

    Qzone

    HTTP Code

    State

    413

    Wrong size of the data package (exceeds the maximum)

    400

    API key is absent

    401

    Wrong API key

    403

    Administrative restrictions on data received from a client

    400

    The error of data unpacking

    {

    "error_message":"Wrong GZIP format"

    }

    EN

    Evernote

    RT

    Reddit

    FB

    Facebook

    RR

    Renren

    GM

    Google Mail

    TB

    Tumblr

    GP

    For example:

    • Start playing

    • New level reached

    • New building

    • New ability

    • Quest completed

    • New item

    • Collection completed

    • Invitation

    • Asking for help

    • New Record

    • Achievement

    • URL sharing

    • Recommendation

    • Review

    and so on...

    personal data
    personal data
    A utility for sending packets
    https://www.devtodev.com/upload/files/devtodevapitester.jar

    400

    Google+

    {
    
        "abcd1234abcd" : {              // Main identifier (device or user ID)
            "prev" : "asdf2345234asdf", // Previous main identifier (device or user ID)
                                        // in case it has been changed 
            "userId":"",                // Additional identifier (it is used if there is an identifier
                                        // that is different from the main identifier.
                                        // For example, a cross-platform user identifier)
            "prevUserId":"";            // Previous additional identifier in case it has been changed
            "sc" :[                     // Event name
                {},                     // Parameters of the first event
                {},                     // Parameters of the second event
                …
            ],
            "gs" :[                     // Event name 
                {}                      // Event parameters
            ],
            …
        },
        "1234abc1234" : {               // Unique user ID
            "userID":"",                // Additional identifier (it is used if there is an identifier
                                        // that is different from the main identifier.
            "sc" :[                     // Event name
                {},                     // Parameters of the first event
                {},                     // Parameters of the second event
                …
            ],
            "gs" :[                     // Event name 
                {}                      // Event parameters
            ],
        },
    }
    "rf" : [
        {
            "timestamp" : 1386259227,   // Required field! The date of registration
            "publisher" : "",           // Required field! The source from which a user came - the name of 
                                        // An advertising platform (advertising network)
            "subpublisher" : "",        // In case a platform is an aggregator - the name of a platform 
                                        // From which a user was resold 
            "subad" : "",               // Specific banner from which a user came (for Facebook)
            "subadgroup" : "",          // The group of ads(for Facebook)
            "subcampaign" : "",         // The name of a campaign from which a user came 
            "subplacement" : "",        // Banner placement - its position on a page 
            "subsite" : "",             // Who placed a banner (an app/ a website where a banner is placed)
            "country" : "US",           // Required field! The country of a user (format ISO 3166-1 alpha-2)
            "currencyCode" : "USD",     // The currency of referral's cost (format ISO 4217)
            "cost" : 9.99,              // The cost of a referral in a specified currency 
        }
    ]
    "ui": [{
    	"timestamp": 1386259227,
    	"country": "GB", // The country of a user (format ISO 3166-1 alpha-2)
    	"language": "en", // The language of a user (format ISO 639-1 (1998)
    	"crossUid": "customuserid", // Custom user ID
    	"ip": "127.0.0.1", // IP
    	"carrier": "Beeline", // The name of a network operator
    	"isRooted": 0, // Rooted (jailbroken) device (1 - rooted)
    	"userAgent": "a lot of info" // Browser user-agent
    }]
    "pl": [{
    	"data": {
    		"age": 21, //Reserved. User's age in years
    		"cheater": true, //Reserved. True, if a user is a cheater 
    		//In case you have your own methods to detect
    		//cheaters, you can mark such users.
    		//Event records made by cheaters will be 
    		//ignored when counting statistical metrics. 
    
    		"tester": true, //Reserved. True, if a user is a tester 
    		//Attention! This marker cannot be removed
    		//through the SDK (It can not be set to false
    		//after true).
    		//Event records made by testers will be 
    		//ignored when counting statistical metrics. 
    
    		"gender": 1, //Reserved. User's sex 0-unknown, 1-male, 2-female 
    		"name": "John Doe", //Reserved. User's name
    		"email": "[email protected]", //Reserved. User's e-mail 
    		"phone": "+15555555555", //Reserved. User's phone number 
    		"photo": "http://google.com/pic.png", //Reserved. User's photo
    
    		//Custom characteristics of a user in a key-value format 
    		"key1": "stringValue", //String value
    		"key2": 1.54, //Number value
    		"key3": [1, 2, "something"] //Array
    	},
    	"timestamp": 12313 //The date of data changes
    }]
    
    "pl": [{
    	"data": {
    		"key1": null, //Remove user's characteristic key1
    		"key2": null //Remove user's characteristic key2
    	},
    	"timestamp": 12313
    }]
    "ai": [{
    	"timestamp": 1386259227,
    	"sdkVersion": "1.1", // Required. In case of communication via API, 
    	// the version of implementation of a communication mechanism
    	"appVersion": "1.2", // App version
    	"codeVersion": 14.0, // Version of app's code 
    	"bundleId": "com.myown.app" // App's bundle 
    }]
    "di" :[
        {
        	  "timestamp" : 1386259227,
            "manufacturer" : "Apple",                   // The manufacturer of a device
            "model" : "iPhone 4,1",                     // uname (iOS), MODEL (Android)
            "screenResolution" : "1024x768",            // The display's resolution of a device
            "screenDpi" : 144,                          // The density of display's points
            "odin" : "klflaiuewfuasydfiasydpf98ay4",    // DeviceUniqueId SHA-1 (Windows Phone),
                                                        // AndroidId SHA-1 (Android)
            "openUdid" : "f;isyofa7w8yp4fapw49",        // OpenUDID ((Android, iOS, Windows phone)
            "idfa" : "AYTSD-ADSYS-LAUSDY-IUAYSD",       // Ad identifier IDFA (iOS)
            "idfv" : "87ASD-9A7SD-AD2G-Q26EO-AS7D",     // Device identifier within the vendor IDFV (iOS)
            "d2dUdid" : "dsufyoa-sfa3wr-ra3rawQ2-AWR3A",// Base64 от DeviceUniqueId (Windows Phone) , 
                                                        // random by http:// www.ietf.org/rfc/rfc4122.txt (Android)
            "imei" : "w87ea6owe7aow78eaow",             // IMEI (Android)
            "androidId" : "o8a7d6oa8s7b6doa87sb6d8bs",  // AndroidID (Android)
            "advertisingId" : "38400000-8cf0-11bd-b23e-10b96e40000d", // Advertising ID (Android, Windows phone 8.1)
            "serialId" : "asd76asd9",                   // Hardware serial number  (Android,Windows phone)
            "deviceVersion": "8.1",                     // OS version
            "deviceModel": "Windows"                    // The name of OS family
        }
    ]
    "gs" : [
        {
            "timestamp" : 1386259227,   // The date of session's start 
            "length" : 34,              // The length of a session in seconds
            "level" : 3,                // Player's level 
            "inProgress" : ["village"]
        },
        …
    ]
    "rp": [
        {
            "name": "inapp_name_1",         // The name of a purchase 
            "entries": [
                {
                    "orderId": "124567.7654321",// Transaction identifier (max. 64 symbols)
                    "price": 1.99,              // The price of a purchase in a transaction currency
                    "currencyCode": "USD",      // Transaction currency(ISO 4217 format)
                    "timestamp": 1386259227,    // The date of a transaction
                    "level": 3,                 // Player's level
                    "inProgress": ["village"]   // Location (game level) in which an action has been performed
                },{     //If there is one more identical purchase for a report period
                    "orderId": "1224567.7634327",
                    "price": 1.99,
                    "currencyCode": "USD",
                    "timestamp": 1386259227,
                    "level": 3,
                    "inProgress": ["village"]
                }
            ]
        },{     //If there are other purchases for a report period 
            "name": "inapp_name_2",
            "entries": [
                {
                    "orderId": "1234567.7654321",
                    "price": 9.99,
                    "currencyCode": "USD",
                    "timestamp": 1386259227,
                    "level": 3,
                    "inProgress": ["town"]
                }
            ]
        }
    ]
    "tr" : [
        {
            "step" : 1,                // the number of a tutorial step that has been completed 
            "timestamp" : 1386259227,  // date
            "level" : 3,               // player's level
            "inProgress" : ["village"],// location (game level) in which an action has been performed
            …
        }
    ]
    "step" : -1  // The beginning of a tutorial(before completing the first step)
    "step" : 0   // The tutorial is skipped 
    "step" : -2  // The tutorial is completed (instead of the last step's number)
    "ce" : [
        {
            "name" : "house_purchase",          // The name of an event (max. 72 symbols)
            "entries" : [
                {
                    "t1" : 1231872631,          // The date of an event
                    "level" : 3,                // Player's level
                    "inProgress" : ["village"], // Location (game level) in which an action has been performed
                    "p" : {
                        "t1" : {
                            // up to 10 parameters grouped by data types
                            "double" : {        // Parameters with values - in numbers
                                "key1" : 1,     // The name of a parameter as a key(max. 32 symbols) and
                                                // the value of a parameter
                                "key2" : 2.123,
                                …
                            },
                            "string" : {        // Parameters with values - in strings
                                "key3" : "a",   //  The name of a parameter as a key(max. 32 symbols) and
                                                // the value of a parameter (max. 255 symbols)
                                "key4" : "abc",
                                …
                            }
                        }                       
                    }
                },
                {                               // If there are several events with the same name 
                "t1" : 1231872638,
                "level" : 3,                    
                "inProgress" : ["village"],
                "p" : {
                    "t1" : {
                        "double" : {
                            "key1" : 1,
                            "key2" : 2.123,
                            …
                        },
                        "string" : {
                            "key3" : "a", 
                            "key4" : "abc",
                            …
                        }
                    }                       
                }
            }
        },
        {                                       // If one user had several events with different names for
                                                // a reporting period, continue 
            …
        }
    ]
    "lu" : [
        {
            "level" : 10,              // Required. The level that a player got 
            "timestamp" : 1386259227,  // Required. The date of moving to the next level 
            "inProgress" : ["village"],// Location (game level)in which an action has been performed
            "balance" : {              // Optional. The balances of a game currency at the end of a level 
                "money1" : 123,        // The name of a game currency as a key and its amount 
                "money2" : 11,
                …
            },
            "spent" : {                // Optional. The amount of a currency spent on a level
                "money1" : 12,         // The name of a game currency as a key and its amount 
                "money2" : 2,
                "wood" : 12,
                …
            },
            "earned" : {               // Optional. The in-game currency that was earned on a level
                "money1" : 8,    
                "money2" : 2,
                "stone" : 1,
                …
            },
            "bought" : {                // Optional. The in-game currency that was bought on a level 
                "money1" : 10,
                "money2" : 2,
                …
            }
        },
        …
    ]
    "ip" :  [
        {
            "purchaseType" : "Weapon",  // The group of an item (max. 96 symbols)
            "purchaseId" : "Dagger",    // The unique name or ID of an item (max. 32 symbols)
            "purchaseAmount" : 1,       // The amount of items bought
            "purchasePrice" : 1.0,      // The price of an item (the overall price of a purchase if there 
                                        // are several identical items are bought) in an in-game currency 
            "purchasePriceCurrency" : "Coins",  // The name of an in-game currency that was used to buy
                                                // an item (max. 24 symbols)
            "timestamp" : 1231872631,   // The date of a purchase
            "level" : 3,                // Player's level
            "inProgress" : ["village"]  // Location (game level) in which an action has been performed
        },
        …
    ]
    "ip" :  [
        {
            "purchaseType" : "Weapon",  // The group of an item (max. 96 symbols)
            "purchaseId" : "Dagger",    // The unique name or ID of an item (max. 32 symbols)
            "purchaseAmount" : 1,       // The amount of items bought
            "purchasePrice" : 1.0,      // The price of an item (the overall price of a purchase if there 
                                        // are several identical items are bought) in an in-game currency 
            "purchasePriceCurrency" : "Coins",  // The name of an in-game currency that was used to buy
                                                // an item (max. 24 symbols)
            "timestamp" : 1231872631,   // The date of a purchase
            "level" : 3,                // Player's level
            "inProgress" : ["village"]  // Location (game level) in which an action has been performed
        },
        {
            "purchaseType" : "Weapon", 
            "purchaseId" : "Dagger",   
            "purchaseAmount" : 0,      
            "purchasePrice" : 2.0,     
            "purchasePriceCurrency" : "Gold", 
            "timestamp" : 1231872631,
            "level" : 3,               
            "inProgress" : ["village"]
        },
        …
    ]
    "pe" : [
        {
            "id" : "location2",         // The name of a location
            "level" : 3,                // Player's level at the moment of location completion 
            "params" : {                // Event parameters
                "source" : "location1", // The name of the previous location of a player 
                "difficulty" : 1,       // Optional. The level of difficulty of location completion 
                "success" : true,       // Success in location completion 
                "duration" : 180        // Optional. Time in seconds of location completion
            },
            "spent" : {                 // Optional. Resources spent during location completion
                "money1" : 12,          // The record of a resource in the format of 
                                        // resource's name - the amount of a resource
                "money2" : 2, 
                "wood" : 12
            }, 
             "earned" : {               // Optional. Resources that are received during location completion 
                 "money1" : 8, 
                 "money2" : 2, 
                 "stone" : 1
            },
            "timestamp" : 1234567890    // The time of exit from a location
        }
    ]
    "adrv": [{
    	"ad_network": "TestAdNetwork", //Name of the ad network responsible for the impression (from 1 to 100 symbols)
    	"revenue": 0.3434, //Reward for banner display in USD
    	"ad_unit": "TestAdUnit", //Banner name (from 1 to 100 symbols,optional)
    	"placement": "TestPlacement" //Banner placement (from 1 to 100 symbols, optional)
    }]
    "sc" : [
        {
            "socialNetwork" : "FB",   // The name of a social network. The value from the list
                                      // of constants for popular networks or your own string name
            "timestamp" : 1386259227, // The date of connection
            "level" : 3,              // Player's level
            "inProgress" : ["village"]// Location (game level) in which an action has been performed
        },
        ...
    ]
    "sp" : [
        {
            "socialNetwork" : "FB",   // The name of a social network. The value from the list of 
                                      // constants for popular networks or your own string name
            "postReason" : "levelup", // The reason of publication (max. 32 symbols). We recommend you
                                      // to group reasons instead of sending such names as "Level 99 reached".
            "timestamp" : 1386259227, // The date of publication 
            "level" : 3,              // Player's level 
            "inProgress" : ["village"]// Location (game level)in which an action has been performed
        },
        ...
    ]
    "wipe" : [
        {
            "saveRegistration": true,     // Optional. A new user will inherit registration dates after
                                          // an old user; a new user will not be registered by devtodev as
                                          // the new one. It might be useful when you test your app.
            "savePayingStatus": true,     // Optional. Old user payment data will be copied into a new
                                          // user card (number of payments, amounts and payment dates).
            "saveCheaterTester": true,    // Optional. cheater and tester labels will be copied into
                                          // a new user card. It might be useful when you test your app.
            "saveCustomProperties": true, // Optional. Old user custom fields and values from those fields
                                          // will be copied into the new user card.
            "timestamp" : 1234567890      // Optional. The time of exit from a location
        }
    ]
    {
    	"JohnDoe": {
    		"prev": "LittleJohn",
    		"ts" : [{ 
            	"isTrackingAllowed": false,
            	"timestamp" : 1386259227
    		}]
    	}
    }
    {
    	"JohnDoe": {
    		"prev": "LittleJohn",
    		"tr": [{
    			"step": 1,
    			"timestamp": 1386259227,
    			"level": 1,
    			"inProgress": ["village"]
    		}, {
    			"step": 2,
    			"timestamp": 1386259236,
    			"level": 1,
    			"inProgress": ["village"]
    		}, {
    			"step": 3,
    			"timestamp": 1386259288,
    			"level": 1,
    			"inProgress": ["town"]
    		}],
    		"gs": [{
    			"timestamp": 1386259227,
    			"length": 1250,
    			"level": 3
    		}],
    		"pl": [{
    			"data": {
    				"gender": 1
    			}
    		}],
    		"lu": [{
    			"level": 4,
    			"inProgress": ["village"],
    			"timestamp": 1442392006,
    			"balance": {
    				"Coins": 1234,
    				"Gold": 11
    			}
    		}],
    		"ce": [{
    			"name": "Round_finished",
    			"entries": [{
    				"t1": 1442392451,
    				"level": 4,
    				"inProgress": ["village"],
    				"p": {
    					"t1": {
    						"double": {
    							"Round_time": 83,
    							"Score": 2.123
    						},
    						"string": {
    							"Result": "Victory",
    							"Type": "Flawless"
    						}
    					}
    				}
    			}, {
    				"t1": 1442393455,
    				"level": 4,
    				"inProgress": ["town"],
    				"p": {
    					"t1": {
    						"double": {
    							"Round time": 102,
    							"Score": 1.5
    						},
    						"string": {
    							"Result": "Defeat",
    							"Type": "Shameful"
    						}
    					}
    				}
    			}]
    		}],
    		"pe": [{
    			"id": "town",
    			"level": 3,
    			"params": {
    				"source": "vilage",
    				"difficulty": 2,
    				"success": true,
    				"duration": 180
    			},
    			"spent": {
    				"Turns": 54,
    				"Boost Bomb": 1,
    				"Extra 5 Turns": 1
    			},
    			"earned": {
    				"Stars": 3,
    				"Score": 1200,
    				"Coins": 5
    			},
    			"timestamp": 1234567890
    		}],
    		"ip": [{
    			"purchaseType": "Weapon",
    			"purchaseId": "Dagger",
    			"purchaseAmount": 1,
    			"purchasePrice": 30.0,
    			"purchasePriceCurrency": "Coins",
    			"timestamp": 1442393479,
    			"level": 4,
    			"inProgress": ["village"]
    		}],
    		"rp": [{
    			"name": "Currency pack 1",
    			"entries": [{
    				"orderId": "1234567.7654321",
    				"inProgress": ["village"],
    				"level": 4,
    				"price": 1.99,
    				"currencyCode": "USD",
    				"timestamp": 1442393460
    			}]
    		}],
    		"sp": [{
    			"socialNetwork": "FB",
    			"postReason": "New level reached",
    			"level": 4,
    			"inProgress": ["village"],
    			"timestamp": 1442392006
    
    		}]
    	}
    }
    $url = 'https://api.devtodev.com/stat/v1/?api='.$api;
    $params = [
            $uid=>[
                'rp' =>[
                    [
                        'name' => $name,
                        'entries' =>[
                            [
                                'orderId' => $transactionId,
                                'price' => 100,
                                'currencyCode' => 'USD',
                                'timestamp' => time(),
                                'level'=> 5,
                                'inProgress'=> ['village']
                            ]
                        ]
                    ]
                ]
            ]
    ];
    
    $curlHandle = curl_init($url);
    curl_setopt($curlHandle, CURLOPT_HTTPHEADER, array('Content-Type: text/plain;charset=UTF-8'));
    curl_setopt($curlHandle, CURLOPT_RETURNTRANSFER, 1);
    curl_setopt($curlHandle, CURLOPT_POST, TRUE);
    curl_setopt($curlHandle, CURLOPT_USERAGENT, "Mozilla/4.0 (compatible;)");
    curl_setopt($curlHandle, CURLOPT_POSTFIELDS, gzencode(json_encode($params)));
    curl_setopt($curlHandle, CURLOPT_FOLLOWLOCATION, true);
    curl_setopt($curlHandle, CURLOPT_ENCODING, 'gzip');
    $response = curl_exec($curlHandle);

    User profile

    This generation of SDK is deprecated and is no longer supported. Information about the current version can be found here.

    In addition to basic methods, you can observe and change the user profiles data. A user profile is the set of properties describing the user, which can be divided into 4 groups:

    1. Cross-platform or custom user identifier. If this identifier is not set by a developer, then the device identifier is used.

    2. Automatically collected properties, including data about user's device, geography, app version, SDK, and some other data which can be received from SDK.

    3. The default set of user properties, which can be set by a developer. The set of this parameters works with separate methods. This set includes the data of the user's name, sex, age, e-mail, phone number and URL of user picture. Also, this set includes the mark of a user as a cheater.

    4. Custom set of user properties. In this case, a developer sets any user data he/she needs to know. The data is set in key-value format and can be numeric, string, array, or boolean. Each project can have up to 30 custom user properties.

    1. Cross-platform or custom user identifier. If this identifier is not set by a developer, then the device identifier is used.

    2. Automatically collected properties, including data about user's device, geography, app version, SDK, and some other data which can be received from SDK.

    3. The default set of user properties, which can be set by a developer. The set of this parameters works with separate methods. This set includes the data of the user's name, sex, age, e-mail, phone number, and URL of user picture. Also, this set includes the mark of a user as a cheater.

    4. Custom set of user properties. In this case, a developer sets any user data he/she needs to know. The data is set in key-value format and can be numeric, string, array, or boolean. Each project can have up to 30 custom user properties.

    1. Cross-platform or custom user identifier. If this identifier is not set by developer, then the device identifier is used.

    2. Automatically collected properties, including data about user's device, geography, app version, SDK, and some other data which can be received from SDK.

    3. Default set of user properties, which can be set by developer. The set of this parameters works with separate methods. This set includes the data of user's name, sex, age, e-mail, phone-number and url of user picture. Also this set includes the mark of user as cheater.

    4. Custom set of user properties. In this case developer sets any user data he/she needs to know. The data is set in key-value format and can be numeric, string, array or boolean. Each project can have up to 30 custom user properties.

    1. Cross-platform or custom user identifier. If this identifier is not set by developer, then the identifier which was set during the initialization is used.

    2. Automatically collected properties, including data about user's device, geography, app version, SDK, and some other data which can be received from SDK.

    3. Basic set of user properties, which can be set by developer. The set of this parameters works with separate methods. This set includes the data of user's name, sex, age, e-mail, phone-number and url of user picture. Also this set includes the mark of user as cheater.

    4. Custom set of user properties. In this case developer sets any user data he/she needs to know. The data is set in key-value format and can be numeric, string, array or boolean. Each project can have up to 30 custom user properties.

    1. Cross-platform or custom user identifier. If this identifier is not set by developer, then the device identifier is used.

    2. Automatically collected properties, including data about user's device, geography, app version, SDK, and some other data which can be received from SDK.

    3. Default set of user properties, which can be set by developer. The set of this parameters works with separate methods. This set includes the data of user's name, sex, age, e-mail, phone-number and url of user picture. Also this set includes the mark of user as cheater.

    4. Custom set of user properties. In this case developer sets any user data he/she needs to know. The data is set in key-value format and can be numeric, string, array or boolean. Each project can have up to 30 custom user properties.

    1. Cross-platform or custom user identifier. If this identifier is not set by developer, then the device identifier is used.

    2. Automatically collected properties, including data about user's device, geography, app version, SDK, and some other data which can be received from SDK.

    3. Default set of user properties, which can be set by developer. The set of this parameters works with separate methods. This set includes the data of user's name, sex, age, e-mail, phone-number and url of user picture. Also this set includes the mark of user as cheater.

    4. Custom set of user properties. In this case developer sets any user data he/she needs to know. The data is set in key-value format and can be numeric, string, array or boolean. Each project can have up to 30 custom user properties.

    1. Cross-platform or custom user identifier. If this identifier is not set by developer, then the device identifier is used.

    2. Automatically collected properties, including data about user's device, geography, app version, SDK, and some other data which can be received from SDK.

    3. Default set of user properties, which can be set by developer. The set of this parameters works with separate methods. This set includes the data of user's name, sex, age, e-mail, phone-number and url of user picture. Also this set includes the mark of user as cheater.

    4. Custom set of user properties. In this case developer sets any user data he/she needs to know. The data is set in key-value format and can be numeric, string, array or boolean. Each project can have up to 30 custom user properties.

    1. Cross-platform or custom user identifier. If this identifier is not set by developer, then the device identifier is used.

    2. Automatically collected properties, including data about user's device, geography, app version, SDK, and some other data which can be received from SDK.

    3. Default set of user properties, which can be set by developer. The set of this parameters works with separate methods. This set includes the data of user's name, sex, age, e-mail, phone-number and url of user picture. Also this set includes the mark of user as cheater.

    4. Custom set of user properties. In this case developer sets any user data he/she needs to know. The data is set in key-value format and can be numeric, string, array or boolean. Each project can have up to 30 custom user properties.

    You can segment users by all the properties in My Apps section of an application.

    Cross-platform user ID

    This method is used for user initialization in the applications that are the parts of cross-platform project.

    We recommend you to apply this method before the SDK initialization, otherwise the user identifier from the previous session will be used since the SDK initialization moment till the setUserID method call.

    If your cross-platform application is supposed to be used without cross-platform authorization, don't use the setUserID method or use the empty string ("") as the user identifier. SDK will assign the unique identifier to user. This identifier will be used until the real cross-platform identifier is assigned to the user.

    If your application allows user to re-login (changing the user during the working session of the application), then the setUserID method should be called just after the authorization. You don't need to call the SDK initialization one more time.

    To see which identifier is used at the moment:

    Replace Cross-platform user ID

    If it is possible to replace the user identifier in your application (for example, to make changes in the login/user id for a particular user), use this method at the moment of replacing the identifier.

    Don't use this method if you're going to perform the user's re-login.

    If it is possible to replace the user identifier in your application (say, to make changes in the login/user id for particular user), use this method at the moment of replacing the identifier.

    Current user level

    This method is used in cross-platform applications and applications with data synchronization.

    This method is required for user's level data initialization. We recommend you to use the setCurrentLevel method just after the user initialization (using the setUserID method).

    Don't use the setCurrentLevel method at the moment of user's level up. We recommend you to use the levelUp method in this case.

    This method is used in cross-platform applications and applications with data synchronization.

    This method is required for user's level data initialization. We recommend you to use the setCurrentLevel method just after the user initialization (using the

    Cheater

    In case you have your own methods of determining cheaters in the application, you can have such users marked. Payments made by them will not be taken into account in the statistics.

    Blueprint

    Name

    User's name. Default user profile property.

    We strongly recommend not to use this property because it refers to .

    Blueprint

    --

    Age

    User's age in years. Default user profile property.

    We strongly recommend not to use this property because it refers to .

    Blueprint

    Gender

    User's gender. Default user profile property.

    We strongly recommend not to use this property because it refers to .

    Blueprint

    E-mail

    User's e-mail. Default user profile property.

    We strongly recommend not to use this property because it refers to .

    Blueprint

    Phone number

    User's phone. Default user profile property.

    We strongly recommend not to use this property because it refers to .

    Blueprint

    Photo

    User's photo URL. Default user profile property.

    We strongly recommend not to use this property because it refers to .

    Blueprint

    Custom user property

    Each project in devtodev can have up to 30 custom user properties.Here is how you can set properties on the current user profile:

    Attention! We strongly recommend that you do not use these properties to transfer and store data that fits the definition of !

    Blueprint

    Increment of the custom property

    Increments the given numeric properties by the given values.

    Blueprint

    Append to custom property (deprecated)

    Adds values to a list-valued property. If the property does not currently exist, it will be created with the given list as it's value. If the property exists and is not list-valued, the append will be ignored.

    Union with custom property (deprecated)

    Adds values to a list-valued property only if they are not already present in the list. If the property does not currently exist, it will be created with the given list as it's value. If the property exists and is not list-valued, the union will be ignored.

    Removing of the custom property

    Removes a property or a list of properties and their values from the current user's profile.

    Blueprint

    Clearing of the all custom properties

    Blueprint

    Code

    This method is used for user initialization in the applications which are the parts of cross-platform project.

    We recommend you to apply this method before the SDK initialization, otherwise the user identifier from the previous session will be used since the SDK initialization moment till the setUserID method call.

    If your cross-platform application supposes to be used without cross-platform authorization, don't use the setUserID method or use the empty string ("") as the user identifier. SDK will assign the unique identifier to user. This identifier will we used until the real cross-platform identifier assigns to the user.

    If your application allows user to re-login (changing the user during the working session of application), then the setUserID method should be called just after the authorization. You don't need to call the SDK initialization one more time.

    /**
    * Initializes the user with the specified cross-platform identifier
    * @param String userId - unique cross-platform user ID used
    * for user identification on your server.
    */
    DevToDev.setUserId(String userId);

    To see which identifier is used at the moment:

    /**
    * Returns current cross-platform user id
    * @return userId - current cross-platform user id (or null, if sdk not initialized)
    */
    DevToDev.getUserId();

    This method is used for user initialization in the applications which are the parts of cross-platform project.

    We recommend you to apply this method before the SDK initialization, otherwise the user identiticator from the previous session will be used since the SDK initialization moment till the UserID property call.

    If your cross-platform application supposes to be used without cross-platform authorization, don't use the UserID property or use the empty string ("") as the user identifier. SDK will assign the unique identifier to user. This identifier will we used until the real cross-platform identifier assigns to the user.

    If your application allows user to re-login (changing the user during the working session of application), then the UserID property should be called just after the authorization. You don't need to call the SDK initialization one more time.

    This property also allows you to see which identifier is used at the moment.

    This method is used for user initialization in the applications which are the parts of cross-platform project. You also can use this identifier in non-crossplatform projects, but in your app the own unique user identifier is used.

    We recommend you to apply this method before the SDK initialization, otherwise the user identifier from the previous session will be used since the SDK initialization moment till the setCrossplatformUserId method call.

    If your cross-platform application supposes to be used without cross-platform authorization, don't use the setCrossplatformUserId method or use the empty string ("") as the user identifier. SDK will assign the unique identifier to user. This identifier will be used until the real cross-platform identifier assigns to the user.

    If your application allows user to re-login (changing the user during the working session of application), then the setCrossplatformUserId method should be called just after the authorization. You don't need to call the SDK initialization one more time.

    To see which identifier is used at the moment:

    This method is used for user initialization in the applications which are the parts of cross-platform project.

    We recommend you to apply this method before the SDK initialization, otherwise the user identifier from the previous session will be used since the SDK initialization moment till the UserID property call.

    If your cross-platform application supposes to be used without cross-platform authorization, don't use the UserID property or use the empty string ("") as the user identifier. SDK will assign the unique identifier to user. This identifier will we used until the real cross-platform identifier assigns to the user.

    If your application allows user to re-login (changing the user during the working session of application), then the UserID property should be called just after the authorization. You don't need to call the SDK initialization one more time.

    This property also allows you to see which identifier is used at the moment.

    This method is used for user initialization in the applications which are the parts of cross-platform project.

    We recommend you to apply this method before the SDK initialization, otherwise the user identifier from the previous session will be used since the SDK initialization moment till the setUserID method call.

    If your cross-platform application supposes to be used without cross-platform authorization, don't use the setUserID method or use the empty string ("") as the user identifier. SDK will assign the unique identifier to user. This identifier will we used until the real cross-platform identifier assigns to the user.

    If your application allows user to re-login (changing the user during the working session of application), then the setUserID method should be called just after the authorization. You don't need to call the SDK initialization one more time.

    To see which identifier is used at the moment:

    This method is used for user initialization in the applications which are the parts of cross-platform project.

    We recommend you to apply this method before the SDK initialization, otherwise the user identifier from the previous session will be used since the SDK initialization moment till the setUserID method call.

    If your cross-platform application supposes to be used without cross-platform authorization, don't use the setUserID method or use the empty string ("") as the user identifier. SDK will assign the unique identifier to user. This identifier will we used until the real cross-platform identifier assigns to the user.

    If your application allows user to re-login (changing the user during the working session of application), then the setUserID method should be called just after the authorization. You don't need to call the SDK initialization one more time.

    To see which identifier is used at the moment:

    This method is used for user initialization in the applications which are the parts of cross-platform project.

    We recommend you to apply this method before the SDK initialization, otherwise the user identifier from the previous session will be used since the SDK initialization moment till the setUserID method call.

    If your cross-platform application supposes to be used without cross-platform authorization, don't use the setUserID method or use the empty string ("") as the user identifier. SDK will assign the unique identifier to user. This identifier will we used until the real cross-platform identifier assigns to the user.

    If your application allows user to re-login (changing the user during the working session of application), then the setUserID method should be called just after the authorization. You don't need to call the SDK initialization one more time.

    Blueprint

    Code

    To see which identifier is used at the moment:

    Blueprint

    Code

    Don't use this method if you're going to perform the user's re-login.

    If it is possible to replace the user identifier in your application (say, to make changes in the login/user id for particular user), use this method at the moment of replacing the identifier.

    Don't use this method if you're going to perform the user's re-login.

    If it is possible to replace the cross-platform user identifier in your application (say, to make changes in the login/user id for particular user), use this method at the moment of replacing the identifier.

    Don't use this method if you're going to perform the user's re-login.

    If it is possible to replace the user identifier in your application (say, to make changes in the login/user id for particular user), use this method at the moment of replacing the identifier.

    Don't use this method if you're going to perform the user's re-login.

    If it is possible to replace the user identifier in your application (say, to make changes in the login/user id for particular user), use this method at the moment of replacing the identifier.

    Don't use this method if you're going to perform the user's re-login.

    If it is possible to replace the user identifier in your application (say, to make changes in the login/user id for particular user), use this method at the moment of replacing the identifier.

    Don't use this method if you're going to perform the user's re-login.

    If it is possible to replace the user identifier in your application (say, to make changes in the login/user id for particular user), use this method at the moment of replacing the identifier.

    Don't use this method if you're going to perform the user's re-login.

    Blueprint

    Field

    Type

    Code

    setUserID
    method).

    Don't use the setCurrentLevel method at the moment of user's level up. We recommend you to use the levelUp method in this case.

    This method is used in cross-platform applications and applications with data synchronization.

    This method is required for user's level data initialization. We recommend you to use the SetCurrentLevel method just after the user initialization (using the UserID method).

    Don't use the SetCurrentLevel method at the moment of user's level up. We recommend you to use the levelUp method in this case.

    If your app uses the user's level mark, we recommend to use this method after every SDK initialization, as soon as data of user's level is available to the application.

    This method is used in cross-platform applications and applications with data synchronization.

    This method is required for user's level data initialization. We recommend you to use the SetCurrentLevel method just after the user initialization (using the UserID method).

    Don't use the SetCurrentLevel method at the moment of user's level up. We recommend you to use the levelUp method in this case.

    This method is used in cross-platform applications and applications with data synchronization.

    This method is required for user's level data initialization. We recommend you to use the setCurrentLevel method just after the user initialization (using the setUserID method).

    Don't use the setCurrentLevel method at the moment of user's level up. We recommend you to use the levelUp method in this case.

    This method is used in cross-platform applications and applications with data synchronization.

    This method is required for user's level data initialization. We recommend you to use the setCurrentLevel method just after the user initialization (using the setUserID method).

    Don't use the setCurrentLevel method at the moment of user's level up. We recommend you to use the levelUp method in this case.

    This method is used in cross-platform applications and applications with data synchronization.

    This method is required for user's level data initialization. We recommend you to use the setCurrentLevel method just after the user initialization (using the setUserID method).

    Don't use the setCurrentLevel method at the moment of user's level up. We recommend you to use the levelUp method in this case.

    Blueprint

    Code

    isCheater

    bool

    True if user is a cheater

    Code

    FString

    User's name

    Code

    Age

    int32

    User's age in years

    Code

    Gender

    FString

    User's gender ('male', 'female', 'unknown')

    Code

    Email

    FString

    User's e-mail

    Code

    Phone

    FString

    User's phone

    Code

    Photo

    FString

    User's photo URL.

    Code

    Attributes

    TArray<FAnalyticsEventAttr>

    Key-value array to set custom property, where key is a user property name, value is a property value

    Code

    Attributes

    TArray<FAnalyticsEventAttr>

    Key-value array, where key is a user property name, value is increment step

    Code

    Attributes

    TArray<FString>

    An array of property names to be removed.

    Code

    Field

    Type

    Description

    Field

    Type

    Description

    Field

    Type

    Description

    Field

    Type

    Description

    Field

    Type

    Description

    Field

    Type

    Description

    Field

    Type

    Description

    Field

    Type

    Description

    Field

    Type

    Description

    Field

    Type

    Description

    personal data
    personal data
    personal data
    personal data
    personal data
    personal data
    personal data
    /**
    * Initializes the user with the specified cross-platform identifier
    * @param NSString userId - unique cross-platform user ID used
    * for user identification on your server.
    */
    [DevToDev setUserId: (NSString *) userId];
    /**
    * Returns current cross-platform user id
    * @return userId - current cross-platform user id
    */
    [DevToDev getUserId];

    Name

    Secondary methods

    Ad impression

    The event is used for individual tracking of ad revenue on user devices. The method is used if there are CPI data available on the client device (they can be obtained from the ad network SDK).

    Do not apply this method if you use ad networks that utilize the server-server protocol for sending ad revenue data (ironSource, AppLovin MAX, and Fyber networks) and you already set up this method of data collection because if you use both data sources, your revenue data may be duplicated.

    Parameter
    Type
    Restrictions
    Description

    Connecting to social networks

    The event is used to track connections to social media channels.

    Use the following constants to specify a social network:

    .facebook, .vkontakte , .twitter, .googleplus, .whatsapp, .viber, .evernote, .googlemail, .linkedin, .pinterest, .qzone, .reddit, .renren, .tumblr

    Or create an object with the desired social media name.

    Use the following constants to specify a social network:

    .facebook, .vkontakte , .twitter, .googleplus, .whatsapp, .viber, .evernote, .googlemail, .linkedin, .pinterest, .qzone, .reddit, .renren, .tumblr

    Or create an object with the desired social media name.

    Posting to social networks

    Track social media posts and analyze their effectiveness and virality. Pass the event after the post has been approved by social media.

    Referrer

    If you have referral information, you can pass it using the following method:

    Force dispatch of accumulated events

    To send an event packet before it is full (10 events, by default) or before the end of the period of its formation (2 minutes, by default), you can use immediate dispatch.

    We don’t recommend using this method unless absolutely necessary! When the Real payment event is created, the forced dispatch of the packet occurs automatically.

    Setters & Getters

    When working with getters you should take into account that the new devtodev SDK is completely asynchronous. The execution result must be processed within the completionHandler.

    All set and get methods need to be called only after the initialization of the SDK.

    It is also worth remembering that the SDK will call the callback in background queues, so we recommend that you transfer the processing of return values to the queue you need.

    For example:

    Setting User Tracking Status (GDPR)

    This method denies/allows tracking of user data and also implements the right to be forgotten in accordance with the requirements of the GDPR.

    When this method is called with the 'false' value, the SDK sends a command to the server to delete all personal user data that was collected by devtodev in this application, blocking further user data collection.

    The user will remain in the devtodev system only as an impersonal unit in the previously aggregated metrics.

    If it is set to ‘true', tracking can be enabled again. In this case, the user will be considered new.

    To enable/disable user tracking by the devtodev system. Bool type.

    Getting device ID

    Get device ID. String type.

    Getting the devtodev SDK version

    Get the version of the integrated devtodev SDK. String type.

    Obtaining user tracking status (GDPR)

    Retrieving the saved state of the user tracking permission by the devtodev system. See “Setting User Tracking Status”. Bool type.

    Getting devtodev ID

    devtodev ID is the primary numeric identifier for the device/user account in the devtodev database. Using devtodev ID, you are sure to find the user in devtodev.

    The identifier will be received from the server some time after the initialization of the SDK.

    If you have set counting by users, a separate devtodev id will be issued for each device user.

    To obtain the devtodev ID, you need to pass the listener to DTDAnalytics:

    The delegate must implement the func didReceiveDevtodevId(with devtodevId: Int)

    The didReceiveDevtodevId method will be called with every ID change on the server side.

    To obtain the devtodev ID, you need to pass the listener to DTDAnalytics:

    The delegate must implement the

    Initialization callback

    To receive a callback when the SDK initialization is complete, you can use a method that will implement the initialization callback. When the SDK completes the initialization, the callback will be called on the main application thread.

    We recommend implementing a callback before calling initialization.

    /**
    * Initializes the user with the specified cross-platform identifier
    * property allows to get and to set unique cross-platform user ID used
    * for user identification on your server.
    */
    DevToDev.SDK.UserId = userId;
    /**
    * Initializes the user with the specified cross-platform identifier
    * @param {string} crossplatformUserId - unique cross-platform user ID used
    * for user identification on your server.
    */
    
    devtodev.setCrossplatformUserId(crossplatformUserId);
    /**
    * Returns current cross-platform user id
    * @return crossPlatformUserId - current cross-platform user id
    */
    
    devtodev.getCrossplatformUserId();
    /// <summary>
    /// Initializes the user with the specified cross-platform identifier
    /// property allows to get and to set unique cross-platform user ID used
    /// for user identification on your server.</summary>
    DevToDev.Analytics.UserId = userId;
    /**
    * Initializes the user with the specified cross-platform identifier
    * @param NSString userId - unique cross-platform user ID used
    * for user identification on your server.
    */
    [DevToDev setUserId: (NSString *) userId];
    /**
    * Returns current cross-platform user id
    * @return userId - current cross-platform user id
    */
    [DevToDev getUserId];
    /**
    * Initializes the user with the specified cross-platform identifier
    * @param userId - unique cross-platform user ID used for user identification on your server.
    */
    DevToDev.setUserId(userId:String);
    /**
    * Returns current cross-platform user id
    * @return userId - current cross-platform user id (string or null, if sdk not initialized)
    */
    DevToDev.getUserId();
    /**
    * Replaces current cross-platform user id
    * Attention! Don't use this method if you're going to perform the user's relogin.
    * <param name="prevUserId">Old user identifier</param>
    * <param name="userId">New user identifier</param>
    */
    DevToDev.SDK.ReplaceUserId(string prevUserId, string userId);
    /**
    * Replaces current cross-platform user id
    * Attention! Don't use this method if you're going to perform the user's relogin.
    * @param {string} newCrossPlatformID - new cross-platform user ID
    */
    
    devtodev.replaceCrossplatformUserId(newCrossPlatformID);
    /// <summary> Replaces current cross-platform user id. 
    /// Attention! Don't use this method if you're going to perform the user's relogin.</summary>
    /// <param name="prevUserId"> Old user identifier </param>
    /// <param name="userId"> New user identifier </param>
    DevToDev.Analytics.ReplaceUserId(string prevUserId, string userId);
    /**
    * Replaces current cross-platform user id
    * Attention! Don't use this method if you're going to perform the user's relogin.
    * @param NSString prevUserId - previous cross-platform user ID
    * @param NSString userId - new cross-platform user ID
    */
    [DevToDev replaceUserId: (NSString *) prevUserId to: (NSString *) userId];
    /**
    * Replaces current cross-platform user id
    * Attention! Don't use this method if you're going to perform the user's relogin.
    * @param prevUserId - previous cross-platform user ID
    * @param userId - new cross-platform user ID
    */
    DevToDev.replaceUserId(prevUserId:String, userId:String);
    /**
    * Initializes the current user level. Required if level feature used in the app.
    * <param name="level">Current game level of the player</param>
    */
    DevToDev.SDK.SetCurrentLevel(int level);
    /**
    * Initializes the current user level. Required if level feature used in the app.
    * @param {number} currentUserLevel - current game level of the player.
    */
    
    devtodev.setCurrentLevel(currentUserLevel);
    /// <summary> Initializes the current user level. Required if level feature used in the app. </summary>
    /// <param name="level"> current game level of the player </param>
    DevToDev.Analytics.SetCurrentLevel(int level);
    /**
    * Initializes the current user level. Required if level feature used in the app.
    * @param NSUInteger level - current game level of the player.
    */
    [DevToDev setCurrentLevel: (NSUInteger) level];
    /**
    * Initializes the current user level. Required if level feature used in the app.
    * @param level - current game level of the player.
    */
    DevToDev.setCurrentLevel(level:int);
    /**
    * Replaces current cross-platform user id
    * Attention! Don't use this method if you're going to perform the user's relogin.
    * @param NSString prevUserId - previous cross-platform user ID
    * @param NSString userId - new cross-platform user ID
    */
    [DevToDev replaceUserId: (NSString *) prevUserId to: (NSString *) userId];
    /**
    * Initializes the current user level. Required if level feature used in the app.
    * @param NSUInteger level - current game level of the player.
    */
    [DevToDev setCurrentLevel: (NSUInteger) level];
    /**
    * Mark user if it's cheater.
    * @param BOOL isCheater - true if user is a cheater
    */
    DevToDev.activeUser.cheater = isCheater;
    /**
    * Mark user if it's cheater.
    * @param boolean isCheater - true if user is a cheater
    */
    DevToDev.getActivePlayer().setCheater(boolean isCheater);
    /**
    * Mark user if it's cheater.
    * <param name="isCheater">true if user is a cheater</param>
    */
    DevToDev.SDK.ActiveUser.SetCheater(boolean isCheater);
    /**
    * Mark user if it's cheater.
    * @param {boolean} isCheater - true if user is a cheater
    */
    
    devtodev.user.cheater(isCheater);
    /// <summary> Mark user if it's cheater. </summary>
    /// <param name="isCheater"> true if user is a cheater </param>
    DevToDev.Analytics.ActiveUser.Cheater = isCheater;
    /**
    * Mark user if it's cheater.
    * @param BOOL isCheater - true if user is a cheater
    */
    DevToDev.activeUser.cheater = isCheater;
    /**
    * Mark user if it's cheater.
    * @param isCheater - true if user is a cheater
    */
    DevToDev.getActiveUser().SetCheater(isCheater:Boolean);
    /**
    * Track user's name
    * @param NSString name - User's name.
    */
    DevToDev.activeUser.name = name;
    /**
    * Track user's name
    * @param String name - User's name
    */
    DevToDev.getActivePlayer().setName(String name);
    /**
    * Track user's name
    * <param name="name">User's name</param>
    */
    DevToDev.SDK.ActiveUser.SetName(string name);
    /**
    * Track user's name
    * @param {string} name - User's name.
    */
    
    devtodev.user.name(name);
    /// <summary> Track user's name </summary>
    /// <param name="name"> User's name </param>
    DevToDev.Analytics.ActiveUser.Name = name;
    /**
    * Track user's name
    * @param NSString name - User's name.
    */
    DevToDev.activeUser.name = name;
    /**
    * Track user's name
    * @param name - User's name.
    */
    DevToDev.getActiveUser().SetName(name:String);
    /**
    * Track user's age
    * @param NSNumber age - User's age
    */
    DevToDev.activeUser.age = age;
    /**
    * Track user's age
    * @param int age - User's age
    */
    DevToDev.getActivePlayer().setAge(int age);
    /**
    * Track user's age
    * <param name="age">User's age</param>
    */
    DevToDev.SDK.ActiveUser.SetAge(int age);
    /**
    * Track user's age
    * @param {number} age - User's age.
    */
    
    devtodev.user.age(age);
    /// <summary> Track user's age </summary>
    /// <param name="age"> User's age </param>
    DevToDev.Analytics.ActiveUser.Age = age;
    /**
    * Track user's age
    * @param NSNumber age - User's age
    */
    DevToDev.activeUser.age = age;
    /**
    * Track user's age
    * @param age - User's age in years
    */
    DevToDev.getActiveUser().SetAge(age:int);
    /**
    * Track user's
    * @param DTDGender gender - User's gender. (Unknown, Male, Female).
    */
    DevToDev.activeUser.gender = gender;
    /**
    * Track user's gender
    * @param Gender gender - User's gender. (Unknown, Male, Female).
    */
    DevToDev.getActivePlayer().setGender(Gender gender);
    /**
    * Track user's gender.
    * <param name="gender">User's gender. (Unknown, Male, Female)</param>
    */
    DevToDev.SDK.ActiveUser.SetGender(DevToDev.Gender gender);
    /**
    * Track user's gender
    * @param {number} gender - User's gender. (0 - Unknown, 1 - Male, 2 - Female).
    */
    
    devtodev.user.gender(gender);
    /// <summary> Track user's gender </summary>
    /// <param name="gender"> User's gender. (Unknown, Male, Female) </param>
    DevToDev.Analytics.ActiveUser.Gender = gender;
    /**
    * Track user's
    * @param DTDGender gender - User's gender. (Unknown, Male, Female).
    */
    DevToDev.activeUser.gender = gender;
    /**
    * Track user's
    * @param gender - User's gender. (Unknown == 0, Male == 1, Female == 2).
    */
    DevToDev.getActiveUser().SetGender(gender:int);
    /**
    * Track user's e-mail
    * @param NSString email - User's e-mail.
    */
    DevToDev.activeUser.email = email;
    /**
    * Track user's e-mail
    * @param String email - User's e-mail.
    */
    DevToDev.getActivePlayer().setEmail(String email);
    /**
    * Track user's e-mail
    * <param name="email">User's e-mail</param>
    */
    DevToDev.SDK.ActiveUser.SetEmail(string email);
    /**
    * Track user's e-mail
    * @param {string} email - User's e-mail.
    */
    devtodev.user.email(email);
    /// <summary> Track user's e-mail </summary>
    /// <param name="email"> User's e-mail </param>
    DevToDev.Analytics.ActiveUser.Email = email;
    /**
    * Track user's e-mail
    * @param NSString email - User's e-mail.
    */
    DevToDev.activeUser.email = email;
    /**
    * Track user's e-mail
    * @param email - User's e-mail.
    */
    DevToDev.getActiveUser().SetEmail(email:String);
    /**
    * Track user's phone number
    * @param NSString phoneNumber - User's phone number.
    */
    DevToDev.activeUser.phone = phoneNumber;
    /**
    * Track user's phone number
    * @param String phoneNumber - User's phone number.
    */
    DevToDev.getActivePlayer().setPhone(String phoneNumber);
    /**
    * Track user's phone number
    * <param name="phoneNumber">User's phone number</param>
    */
    DevToDev.SDK.ActiveUser.SetPhone(string phoneNumber);
    /**
    * Track user's phone number
    * @param {string} phone_number - User's phone number.
    */
    
    devtodev.user.phone(phone_number);
    /// <summary> Track user's phone number </summary>
    /// <param name="phoneNumber"> User's phone number </param>
    DevToDev.Analytics.ActiveUser.Phone = phoneNumber;
    /**
    * Track user's phone number
    * @param NSString phoneNumber - User's phone number.
    */
    DevToDev.activeUser.phone = phoneNumber;
    /**
    * Track user's phone number
    * @param phoneNumber - User's phone number.
    */
    DevToDev.getActiveUser().SetPhone(phoneNumber:String);
    /**
    * Track user's photo URL
    * @param NSString photoUrl - User's photo URL.
    */
    DevToDev.activeUser.photo = photoUrl;
    /**
    * Track user's photo URL
    * @param String photoUrl - User's photo url.
    */
    DevToDev.getActivePlayer().setPhoto(String photoUrl);
    /**
    * Track user's photo URL
    * <param name="photoUrl">User's photo url</param>
    */
    DevToDev.SDK.ActiveUser.SetPhoto(string photoUrl);
    /**
    * Track user's photo URL
    * @param {string} photo_url - User's phone number.
    */
    
    devtodev.user.photo(photo_url);
    /// <summary> Track user's photo URL </summary>
    /// <param name="photoUrl"> User's photo url </param>
    DevToDev.Analytics.ActiveUser.Photo = photoUrl;
    /**
    * Track user's photo URL
    * @param NSString photoUrl - User's photo URL.
    */
    
    DevToDev.activeUser.photo = photoUrl;
    /**
    * Track user's photo URL
    * @param photoUrl - User's photo url.
    */
    DevToDev.getActiveUser().SetPhoto(photoUrl:String);
    /**
     * Set properties on a user data.
     *
     * ### Usage:
     *     [DevToDev.activeUser setUserDataWithKey:@"Hair color" andValue: @"copper red"];
     *  properties can have string, integer, date or list type
     *
     * @param NSString key - the name of the property
     * @param {*} value -  a value to set on the given property
     */
    [DevToDev.activeUser setUserDataWithKey: (NSString *) key andValue: (id) value];
    
    /**
     * Set multiple properties at once
     *
     * ### Usage:
     *     [DevToDev.activeUser setUserData:@{
     *         @"Hair color"   : @"blonde",
     *           @"Last payment" : 100,
     *           @"Last order"   : @[ 
     *             @"Coloring",
     *             @"Hair Straightening"
     *         ],
     *         @"Order date"   : [NSDate date]
     *     }];
     *  properties can have string, integer, date or list type
     *
     * @param NSDictionary userData - an associative array of names and values.
     */
    [DevToDev.activeUser setUserData: (NSDictionary *) userData];
    /**
     * Set properties on a user data.
     *
     * ### Usage:
     *     DevToDev.getActivePlayer().setUserData("Hair color", "copper red");
     *  properties can have String, Number or Collection type
     *
     * @param String key - the name of the property
     * @param {*} value -  a value to set on the given property
     */
    DevToDev.getActivePlayer().setUserData(String key, Object value);
    
    /**
     * Set multiple properties at once
     *
     * ### Usage:
     *     final Map<String, Object> userData = new HashMap<String, Object>();
     *     userData.put("Hair color", "blonde");
     *     userData.put("Last payment", 100);
     *     final List<Object> lastOrder = new ArrayList<Object>();
     *     lastOrder.add("Coloring");
     *     lastOrder.add("Hair Straightening");
     *     userData.put("Last order", lastOrder);
     *     DevToDev.getActivePlayer().setUserData(userData);
     *  properties can have String, Number or Collection type
     *
     * @param Map<String, Object> userData - an associative array of names and values.
     */
    DevToDev.getActivePlayer().setUserData(final Map<String, Object> userData);
    /**
     * Set properties on a user data.
     *
     * ### Usage:
     *     DevToDev.SDK.ActiveUser.SetUserData("Hair color", "copper red");
     *  properties can have string, int, double or List<object> type
     *
     * <param name="key">property name</param>
     * <param name="value">a value to set on the given property</param>
     */
    DevToDev.SDK.ActiveUser.SetUserData(string key, object value);
    
    /**
     * Set multiple properties at once
     *
     * ### Usage:
     *     Dictionary<string, object> userData = new Dictionary<string, object>();
     *     userData.Add("Hair color", "blonde");
     *       userData.Add("Last payment", 100);
     *     List<object> lastOrder = List<object>();
     *     lastOrder.Add("Coloring");
     *     lastOrder.Add("Hair Straightening");
     *     userData.Add("Last order", lastOrder);
     *     DevToDev.SDK.ActiveUser.SetUserData(userData);
     *  properties can have string, int, double or List<object> type
     *
     * <param name="userData">an associative array of names and values</param>
     */
    DevToDev.SDK.ActiveUser.SetUserData(Dictionary<string, object> userData);
    /*
     * Set properties on a user data.
     *
     * ### Usage:
     *     devtodev.user.set('Hair color', 'copper red');
     *
     *     // to set multiple properties at once
     *     devtodev.user.set({
     *         'Hair color': 'blonde',
     *            'Last payment': 100,
     *            'Last order': ['Coloring','Hair Straightening'],
     *         'Order date': new Date()
     *     });
     *     // properties can be strings, integers, dates, or lists
     *
     * @param {Object|String} prop If a string, this is the name of the property. If an object, this is an associative array of names and values.
     * @param {*} [val] A value to set on the given property
     */
    devtodev.user.set(prop, val);
    /// <summary> Set properties on a user data. </summary>
    /// <example> Usage:
    /// 
    ///     DevToDev.Analytics.ActiveUser.SetUserData("Hair color", "copper red");
    ///     //properties can have string, int, double or List<object> type
    /// 
    /// </example>
    /// <param name="key">property name</param>
    /// <param name="value">a value to set on the given property</param>
    DevToDev.Analytics.ActiveUser.SetUserData(string key, object value);
    
    /// <summary> Set multiple properties at once. </summary>
    /// <example> Usage:
    /// 
    ///     Dictionary<string, object> userData = new Dictionary<string, object>();
    ///     userData.Add("Hair color", "blonde");
    ///     userData.Add("Last payment", 100);
    ///     List<object> lastOrder = List<object>();
    ///     lastOrder.Add("Coloring");
    ///     lastOrder.Add("Hair Straightening");
    ///     userData.Add("Last order", lastOrder);
    ///     DevToDev.Analytics.ActiveUser.SetUserData(userData);
    ///     //properties can have string, int, double or List<object> type
    /// 
    /// </example>
    /// <param name="userData">an associative array of names and values</param>
    DevToDev.Analytics.ActiveUser.SetUserData(Dictionary<string, object> userData);
    /**
     * Set properties on a user data.
     *
     * ### Usage:
     *     [DevToDev.activeUser setUserDataWithKey:@"Hair color" andValue: @"copper red"];
     *  properties can have string, integer, date or list type
     *
     * @param NSString key - the name of the property
     * @param {*} value -  a value to set on the given property
     */
    [DevToDev.activeUser setUserDataWithKey: (NSString *) key andValue: (id) value];
    
    /**
     * Set multiple properties at once
     *
     * ### Usage:
     *     [DevToDev.activeUser setUserData:@{
     *         @"Hair color"   : @"blonde",
     *           @"Last payment" : 100,
     *           @"Last order"   : @[ 
     *             @"Coloring",
     *             @"Hair Straightening"
     *         ],
     *         @"Order date"   : [NSDate date]
     *     }];
     *  properties can have string, integer, date or list type
     *
     * @param NSDictionary userData - an associative array of names and values.
     */
    [DevToDev.activeUser setUserData: (NSDictionary *) userData];
    /**
     * Set properties on a user data.
     * ### Usage:
     *     DevToDev.getActiveUser().SetUserData("Hair color", "copper red");
     *  properties can have String, Number or Array type
     *
     * @param key - the name of the property
     * @param value -  a value to set on the given property
     */
    DevToDev.getActiveUser().SetUserData(key:String, value:Object);
    
    /**
     * Set multiple properties at once
     *
     * ### Usage:
     *     var userData:Dictionary = new Dictionary();
     *     userData["Hair color"] = "blonde";
     *       userData["Last payment"] = 100;
     *     var lastOrder:Array = new Array();
     *     lastOrder.push("Coloring");
     *     lastOrder.push("Hair Straightening");
     *     userData["Last order"] = lastOrder;
     *     DevToDev.getActiveUser().SetUserDataMany(userData);
     *  properties can have String, Number or Collection type
     *
     * @param userData - an associative array of names and values.
     */
    DevToDev.getActiveUser().SetUserDataMany(userData:Dictionary);
    /**
     * Increments or decrements numeric user's properties.
     * ### Usage:
     *     [DevToDev.activeUser incrementWithKey: @"Rounds played" andValue: 1];
     *
     *     // to decrement a counter, pass a negative number
     *     [DevToDev.activeUser incrementWithKey: @"Rounds played" andValue: -1];
     *
     * @param NSString key - the name of the property
     * @param NSNumber value - an amount to increment the given property
     */
    [DevToDev.activeUser incrementWithKey: (NSString *) key andValue: (id) value];
    
    /**
     * Increments or decrements multiple numeric user's properties at once.
     *  ### Usage:
     *     [DevToDev.activeUser increment: @{
     *         @"Rounds played"  : 1,
     *         @"Enemies killed" : 6
     *     }];
     *
     * @param NSDictionary values - an associative array of property names and numeric values.
     */
    [DevToDev.activeUser increment: (NSDictionary *) values];
    /**
     * Increments or decrements numeric user's properties.
     * ### Usage:
     *     DevToDev.getActivePlayer().increment("Rounds played", 1);
     *
     *     // to decrement a counter, pass a negative number
     *     DevToDev.getActivePlayer().increment("Rounds played", -1);
     *
     * @param String key - the name of the property
     * @param Number value - an amount to increment the given property
     */
    DevToDev.getActivePlayer().increment(String key, Number value);
    
    /**
     * Increments or decrements multiple numeric user's properties at once.
     *  ### Usage:
     *     final Map<String, Number> data = new HashMap<String, Number>();
     *     data.put("Rounds played", 1);
     *     data.put("Enemies killed", 6);
     *     DevToDev.getActivePlayer().increment(data);
     *
     * @param Map<String, Number> values - an associative array of property names and numeric values.
     */
    DevToDev.getActivePlayer().increment(final Map<String, Number> data);
    /**
     * Increments or decrements numeric user's properties.
     * ### Usage:
     *     DevToDev.SDK.ActiveUser.Increment("Rounds played", 1);
     *
     *     // to decrement a counter, pass a negative number
     *     DevToDev.SDK.ActiveUser.Increment("Rounds played", -1);
     *
     * <param name="key">property name</param>
     * <param name="value">an amount to increment the given property(int or double)</param>
     */
    DevToDev.SDK.ActiveUser.Increment(string key, object value);
    
    /**
     * Increments or decrements multiple numeric user's properties at once.
     *  ### Usage:
     *     Dictionary<string, object> data = new Dictionary<string, object>();
     *     data.Add("Rounds played", 1);
     *     data.Add("Enemies killed", 6);
     *     DevToDev.SDK.ActiveUser.Increment(data);
     *
     * <param name="values">an associative array of property names and numeric(int, double) values</param>
     */
    DevToDev.SDK.ActiveUser.Increment(Dictionary<string, object>  values);
    /*
     * Increments or decrements numeric user's properties.
     * ### Usage:
     *     devtodev.user.increment('Rounds played', 1);
     *     // or if you're just incrementing a counter by 1, you can simply do
     *     devtodev.user.increment('Rounds played');
     *
     *     // to decrement a counter, pass a negative number
     *     devtodev.user.increment('Rounds played', -1);
     *
     *     // you can increment multiple properties at once:
     *     devtodev.user.increment({
     *         'Rounds played': 1,
     *         'Enemies killed': 6
     *     });
     * @param {Object|String} prop If a string, this is the name of the property. If an object, this is an associative array of names and numeric values.
     * @param {Number} [val] An amount to increment the given property
     */
    devtodev.user.increment(prop, val);
    /// <summary> Increments or decrements numeric user's properties.</summary>
    /// <example> Usage:
    /// 
    ///     DevToDev.Analytics.ActiveUser.Increment("Rounds played", 1);
    ///     //to decrement a counter, pass a negative number
    ///     DevToDev.Analytics.ActiveUser.Increment("Rounds played", -1);
    /// 
    /// </example>
    /// <param name="key">property name</param>
    /// <param name="value">an amount to increment the given property(int or double)</param>
    DevToDev.Analytics.ActiveUser.Increment(string key, object value);
    
    /// <summary> Increments or decrements multiple numeric user's properties at once.</summary>
    /// <example> Usage:
    /// 
    ///     Dictionary<string, object> data = new Dictionary<string, object>();
    ///     data.Add("Rounds played", 1);
    ///     data.Add("Enemies killed", 6);
    ///     DevToDev.Analytics.ActiveUser.Increment(data);
    /// 
    /// </example>
    /// <param name="values">an associative array of property names and numeric(int, double) values</param>
    DevToDev.Analytics.ActiveUser.Increment(Dictionary<string, object>  values);
    /**
     * Increments or decrements numeric user's properties.
     * ### Usage:
     *     [DevToDev.activeUser incrementWithKey: @"Rounds played" andValue: 1];
     *
     *     // to decrement a counter, pass a negative number
     *     [DevToDev.activeUser incrementWithKey: @"Rounds played" andValue: -1];
     *
     * @param NSString key - the name of the property
     * @param NSNumber value - an amount to increment the given property
     */
    [DevToDev.activeUser incrementWithKey: (NSString *) key andValue: (id) value];
    
    /**
     * Increments or decrements multiple numeric user's properties at once.
     *  ### Usage:
     *     [DevToDev.activeUser increment: @{
     *         @"Rounds played"  : 1,
     *         @"Enemies killed" : 6
     *     }];
     *
     * @param NSDictionary values - an associative array of property names and numeric values.
     */
    [DevToDev.activeUser increment: (NSDictionary *) values];
    /**
     * Increments or decrements numeric user's properties.
     * ### Usage:
     *     DevToDev.getActiveUser().Increment("Rounds played", 1);
     *
     *     // to decrement a counter, pass a negative number
     *     DevToDev.getActiveUser().Increment("Rounds played", -1);
     *
     * @param key - the name of the property
     * @param value - an amount to increment the given property
     */
    DevToDev.getActiveUser().Increment(key:String, value:Number);
    
    /**
     * Increments or decrements multiple numeric user's properties at once.
     *  ### Usage:
     *     var data:Dictionary = new Dictionary();
     *     data["Rounds played"] = 1;
     *     data["Enemies killed"] 6;
     *     DevToDev.getActivePlayer().IncrementMany(data);
     *
     * @param values - an associative array of property names and numeric values.
     */
    DevToDev.getActiveUser().IncrementMany(data:Dictionary);
    /**
    * Append values to list properties.
    * @param NSString key - property name
    * @param {NSString|NSNumber|NSNull|NSDictionary|NSDate|NSURL} value - appending value
    */
    [DevToDev.activeUser appendWithKey: (NSString *) key andValue: (id) value];
    
    /**
     * Multiple append list-valued properties at once
     * @param NSDictionary values - an associative array of property names and values.
     */
    [DevToDev.activeUser append: (NSDictionary *) values];
    /**
    * Append values to list properties.
    * @param String key - property name
    * @param {String|Number|null|Collection|Map|Date|Url} value - appending value
    */
    DevToDev.getActivePlayer().append(String key, Object value);
    
    /**
     * Multiple append list-valued properties at once
     * @param Map<String, Object> values - an associative array of property names and values.
     */
    DevToDev.getActivePlayer().append(final Map<String, Object> data);
    /**
    * Append values to list properties.
    * <param name="key">property name</param>
    * <param name="value">appending value of type {string|int|double|List}</param>
    */
    DevToDev.SDK.ActiveUser.Append(string key, object value);
    
    /**
     * Multiple append list-valued properties at once
     * <param name="values">an associative array of property names and values</param>
     */
    DevToDev.SDK.ActiveUser.Append(Dictionary<string, object> values);
    /// <summary> Append values to list properties. </summary>
    /// <param name="key"> Property name </param>
    /// <param name="value"> Appending value of type {string|int|double|List} </param>
    DevToDev.Analytics.ActiveUser.AppendUserData(string key, object value);
    
    /// <summary> Multiple append list-valued properties at once </summary>
    /// <param name="values"> An associative array of property names and values </param>
    DevToDev.Analytics.ActiveUser.AppendUserData(Dictionary<string, object> values);
    /**
    * Append values to list properties.
    * @param NSString key - property name
    * @param {NSString|NSNumber|NSNull|NSArray|NSDictionary|NSDate|NSURL} value - appending value
    */
    [DevToDev.activeUser appendWithKey: (NSString *) key andValue: (id) value];
    
    /**
     * Multiple append list-valued properties at once
     * @param NSDictionary values - an associative array of property names and values.
     */
    [DevToDev.activeUser append: (NSDictionary *) values];
    /**
    * Append values to list properties.
    * @param key - property name
    * @param value - appending value (String|Number|Array)
    */
    DevToDev.getActiveUser().AppendUserData(key:String, value:Object);
    
    /**
     * Multiple append list-valued properties at once
     * @param values - an associative array of property names and values.
     */
    DevToDev.getActiveUser().AppendUserDataMany(data:Dictionary);
    /**
    * Union a given list with a list-valued property, excluding duplicate values.
    * @param NSString key - property name
    * @param {NSString|NSNumber|NSNull|NSDictionary|NSDate|NSURL} value - appending value
    */
    [DevToDev.activeUser unionWithKey: (NSString *) key andValue: (id) value];
    
    /**
     * Multiple union of a given lists with a list-valued properties at once
     * @param NSDictionary values - an associative array of property names and values.
     */
    [DevToDev.activeUser unionWithData: (NSDictionary *) values];
    /**
    * Union a given list with a list-valued property, excluding duplicate values.
    * @param String key - property name
    * @param {String|Number|null|Collection|Map|Date|Url} value - appending value
    */
    DevToDev.getActivePlayer().union(String key, Object value);
    
    /**
     * Multiple union of a given lists with a list-valued properties at once
     * @param Map values - an associative array of property names and values.
     */
    /**
    * Union a given list with a list-valued property, excluding duplicate values.
    * <param name="key">property name</param>
    * <param name="value">appending value of type {string|int|double|List}</param>
    */
    DevToDev.SDK.ActiveUser.Union(string key, object value);
    
    /**
     * Multiple union of a given lists with a list-valued properties at once
     * <param name="values">an associative array of property names and values</param>
     */
    DevToDev.SDK.ActiveUser.Union(Dictionary<string, object> values);
    /// <summary> Union a given list with a list-valued property, excluding duplicate values. </summary>
    /// <param name="key"> Property name </param>
    /// <param name="value"> Appending value of type {string|int|double|List} </param>
    DevToDev.Analytics.ActiveUser.AppendUserData(string key, object value);
    
    /// <summary> Multiple union of a given lists with a list-valued properties at once </summary>
    /// <param name="values"> An associative array of property names and values </param>
    DevToDev.Analytics.ActiveUser.AppendUserData(Dictionary<string, object> values);
    /**
    * Union a given list with a list-valued property, excluding duplicate values.
    * @param NSString key - property name
    * @param {NSString|NSNumber|NSNull|NSArray|NSDictionary|NSDate|NSURL} value - appending value
    */
    [DevToDev.activeUser unionWithKey: (NSString *) key andValue: (id) value];
    
    /**
     * Multiple union of a given lists with a list-valued properties at once
     * @param NSDictionary values - an associative array of property names and values.
     */
    [DevToDev.activeUser unionWithData: (NSDictionary *) values];
    /**
    * Union a given list with a list-valued property, excluding duplicate values.
    * @param key - property name
    * @param value - appending value (String|Number|Array)
    */
    DevToDev.getActiveUser().UnionUserData(key:String, value:Object);
    
    /**
     * Multiple union of a given lists with a list-valued properties at once
     * @param values - an associative array of property names and values.
     */
    DevToDev.getActiveUser().UnionUserDataMany(data:Dictionary);
    /**
     * Removes property from user data.
     *
     * ### Usage:
     *     [DevToDev.activeUser unsetUserDataWithKey: @"Hair color"];
     *
     * @param NSString key - the name of the property
     */
    [DevToDev.activeUser unsetUserDataWithKey: (NSString *) key];
    
    /**
     * Removes multiple properties from user data at once.
     *
     * ### Usage:
     *     [DevToDev.activeUser unsetUserData: @[ @"Hair color", @"blonde", @"Last payment" ]];
     *
     * @param NSArray keys - an array of property names.
     */
    [DevToDev.activeUser unsetUserData: (NSArray *) keys];
    /**
     * Removes property from user data.
     *
     * ### Usage:
     *     DevToDev.getActivePlayer().unsetUserData("Hair color");
     *
     * @param String key - the name of the property
     */
    DevToDev.getActivePlayer().unsetUserData(String key);
    
    /**
     * Removes multiple properties from user data at once.
     *
     * ### Usage:
     *     final List<String> unsetData = new ArrayList<String>();
     *     unsetData.add("Hair color");
     *     unsetData.add("Last payment");
     *     DevToDev.getActivePlayer().unsetUserData(unsetData);
     *
     * @param List keys - an array of property names.
     */
    DevToDev.getActivePlayer().unsetUserData(final List<String> keys);
    /**
     * Removes property from user data.
     *
     * ### Usage:
     *     DevToDev.SDK.ActiveUser.UnsetUserData("Hair color");
     *
     * <param name="key">the name of the property</param>
     */
    DevToDev.SDK.ActiveUser.UnsetUserData(string key);
    
    /**
     * Removes multiple properties from user data at once.
     *
     * ### Usage:
     *     List<string> unsetData = new List<string>();
     *     unsetData.add("Hair color");
     *     unsetData.add("Last payment");
     *     DevToDev.SDK.ActiveUser.UnsetUserData(unsetData);
     *
     * <param name="keys">an array of property names</param>
     */
    DevToDev.SDK.ActiveUser.UnsetUserData(List<string> keys);
    /*
     * Removes properties from a user data.
     *
     * ### Usage:
     *     devtodev.user.remove('Hair color');
     *
     *     // to set multiple properties at once
     *     devtodev.user.remove(['Hair color', 'blonde', 'Last payment']);
     *
     * @param {Array|String} prop If a string, this is the name of the property. If an array, this is an array of names.
     */
    devtodev.user.remove(prop);
    /// <summary> Removes property from user data. </summary>
    /// <example> Usage:
    /// 
    ///     DevToDev.Analytics.ActiveUser.UnsetUserData("Hair color");
    /// 
    /// </example>
    /// <param name="key"> The name of the property </param>
    DevToDev.Analytics.ActiveUser.UnsetUserData(string key);
    
    /// <summary> Removes multiple properties from user data at once. </summary>
    /// <example> Usage:
    /// 
    ///     List<string> unsetData = new List<string>();
    ///     unsetData.add("Hair color");
    ///     unsetData.add("Last payment");
    ///     DevToDev.Analytics.ActiveUser.UnsetUserData(unsetData);
    /// 
    /// </example>
    /// <param name="keys"> An array of property names </param>
    DevToDev.Analytics.ActiveUser.UnsetUserData(List<string> keys);
    /**
     * Removes property from user data.
     *
     * ### Usage:
     *     [DevToDev.activeUser unsetUserDataWithKey: @"Hair color"];
     *
     * @param NSString key - the name of the property
     */
    [DevToDev.activeUser unsetUserDataWithKey: (NSString *) key];
    
    /**
     * Removes multiple properties from user data at once.
     *
     * ### Usage:
     *     [DevToDev.activeUser unsetUserData: @[ @"Hair color", @"blonde", @"Last payment" ]];
     *
     * @param NSArray keys - an array of property names.
     */
    [DevToDev.activeUser unsetUserData: (NSArray *) keys];
    /**
     * Removes property from user data.
     * ### Usage:
     *     DevToDev.getActiveUser().UnsetUserData("Hair color");
     *
     * @param key - the name of the property
     */
    DevToDev.getActiveUser().UnsetUserData(key:String);
    
    /**
     * Removes multiple properties from user data at once.
     * ### Usage:
     *     var unsetData:Array = new Array();
     *     unsetData.push("Hair color");
     *     unsetData.push("Last payment");
     *     DevToDev.getActiveUser().UnsetUserDataMany(unsetData);
     *
     * @param keys - an array of property names.
     */
    DevToDev.getActiveUser().UnsetUserDataMany(keys:Array);
    /**
     * Removes all user's custom personal data from devtodev data base.
     */
    [DevToDev.activeUser clearUserData];
    /**
     * Removes all user's custom personal data from devtodev data base.
     */
    DevToDev.getActivePlayer().clearUserData();
    /**
     * Removes all user's custom personal data from devtodev data base.
     */
    DevToDev.SDK.ActiveUser.ClearUserData();
    /*
     * Removes all user's custom personal data from devtodev data base.
     */
    
    devtodev.user.clearUser();
    /// <summary> Removes all user's custom personal data from devtodev data base.</summary>
    DevToDev.Analytics.ActiveUser.ClearUserData();
    /**
     * Removes all user's custom personal data from devtodev data base.
     */
    [DevToDev.activeUser clearUserData];
    /**
     * Removes all user's custom personal data from devtodev data base.
     */
    DevToDev.getActiveUser().ClearUserData();
    UPeopleLibrary::ClearUserData();
    /**
    * Replaces current cross-platform user id
    * Attention! Don't use this method if you're going to perform the user's relogin.
    * @param String prevUserId - previous cross-platform user ID
    * @param String userId - new cross-platform user ID
    */
    DevToDev.replaceUserId(String prevUserId, String userId);
    /**
    * Initializes the current user level. Required if level feature used in the app.
    * @param int level - current game level of the player.
    */
    DevToDev.setCurrentLevel(int level);
    UPeopleLibrary::Cheater(bool cheater);
    UPeopleLibrary::Name(const FString& name);
    UPeopleLibrary::Age(int32 age);
    UPeopleLibrary::Gender(const FString& InGender);
    UPeopleLibrary::Email(const FString& email);
    UPeopleLibrary::Phone(const FString& phone);
    UPeopleLibrary::Photo(const FString& photo);
    UPeopleLibrary::SetUserData(const TArray<FAnalyticsEventAttr>& Attributes);
    UPeopleLibrary::IncrementUserData(const TArray<FAnalyticsEventAttr>& Attributes);
    UPeopleLibrary::UnsetUserData(const TArray<FString>& Attributes);

    placement

    String?

    from 1 to 100 symbols, optional

    Banner placement

    unit

    String?

    from 1 to 100 symbols, optional

    Banner name

    Parameter
    Type
    Restrictions
    Description

    network

    NSString

    from 1 to 100 symbols

    Name of the ad network responsible for the impression

    revenue

    double

    from 0,0 to Double.max

    Reward for banner display in USD

    Parameter
    Type
    Restrictions
    Description

    network

    String

    from 1 to 100 symbols

    Name of the ad network responsible for the impression

    revenue

    Double

    from 0,0 to Double.max

    Reward for banner display in USD

    Parameter
    Type
    Restrictions
    Description

    network

    String

    from 1 to 100 symbols

    Name of the ad network responsible for the impression

    revenue

    Double

    from 0,0 to Double.max

    Reward for banner display in USD

    Parameter
    Type
    Restrictions
    Description

    network

    String

    from 1 to 100 symbols

    Name of the ad network responsible for the impression

    revenue

    Double

    from 0,0 to Double.max

    Reward for banner display in USD

    Parameter
    Type
    Restrictions
    Description

    network

    String

    from 1 to 100 symbols

    Name of the ad network responsible for the impression

    revenue

    Double

    from 0,0 to Double.max

    Reward for banner display in USD

    Blueprint

    Parameter

    Type

    Restrictions

    Description

    socialNetwork

    FString

    from 1 to 100 symbols

    The name of the ad network that delivered the impression.

    revenue

    float

    Parameter
    Type
    Restrictions
    Description

    network

    String

    from 1 to 100 symbols

    Name of the ad network responsible for the impression

    revenue

    Float

    from 0,0 to Double.max

    Reward for banner display in USD

    Use the following constants to specify a social network:

    DTDSocialNetwork.facebook, DTDSocialNetwork.vkontakte , DTDSocialNetwork.twitter, DTDSocialNetwork.googleplus, DTDSocialNetwork.whatsapp, DTDSocialNetwork.viber, DTDSocialNetwork.evernote, DTDSocialNetwork.googlemail, DTDSocialNetwork.linkedin, DTDSocialNetwork.pinterest, DTDSocialNetwork.qzone, DTDSocialNetwork.reddit, DTDSocialNetwork.renren, DTDSocialNetwork.tumblr

    Or create an object with the desired social media name.

    DTDAnalytics.socialNetworkPost(
        socialNetwork = DTDSocialNetwork.facebook
    )

    Use the following constants to specify a social network:

    DTDSocialNetwork.Companion.getFacebook(), DTDSocialNetwork.Companion.getVkontakte(), DTDSocialNetwork.Companion.getTwitter(), DTDSocialNetwork.Companion.getGoogleplus(), DTDSocialNetwork.Companion.Whatsapp(), DTDSocialNetwork.Companion.getViber(), DTDSocialNetwork.Companion.getEvernote(), DTDSocialNetwork.Companion.getGooglemail(), DTDSocialNetwork.Companion.getLinkedin(), DTDSocialNetwork.Companion.getPinterest(), DTDSocialNetwork.Companion.getQzone(), DTDSocialNetwork.Companion.getReddit(), DTDSocialNetwork.Companion.getRenren(), DTDSocialNetwork.Companion.getTumblr()

    Or create an object with the desired social media name.

    DTDAnalytics.INSTANCE.socialNetworkConnect(DTDSocialNetwork.Companion.getFacebook());

    Use the following constants to specify a social network:

    DTDSocialNetwork.facebook, DTDSocialNetwork.vkontakte , DTDSocialNetwork.twitter, DTDSocialNetwork.googleplus, DTDSocialNetwork.whatsapp, DTDSocialNetwork.viber, DTDSocialNetwork.evernote, DTDSocialNetwork.googlemail, DTDSocialNetwork.linkedin, DTDSocialNetwork.pinterest, DTDSocialNetwork.qzone, DTDSocialNetwork.reddit, DTDSocialNetwork.renren, DTDSocialNetwork.tumblr

    Or create an object with the desired social media name:

    Use the following constants to specify a social network:

    DTDSocialNetwork.facebook, DTDSocialNetwork.vkontakte , DTDSocialNetwork.twitter, DTDSocialNetwork.googleplus, DTDSocialNetwork.whatsapp, DTDSocialNetwork.viber, DTDSocialNetwork.evernote, DTDSocialNetwork.googlemail, DTDSocialNetwork.linkedin, DTDSocialNetwork.pinterest, DTDSocialNetwork.qzone, DTDSocialNetwork.reddit, DTDSocialNetwork.renren, DTDSocialNetwork.tumblr

    Or create an object with the desired social media name:

    Blueprint

    Argument

    Type

    Description

    socialNetwork

    EDTDSocialNetwork

    Predefined social network.

    Or use special method for custom social network:

    Blueprint

    Argument

    Use the following constants to specify a social network:

    .facebook, .vkontakte , .twitter, .googleplus, .whatsapp, .viber, .evernote, .googlemail, .linkedin, .pinterest, .qzone, .reddit, .renren, .tumblr

    Or create an object with the desired social media name.

    Description

    socialNetwork

    EDTDSocialNetwork

    Predefined social network.

    Blueprint

    Argument

    Type

    Description

    socialNetwork

    FString

    Custom social network.

    Description

    utmData

    TMap<EDTDReferralProperty, FString>

    UTM data.

    onResult

    • FAnalyticsDynamicGetterStringDelegate

    • FDTDGetterStringDelegate

    Callback.

    Description

    onResult

    • FAnalyticsDynamicGetterStringDelegate

    • FDTDGetterStringDelegate

    Callback.

    Description

    onResult

    • FAnalyticsDynamicGetterStringDelegate

    • FDTDGetterStringDelegate

    Callback.

    Description

    onResult

    • FAnalyticsDynamicGetterBoolDelegate

    • FDTDGetterBoolDelegate

    Callback.

    (void)didReceiveDevtodevIdWith:(NSInteger)devtodevId;

    The didReceiveDevtodevId method will be called with every ID change on the server side.

    To obtain the devtodev ID, you need to pass the DTDIdentifiersListener listener to DTDAnalytics:

    The didReceiveDevtodevId method will be called with every ID change on the server side.

    To obtain the devtodev ID, you need to pass the DTDIdentifiersListener listener to DTDAnalytics:

    The didReceiveDevtodevId method will be called with every ID change on the server side.

    To obtain the devtodev ID, you need to pass the DTDIdentifiersListener listener delegate to DTDAnalytics:

    The delegate method will be called with every ID change on the server side.

    To obtain the devtodev ID, you need to pass the DTDIdentifiersListener listener delegate to DTDAnalytics:

    The delegate method will be called with every ID change on the server side.

    Blueprint

    Argument

    Type

    Description

    listener

    • FAnalyticsDynamicGetterLongDelegate

    • FDTDGetterLongDelegate

    devtodev ID Listener.

    To obtain the devtodev ID, you need to pass the Callable to DTDAnalytics:

    The didReceiveDevtodevId method will be called with every ID change on the server side.

    network

    String

    from 1 to 100 symbols

    Name of the ad network responsible for the impression

    revenue

    Double

    from 0,0 to Double.max

    Reward for banner display in USD

    Argument

    Argument

    Argument

    Type

    Argument

    Argument

    Argument

    Blueprint
    Blueprint
    Blueprint
    Blueprint
    Blueprint
    Blueprint
    Blueprint
    Blueprint
    let network = DTDSocialNetwork(name: "NetworkName")
    DTDAnalytics.socialNetworkConnect(socialNetwork: network)
    DTDSocialNetwork *network = [[DTDSocialNetwork alloc] initWithName:@"NetworkName"];
    [DTDAnalytics socialNetworkConnect:network];

    Type

    Type

    Description

    Type

    Type

    Type

    DTDAnalytics.adImpression(network: "Network name",
                              revenue: 0.15,
                              placement: "Placement of the banner",
                              unit: "Banner title")

    Field

    Type

    Description

    User Id

    FString

    Unique cross-platform user id

    Description

    From

    FString

    Current user Id

    To

    FString

    New user Id

    Field

    Type

    Description

    Level

    int32

    Current user level

    var network = new DTDSocialNetwork(name: "NetworkName");
    DTDAnalytics.SocialNetworkConnect(socialNetwork: network);
    var network = new DTDSocialNetwork(name: "NetworkName");
    DTDAnalytics.SocialNetworkConnect(socialNetwork: network);
    window.devtodev.socialNetworkConnect('NetworkName')
    UDTDAnalyticsBPLibrary::SocialNetworkConnect(EDTDSocialNetwork::Linkedin);
    UDTDAnalyticsBPLibrary::SocialNetworkConnectCustom("SocialNetworkName");
    DTDAnalytics.SocialNetworkConnect(GDDTDSocialNetwork.Facebook())
    let network = GDDTDSocialNetwork(name: "NetworkName")
    DTDAnalytics.SocialNetworkConnect(network)
    DTDAnalytics.SocialNetworkPost(GDDTDSocialNetwork.Facebook(), "New level reached")
    var reffer = GDDTDReferralProperty.new()
    reffer.AddCampaign("Warm Snow Boots")
    reffer.AddContent("Snow Boots")
    reffer.AddMedium("CPI")
    reffer.AddSource("AdWords")
    reffer.AddTerm("shoes+boots")
    
    DTDAnalytics.Referrer(reffer)
    DTDAnalytics.GetDeviceId(getDeviceHandler)
    
    func getDeviceHandler(deviceId: String):
        print(deviceId)
    DTDAnalytics.GetSdkVersion(getSdkVersionHandler)
    
    func getSdkVersionHandler(sdkVersion: String):
        print(sdkVersion)
    DTDAnalytics.GetTrackingAvailability(getTrackingAvailabilityHandler)
    
    func getTrackingAvailabilityHandler(trackingAvailability: bool):
        print(str(trackingAvailability))
    DTDAnalytics.setIdentifiersListener(object : DTDIdentifiersListener {
                override fun didReceiveDevtodevId(devtodevId: Long) {
                   /// your code
                }
            })
    DTDAnalytics.INSTANCE.setIdentifiersListener(new DTDIdentifiersListener() {
                @Override
                public void didReceiveDevtodevId(long devtodevId) {
                    // your code
                }
    });
    DTDAnalytics.SetIdentifiersListener(devtodevId =>
    {
        // Your code...
    });
    DTDAnalytics.SetIdentifiersListener(devtodevId =>
    {
        // Your code...
    });
    window.devtodev.setIdentifiersListener((devtodevId) =>
    {
    // Your code...
    })
    auto listener = new FDTDGetterLongDelegate();
    listener->BindLambda([](int64 value)
    {
    	// Your code...
    });
    UDTDAnalyticsBPLibrary::SetIdentifiersListener(*listener);
    DTDAnalytics.SetIdentifiersCallback(identifiersUpdated)
    
    func identifiersUpdated(devtodevID: int):
        print("SetIdentifiersCallback DevtodevID is " + str(devtodevID))
    DTDAnalytics.socialNetworkPost(socialNetwork: .facebook, 
                                          reason: "New level reached")
    [DTDAnalytics socialNetworkPost:DTDSocialNetwork.facebook withReason:@"New level reached"];
    DTDAnalytics.socialNetworkPost(
        socialNetwork = DTDSocialNetwork.facebook,
        reason = "New level reached"
    )
     DTDAnalytics.INSTANCE.socialNetworkPost(
            DTDSocialNetwork.Companion.getFacebook(),
            "New level reached"
    )
    DTDAnalytics.SocialNetworkPost(
        socialNetwork: DTDSocialNetwork.facebook,
        reason: "New level reached");
    DTDAnalytics.SocialNetworkPost(
        socialNetwork: DTDSocialNetwork.Facebook,
        reason: "New level reached");
    window.devtodev.socialNetworkPost("NetworkName", "New level reached")
    let referrerData = [DTDReferralProperty.source: "AdWords",
                        DTDReferralProperty.medium: "CPI",
                        DTDReferralProperty.content: "Snow Boots",
                        DTDReferralProperty.campaign: "Warm Snow Boots",
                        DTDReferralProperty.term: "shoes+boots"]
                        
    DTDAnalytics.referrer(utmData: referrerData)
    NSDictionary <DTDReferralProperty *, NSString *> * referrerData = @{
      DTDReferralProperty.source: @"AdWords",
      DTDReferralProperty.medium: @"CPI",
      DTDReferralProperty.content: @"Snow Boots",
      DTDReferralProperty.campaign: @"Warm Snow Boots",
      DTDReferralProperty.term: @"shoes+boots",
    };
    [DTDAnalytics referrer:referrerData];
    enum class DTDReferralProperty {
        Source,
        Campaign,
        Content,
        Medium,
        Term;
    }
    
    val referrer = mapOf(
            DTDReferralProperty.Medium to "some value",
            DTDReferralProperty.Campaign to "some value"
    )    
    DTDAnalytics.referrer(utmData = referrer)
    public final enum class DTDReferralProperty {
        Source,
        Campaign,
        Content,
        Medium,
        Term;
    }
    
    Map<DTDReferralProperty, String> propertyStringHashMap = new HashMap<>();
    propertyStringHashMap.put(DTDReferralProperty.Medium, "some value");
    propertyStringHashMap.put(DTDReferralProperty.Campaign, "some value");
    DTDAnalytics.INSTANCE.referrer(propertyStringHashMap);
    var referrer = new Dictionary<DTDReferralProperty, string>
    {
        [DTDReferralProperty.Medium] = "some value",
        [DTDReferralProperty.Campaign] = "some value"
    };
    DTDAnalytics.Referrer(referrer: referrer);
    var referrer = new Dictionary<DTDReferralProperty, string>
    {
        [DTDReferralProperty.Medium] = "some value",
        [DTDReferralProperty.Campaign] = "some value"
    };
    DTDAnalytics.Referrer(referrer: referrer);
    var referrer = {
        source: "some source",
        term: "some term",
        medium: "some medium",
        source: "some source",
        content: "some content",
        campaign: "some campaign",
    };
    window.devtodev.referrer(referrer)
    DTDAnalytics.sendBufferedEvents()
    [DTDAnalytics sendBufferedEvents];
    DTDAnalytics.sendBufferedEvents()
    DTDAnalytics.INSTANCE.sendBufferedEvents();
    DTDAnalytics.SendBufferedEvents();
    DTDAnalytics.SendBufferedEvents();
    window.devtodev.sendBufferedEvents()
    UDTDAnalyticsBPLibrary::SendBufferedEvents();
    DTDAnalytics.SendBufferedEvents()
    DispatchQueue.main.async{}
    dispatch_async(dispatch_get_main_queue(), ^{ });
    Handler(Looper.getMainLooper()).post{}
    ContextCompat.getMainExecutor(context).execute(() -> {
        // This is where your UI code goes.
    });
    var result = await DTDAnalytics.GetUserId();
    DTDAnalytics.GetUserId( id => {
      //your code
    });
    DTDAnalytics.setTrackingAvailability(value: true)
    [DTDAnalytics trackingAvailability:true];
    DTDAnalytics.setTrackingAvailability(value = true)
    DTDAnalytics.INSTANCE.setTrackingAvailability(true);
    DTDAnalytics.SetTrackingAvailability(trackingValue: true);
    DTDAnalytics.SetTrackingAvailability(trackingValue: true);
    window.devtodev.setTrackingAvailability(true)
    UDTDAnalyticsBPLibrary::SetTrackingAvailability(true);
    DTDAnalytics.SetTrackingAvailability(true)
    DTDAnalytics.getDeviceId { deviceId in
      // your code
    }
    [DTDAnalytics deviceIdHandler:^(NSString * _Nonnull deviceId) {
      // your code
    }];
    DTDAnalytics.getDeviceId { deviceId ->
      // your code
    }
    DTDAnalytics.INSTANCE.getDeviceId(deviceId ->
            // your code
            null
    );
    var devideId = await DTDAnalytics.GetDeviceId();
    DTDAnalytics.GetDeviceId( id => {
      //your code
    });
    var devideId = window.devtodev.getDeviceId()
    DTDAnalytics.getSDKVersion { sdkVersion in
      // your code
    }
    [DTDAnalytics sdkVersionHandler:^(NSString * _Nonnull sdkVersion) {
      // your code
    }];
    DTDAnalytics.getSDKVersion { sdkVersion ->
      // your code
    }
    DTDAnalytics.INSTANCE.getSdkVersion ( sdkVersion ->
             // your code
             null
    );
    var sdkVersion = DTDAnalytics.GetSdkVersion();
    DTDAnalytics.GetSdkVersion( version => {
      //your code
    });
    var sdkVersion = window.devtodev.getSDKVersion()
    DTDAnalytics.getTrackingAvailability { trackingAvailability in
      // your code
    }
    [DTDAnalytics trackingAvailabilityHandler:^(BOOL trackingAvailability) {
      // your code
    }];
    
    DTDAnalytics.getTrackingAvailability { trackingAvailability ->
      // your code
    }
    DTDAnalytics.INSTANCE.getTrackingAvailability( trackingAvailability ->
             // your code
             null
    );
    var trackingAvailability = await DTDAnalytics.GetTrackingAvailability();
    DTDAnalytics.GetTrackingAvailability( tracking => {
      //your code
    });
    var trackingAvailability = window.devtodev.getTrackingAvailability()
    DTDAnalytics.setIdentifiersListener(listener: self)
    func didReceiveDevtodevId(with devtodevId: Int) {
      /// your code
    }
    @interface Controller ()  <DTDIdentifiersListener>
    [DTDAnalytics setIdentifiersListenerWithListener:self];
    DTDAnalytics.setInitializationCompleteCallback {
      print("Initialized has been finished.")
    }
    let config = DTDAnalyticsConfiguration()
    config.logLevel = .error
    DTDAnalytics.initialize(applicationKey: "App ID", configuration: config)
    [DTDAnalytics setInitializationCompleteCallback:^{
      NSLog(@"%@", @"Initialized has been finished.");
    }];
    DTDAnalyticsConfiguration *config = [[DTDAnalyticsConfiguration alloc] init];
    config.logLevel = DTDLogLevelError;
    [DTDAnalytics applicationKey:@"App ID" configuration:config];
    DTDAnalytics.setInitializationCompleteCallback {
        Log.d("TAG", "Initialized has been finished.")
    }
    val config = DTDAnalyticsConfiguration()
    config.logLevel = DTDLogLevel.Error
    DTDAnalytics.initialize(appKey = "App ID", analyticsConfiguration = config, context = this)
    DTDAnalytics.INSTANCE.setInitializationCompleteCallback(() -> {
        Log.d("TAG", "Initialized has been finished.");
        return null;
    });
    DTDAnalyticsConfiguration config = new DTDAnalyticsConfiguration();
    config.setLogLevel(DTDLogLevel.Error);
    DTDAnalytics.INSTANCE.initialize("App ID", config, this);
    DTDAnalytics.LogLevel = DTDLogLevel.Error;
    DTDAnalytics.SetInitializationCompleteCallback(()=>Console.WriteLine($"Initialization has been finished"));
    DTDAnalytics.Initialize("APP_KEY");
    DTDAnalytics.SetInitializationCompleteCallback(() =>
    {
        Debug.Log("Initialized has been finished.");
    });
    
    DTDAnalytics.SetLogLevel(DTDLogLevel.Error);
    DTDAnalytics.Initialize("APP_KEY");
    window.devtodev.setInitializationCompleteCallback(() =>
    {
    // Your code...
    })
    func initCompleteCallback():
      print("Initialized has been finished.")
    
    func _ready():
      DTDAnalytics.SetInitializationCompleteCallback(initCompleteCallback)
      var config = GDDTDAnalyticsConfiguration.new()
      config.logLevel = GDDTDLogLevel.Error
      DTDAnalytics.InitializeWithConfig(appKey, config)
    UDTDAnalyticsBPLibrary::SocialNetworkPost(EDTDSocialNetwork::Linkedin, "PostReason");
    UDTDAnalyticsBPLibrary::SocialNetworkPostCustom("SocialNetworkName", "PostReason");
    TMap<EDTDReferralProperty, FString> referrer;
    referrer.Add(EDTDReferralProperty::Source, "Source");
    referrer.Add(EDTDReferralProperty::Medium, "Medium ");
    referrer.Add(EDTDReferralProperty::Content, "Content ");
    referrer.Add(EDTDReferralProperty::Campaign, "Campaign ");
    referrer.Add(EDTDReferralProperty::Term, "Term ");
    UDTDAnalyticsBPLibrary::Referrer(referrer);
    auto onResult = new FDTDGetterStringDelegate();
    onResult->BindLambda([](const FString& value)
    {
    	// Your code...
    });
    UDTDAnalyticsBPLibrary::GetUserId(*onResult);
    auto onResult = new FDTDGetterStringDelegate();
    onResult->BindLambda([](const FString& value)
    {
    	// Your code...
    });
    UDTDAnalyticsBPLibrary::GetDeviceId(*onResult);
    // Some codecauto onResult = new FDTDGetterStringDelegate();
    onResult->BindLambda([](const FString& value)
    {
    	// Your code...
    });
    UDTDAnalyticsBPLibrary::GetSdkVersion(*onResult);
    auto onResult = new FDTDGetterBoolDelegate();
    onResult->BindLambda([](bool value)
    {
    	// Your code...
    });
    UDTDAnalyticsBPLibrary::GetTrackingAvailability(*onResult);
    - (void)didReceiveDevtodevIdWith:(NSInteger)devtodevId {
      // your code
    }
    // Initializes the user with the specified cross-platform identifier
    // FString activeUserId - unique cross-platform user id
    
    FAnalytics::Get().GetDefaultConfiguredProvider()->SetUserID(FString activeUserId);
    // Returns current cross-platform user id
    
    FAnalytics::Get().GetDefaultConfiguredProvider()->GetUserID();
    // Replaces cross-platform user id
    // FString from - current user id
    // FString to - new user id
    
    UDevToDevBlueprintFunctionLibrary::ReplaceUserId(const FString& from, const FString& to);
    // Initializes the current user level
    // int32 level - current user level
    
    UDevToDevBlueprintFunctionLibrary::SetCurrentLevel(int32 level)

    placement

    NSString _Nullable

    from 1 to 100 symbols, optional

    Banner placement

    unit

    NSString _Nullable

    from 1 to 100 symbols, optional

    Banner name

    placement

    String?

    from 1 to 100 symbols, optional

    Banner placement

    unit

    String?

    from 1 to 100 symbols, optional

    Banner name

    placement

    String?

    from 1 to 100 symbols, optional

    Banner placement

    unit

    String?

    from 1 to 100 symbols, optional

    Banner name

    placement

    String?

    from 1 to 100 symbols, optional

    Banner placement

    unit

    String?

    from 1 to 100 symbols, optional

    Banner name

    placement

    String?

    from 1 to 100 symbols, optional

    Banner placement

    unit

    String?

    from 1 to 100 symbols, optional

    Banner name

    form 0.0 to float.MaxValue

    Reward for displaying a banner in USD.

    placement

    FString

    from 1 to 100 symbols

    Placement of the banner.

    unit

    FString

    from 1 to 100 symbols

    Banner title.

    placement

    String

    from 1 to 100 symbols, optional

    Banner placement

    unit

    String

    from 1 to 100 symbols, optional

    Banner name

    Type

    Description

    socialNetwork

    FString

    Custom social network.

    [DTDAnalytics adImpressionWithNetwork:@"Network name"
                                  revenue:0.15f
                                placement:@"Placement of the banner"
                                    unit:@"Banner title"];
    DTDAnalytics.adImpression(
        network = "Network name",
        revenue = 0.45,
        placement = "Placement of the banner",
        unit = "Banner title"
    )
    DTDAnalytics.INSTANCE.adImpression(
            "Network name",
            0.45,
            "Placement of the banner",
            "Banner title"
    );
    var network = "Network name";
    var revenue = 0.15;
    var placement = "Placement of the banner";
    var unit = "Banner title";
    DTDAnalytics.AdImpression(network, revenue, placement, unit);
    var network = "Network name";
    var revenue = 0.15;
    var placement = "Placement of the banner";
    var unit = "Banner title";
    DTDAnalytics.AdImpression(network, revenue, placement, unit);
    UDTDAnalyticsBPLibrary::AdImpression("NetworkName", 0.36, "BannerPlacement", "BannerTitle");
    DTDAnalytics.AdImpression("Network name", 0.15, "Placement of the banner", "Banner title")

    Basic methods

    This generation of SDK is deprecated and is no longer supported. Information about the current version can be found here.

    Expert tips before integrating the events.

    Onboarding (tutorial)

    The Tutorial steps event allows you to evaluate the effectiveness of the tutorial steps system. The event should be sent at the end of each tutorial step indicating the number of every passed step as a parameter.

    Use the following constants to specify basic events of tutorial steps:

    • Start or -1 - at the beginning, before the first step is completed;

    • Finish or -2 - instead of the final step number;

    • Skipped or 0 - in case а user skipped the tutorial.

    In other cases use step numbers. Make sure you use numbers above 0 to enumerate the steps.

    The logic of the use of the Skipped constant in the Tutorial steps event is provided only in case a user has completely refused to pass the tutorial. After Skipped is used, no other values of the Tutorial steps event must be received.

    Blueprint

    Leveling up

    This event is for games only.

    You can analyze the distribution of the players over the levels. The event should be sent right after the player reached the next level. You can find more information on what is the right moment to use LevelUp event .

    Blueprint

    To track the average account balance of in-game currency by the end of each level, please provide the list of currency names and amounts.

    Blueprint

    To track the average amount of in-game currency earned during a level, it is necessary to send a special event after each time an in-game account is replenished.

    Blueprint

    AccrualType can take one of the following values:

    Real-World Currency Payment

    To track payments, add this event right after the platform confirms that a payment went through.

    A unique order identifier is a value of a transactionIdentifier property in SKPaymentTransaction object inside the receipt of completed transaction.

    devtodev server does not process transactions with previously used transaction IDs. Also, the server validates identifiers in appearance to avoid evident cheat transactions. To avoid adding cheat payments into reports completely, use devtodev anti-cheat service before creating a realPayment event.

    How to find the transaction ID in GooglePlay transaction?

    Find the INAPP_PURCHASE_DATA object In the JSON fields that are returned in the response data for a purchase order. A unique transaction identifier is the value of orderId property in INAPP_PURCHASE_DATA object. If the order is a test purchase made via the In-app Billing Sandbox,

    Virtual Currency Payment

    This event is for games only.

    To track expenditures of in-game currency and popularity of products, add this event right after the purchase.

    In case a product is bought for several game currencies at once, it is necessary to make a dictionary that includes the names and amounts of the paid currencies.

    In case a product was bought for several game currencies at once, it is necessary to make a hashmap that includes the names and amounts of the paid currencies.

    …and so on…

    Example:

    In case a product was bought for several game currencies at once, it is necessary to make a hashmap including the names and amounts of the paid currencies.

    Example:

    In case a product was bought for several game currencies at once, it is necessary to make a dictionary including the names and amounts of the paid currencies.

    Please keep in mind that there is a limit for the number of unique values of the "purchaseCurrency" parameter - 30 currencies per project. Currencies cannot be deleted or renamed.

    Custom Events

    If you want to count the events that are not among basic, use custom events.

    Attention! We strongly recommend that you do not use custom event properties to transfer and store data that fits the definition of !

    The event must have a unique name and can include up to 20 parameters. The maximum length of the event name is 72 symbols.

    Every parameter inside one event must have a unique name. The maximum length of the parameter name is 32 symbols.

    The values of parameters can be string or number type (int, long, float, double). The maximum length of the parameter value is 255 symbols.

    No more than 300 variants of custom event names can be used for one project. Try to enlarge events in meaning by using event parameters. Events that didn't get into the limit of unique event names will be discarded.

    For a string parameter, it is acceptable to use not more than 50,000 unique values for the whole event history. In case the limit of unique values is exceeded, the parameter is ignored.

    Therefore, we recommend not to set user IDs and Unix time as parameter values of custom events. Try to integrate parameter values if they have a very large variability. Otherwise, it will be very difficult to analyze the data or after some time it may be even ignored.

    We strongly recommend not to change the type of data transferred in the parameter over time. In case you change the data type in parameter, it will be duplicated with the same name and different data types in devtodev database which will result in more complicated report building.

    20 parameter names may be associated with any event:

    Then use method:

    20 parameter names may be associated with any event:

    Then use method:

    Example:

    20 parameter names may be associated with any event:

    Example:

    20 parameter names may be associated with any event:

    Then use method:

    Example:

    Progression event

    This event is for games only.

    First of all, a Progression event is used for games with short (within one game session) locations/game levels. The event allows you to gather data on passing the locations and get statistics on parameters that vary during the location passing.

    Developer must use the following two methods:

    1. Method startProgressionEvent when entering the location:

    2. Method endProgressionEvent when exiting (no matter if completed or not) the location:

      LocationEventParams class methods:

    The user can be only in one location at the same time. When moving to another location (including embedded), the previous location must be completed. Information on locations, the passing of which was not completed by calling endProgressionEvent method during the game session (the call of endProgressionEvent method is not integrated; user unloaded the application from the device memory; there was an application crash), do not fall in the statistics.

    DevToDev.TutorialState.Start or -1 - at the beginning, before the first step is completed;

  • DevToDev.TutorialState.Finish or -2 - instead of the last step number;

  • DevToDev.TutorialState.Skipped or 0 - if a user skipped the tutorial.

    • DevToDev.TutorialState.Start or -1 - at the beginning, before the first step is completed;

    • DevToDev.TutorialState.Finish or -2 - instead of the last step number;

    • DevToDev.TutorialState.Skipped or 0 - if a user skipped the tutorial.

    • -1 - Start the tutorial (at the beginning, before the first step is completed)

    • -2 - Tutorial finished (instead of the last step number)

    • 0 - Tutorial skipped (if a user skipped the tutorial).

    • DevToDev.TutorialState.Start or -1 - at the beginning, before the first step is completed;

    • DevToDev.TutorialState.Finish or -2 - instead of the last step number;

    • DevToDev.TutorialState.Skipped or 0 - if a user skipped the tutorial.

    • Start or -1 - at the beginning, before the first step is completed;

    • Finish or -2 - instead of the final step number;

    • Skipped or 0 - in case a user skipped the tutorial.

    • TutorialState.START or -1 - at the beginning, before the first step is completed;

    • TutorialState.FINISH or -2 - instead of the last step number;

    • TutorialState.SKIPPED or 0 - if a user skipped the tutorial.

    • -1 (Start) - at the beginning, before the first step is completed;

    • -2 (Finish) - instead of the number of the last step;

    • 0 (Skipped) - in case a user skipped the tutorial.

    Step

    int32

    The latest successfully completed tutorial step

    Code

    Field

    Type

    Description

    Level

    int32

    level reached by the player

    Code

    Level

    int32

    level reached by the player

    Field

    Type

    Description

    Game Currency Type

    FString

    Currency name (max. 24 symbols)

    Game Currency Amount

    int32

    The amount an account has been credited with.

    accrualType

    Enum

    Can take one of following values: "Earned" or "Purchased"

    Code

    orderId
    property will be empty.

    devtodev server does not process transactions with previously used transaction IDs. Also, the server validates the identifiers in appearance to avoid evident cheat transactions. To avoid completely the entering of cheat payments from GooglePlay in reports, use devtodev anticheat service before creating realPayment event.

    Example:

    Example:

    How to find the transaction ID in iTunes transaction?

    Unique order identifier is a value of "transactionIdentifier" property in SKPaymentTransaction object inside the receipt of completed transaction.

    How to find the transaction ID in GooglePlay transaction?

    Find the INAPP_PURCHASE_DATA object In the JSON fields that are returned in the response data for a purchase order. A unique transaction identifier is the value of orderId property in INAPP_PURCHASE_DATA object. If the order is a test purchase made via the In-app Billing Sandbox, orderId property will be empty.

    devtodev server does not process transactions with previously used transaction IDs. Also the server validates the identifiers in appearance, to avoid evident cheat transactions. To avoid the entering of cheat payments in reports completely, use devtodev anticheat service before creating realPayment event.

    Blueprint

    Field

    type

    Description

    Transaction Id

    FString

    Unique transaction ID

    In AppPrice

    float

    Product price (in user's currency)

    In App Name

    Code

    In case a product was bought for several game currencies at once, it is necessary to make a dictionary including the names and amounts of the paid currencies.

    In case a product was bought for several game currencies at once, it is necessary to make a hashmap including the names and amounts of the paid currencies.

    Blueprint

    Notice! If the purchase is done by more than one currency, then the method should be called as many times as many currencies were used, but the amount of purchase should be set only in one of the times.

    Use the method “Record Simple Item Purchase with Attributes” from Analytics Blueprint Library.

    Field

    Type

    Description

    Item Id

    FString

    Unique purchase Id or name (max. 32 symbols)

    Item Quantity

    Item Id field is the identifier of purchased item, Item Quantity is the amount of purchased item. Attributes array should contain the following obligatory information:

    Code

    20 parameter names may be associated with any event:

    Then use method:

    20 parameter names may be associated with any event:

    Then use method:

    20 parameter names may be associated with any event:

    Then use method:

    Field

    Type

    Description

    Event Name

    FString

    Custom event name

    20 parameter names may be associated with any event. Use "Record Event With Attributes".

    Code

    Let’s look at the example of event integration for a match3 game with a location map:

    1. Method startProgressionEvent when enetring the location

    2. Method endProgressionEvent when exiting (no matter if completed or not) the location

      LocationEventParams class methods:

    The user can be only in one location at the same time. When moving to another location (including embedded), the previous location must be completed. Information on locations, the passing of which was not completed by calling endProgressionEvent method during the game session (the call of endProgressionEvent method is not integrated; user unloaded the application from the device memory; there was an application crash) do not fall in the statistics.

    Let’s analyse the example of event integration on match3 game with location map:

    1. Method StartProgressionEvent when enetring the location

    2. Method EndProgressionEvent when exiting (no matter if completed or not) the location

      LocationEventParams class methods:

    The user can be only in one location at the same time. When moving to another location (including embedded), the previous location must be completed. Information on locations, the passing of which was not completed by calling EndProgressionEvent method during the game session (the call of EndProgressionEvent method is not integrated; user unloaded the application from the device memory; there was an application crash) do not fall in the statistics.

    Let’s analyse the example of event integration on match3 game with location map:

    1. Method startProgressionEvent when enetring the location

    2. Method endProgressionEvent when exiting (no matter if completed or not) the location

      Location parameters object contains:

    The user can be only in one location at the same time. When moving to another location (including embedded), the previous location must be completed. Information on locations, the passing of which was not completed by calling endProgressionEvent method during the game session (the call of endProgressionEvent method is not integrated; user unloaded the application from the device memory; there was an application crash) do not fall in the statistics.

    Let’s analyse the example of event integration on match3 game with location map:

    1. Method StartProgressionEvent when enetring the location

    2. Method EndProgressionEvent when exiting (no matter if completed or not) the location

      ProgressionEventParams class methods:

    The user can be only in one location at the same time. When moving to another location (including embedded), the previous location must be completed. Information on locations, the passing of which was not completed by calling EndProgressionEvent method during the game session (the call of EndProgressionEvent method is not integrated; user unloaded the application from the device memory; there was an application crash) do not fall in the statistics.

    Let’s analyse the example of event integration on match3 game with location map:

    1. Method startProgressionEvent when enetring the location

    2. Method endProgressionEvent when exiting (no matter if completed or not) the location

      LocationEventParams class methods:

    The user can be only in one location at the same time. When moving to another location (including embedded), the previous location must be completed. Information on locations, the passing of which was not completed by calling endProgressionEvent method during the game session (the call of endProgressionEvent method is not integrated; user unloaded the application from the device memory; there was an application crash) do not fall in the statistics.

    Let’s analyse the example of event integration on match3 game with location map:

    1. Method StartProgressionEvent when enetring the location

    2. Method EndProgressionEvent when exiting (no matter if completed or not) the location

      LocationEventParams class methods:

    The user can be only in one location at the same time. When moving to another location (including embedded), the previous location must be completed. Information on locations, the passing of which was not completed by calling EndProgressionEvent method during the game session (the call of EndProgressionEvent method is not integrated; user unloaded the application from the device memory; there was an application crash) do not fall in the statistics.

    Let’s analyse the example of event integration on match3 game with location map:

    1. Method StartProgressionEvent when enetring the location

      Blueprint

      Field

      Type

      Description

      locationName

      FString

      The name of location user entered

      Attributes

      Code

    2. Method EndProgressionEvent when exiting (no matter if completed or not) the locationBlueprint

    The user can be only in one location at the same time. When moving to another location (including embedded), the previous location must be completed. Information on locations, the passing of which was not completed by calling endProgressionEvent method during the game session (the call of endProgressionEvent method is not integrated; user unloaded the application from the device memory; there was an application crash) do not fall in the statistics.

    Let’s analyse the example of event integration on match3 game with location map:

    Player comes to the third location on the map “Village” while following the game map. Passing on the first level of difficulty. Before entering this location gamer passed the third location on the map “City”. .. Player passing the location. Player finishes passing of the third location on the map “Village”. The location is passed successfully. The passing took 389 seconds. Gamer finished the passing with 3 stars and gained 70 coins. While the passing gamer used boost and bought extra 5 turns.

    Field

    Type

    Description

    Field

    Type

    Description

    here
    personal data
    /**
    * The event allowing to track the stage of tutorial a player is on.
    * @param NSUInteger tutorialStep - the latest successfully completed tutorial step.
    */
    [DevToDev tutorialCompleted: (NSUInteger) tutorialStep];
    /**
    * The event allowing to track the stage of tutorial a player is on.
    * @param int tutorialStep - the latest successfully completed tutorial step.
    */
    DevToDev.tutorialCompleted(int tutorialStep);
    /**
    *  <param name="state"> The latest successfully completed tutorial step </param>
    */
    DevToDev.SDK.Tutorial(int state)
    /**
    * The event allowing to track the stage of tutorial a player is on.
    * @param {number} tutorialStep - the latest successfully completed tutorial step.
    */
    devtodev.tutorialCompleted(tutorialStep);
    /// <summary> The event allowing to track the stage of tutorial a player is on. </summary>
    /// <param name="tutorialStep"> The latest successfully completed tutorial step </param>
    DevToDev.Analytics.Tutorial(int tutorialStep);

    User profile

    User ID

    This method is used to initialize the user in applications where you have set calculation by user ID specified by the developer.

    You can also use this method when calculating by device ID (by default) to pass in the user ID used on your servers so that you can easily find the user on devtodev.

    We recommend that you pass this parameter in the initialization configuration (userId property of an instance of theDTDAnalyticsConfiguration class).

    Do not pass an empty string ("") to the setUserID method as the user ID. Assigning an empty string is a command to assign a default value to the user ID (it is equal to the current device ID). In applications with calculation by user ID, this can lead to unnecessary registrations.

    To set a new value as the user ID, use the setUserId method.

    To get the current value of the user ID, use the asynchronous method getDeviceId(_ completionHandler: @escaping (String) -> Void)

    We recommend that you pass this parameter in the initialization configuration (userId property of an instance of theDTDAnalyticsConfiguration class).

    Do not pass an empty string ("") to the setUserID method as the user ID. Assigning an empty string is a command to assign a default value to the user ID (it is equal to the current device ID). In applications with calculation by user ID, this can lead to unnecessary registrations.

    To set a new value as the user ID, use the

    We recommend that you pass this parameter in the initialization configuration (userId property of an instance of theDTDAnalyticsConfiguration class).

    Do not pass an empty string ("") to the setUserID method as the user ID. Assigning an empty string is a command to assign a default value to the user ID (it is equal to the current device ID). In applications with calculation by user ID, this can lead to unnecessary registrations.

    To set a new value as the user ID, use the

    We recommend that you pass this parameter in the initialization configuration (userId property of an instance of theDTDAnalyticsConfiguration class).

    Do not pass an empty string ("") to the setUserID method as the user ID. Assigning an empty string is a command to assign a default value to the user ID (it is equal to the current device ID). In applications with calculation by user ID, this can lead to unnecessary registrations.

    To set a new value as the user ID, use the

    We recommend that you pass this parameter in the initialization configuration (userId property of an instance of theDTDAnalyticsConfiguration class).

    Do not pass an empty string ("") to the SetUserID method as the user ID. Assigning an empty string is a command to assign a default value to the user ID (it is equal to the current device ID). In applications with calculation by user ID, this can lead to unnecessary registrations.

    To set a new value as the user ID, use the

    We recommend that you pass this parameter in the initialization configuration (userId property of an instance of theDTDAnalyticsConfiguration class).

    Do not pass an empty string ("") to the SetUserID method as the user ID. Assigning an empty string is a command to assign a default value to the user ID (it is equal to the current device ID). In applications with calculation by user ID, this can lead to unnecessary registrations.

    To set a new value as the user ID, use the

    We recommend that you pass this parameter in the initialization configuration (userId property in config object).

    Do not pass an empty string ("") to the setUserID method as the user ID. Assigning an empty string is a command to assign a default value to the user ID (it is equal to the current device ID). In applications with calculation by user ID, this can lead to unnecessary registrations.

    To set a new value as the user ID, use the setUserId method.

    We recommend that you pass this parameter in the initialization configuration (UserId property of an instance of the FDTDAnalyticsConfiguration class).

    Do not pass an empty string ("") to the SetUserID method as the user ID. Assigning an empty string is a command to assign a default value to the user ID (it is equal to the current device ID). In applications with calculation by user ID, this can lead to unnecessary registrations.

    To set a new value as the user ID, use method:

    We recommend that you pass this parameter in the initialization configuration (userId property of an instance of the GDDTDAnalyticsConfiguration class).

    Do not pass an empty string ("") to the setUserID method as the user ID. Assigning an empty string is a command to assign a default value to the user ID (it is equal to the current device ID). In applications with calculation by user ID, this can lead to unnecessary registrations.

    To set a new value as the user ID, use the SetUserId method.

    To get the current value of the user ID, use the asynchronous method

    In order to track a user on several devices you can switch the identification method to identification by Custom User Id. This switch can be done only by devtodev – you just need to write a request to the support (use the Contact Us form), specifying the space name and project name. You must set the user IDs for all users before changing the identification method.

    Please note: changing the idetification method is irreversible and you will not be able to switch back to the device identifiers in the future!

    Replace

    This method is used in very rare cases. It is applicable in the case when the calculation of users in the project is carried out by the user ID specified by the developer. In this case, the application can change this identifier. For example, calculation in the application is carried out by user emails and in the application, it is possible to change this email to another one. At the time of replacement, you need to call this method, specifying the user's previous email and the one with which it was replaced.

    Do not use this method to re-login as a different user! The setUserId method is used for relogging.

    Current user level

    This method is used in cross-platform and data synchronized applications. The method is required to update user level data.

    To set the current value to the user level, use setCurrentLevel(currentLevel: Int) method:

    We recommend that you use the setCurrentLevel method immediately after using the method or specify the current user level in the initialization configuration (the level property of an instance of the DTDAnalyticsConfiguration).

    Do not use the setCurrentLevel

    Cheater

    If you have your own methods for detecting cheaters in the application, you can tag such users. Actions taken by these users will not be counted in statistics.

    Arguments
    Type

    Tester

    Use this method to tag a user as a tester. Events performed by testers will not be included in statistics.

    Arguments
    Type

    Reserved user properties

    Attention! These properties have been removed since the devtodev SDK versions: iOS & macOS 2.4.0, Android 2.5.0, Unity SDK 3.8.0, Godot 1.0.0, Web 2.1

    We strongly recommend not to use these properties because they refer to .

    Attention! We strongly discourage the storage of personal user data! If you plan to pass this data, be sure to indicate this in the ‘Nutrition label’ when submitting the application to the App Store review.

    Custom user property

    Each devtodev project can have up to 30 custom user properties. User custom property values can be a number, a string (up to 500 symbols), or a boolean value.

    Attention! We strongly recommend that you do not use these properties to transfer and store data that fits the definition of !

    This is how you can set properties on the current user profile:

    It is important to remember that the key for custom user properties has a length limit. If the limit is exceeded (from 1 to 64 characters), the key will be truncated to the maximum allowed length

    To get the current value stored in the user profile on the SDK, you need to use the method:

    getValue(key: String, _completionHandler: @escaping (Any) -> Void)

    Unset user property

    It removes a property or a list of properties and their values from the current user profile.

    Unset all user properties

    To remove all properties from the user card, use:

    Keep in mind that the cheater mark is not cleared from the user card; you can only uncheck the mark manually using the method.

    Keep in mind that the cheater mark is not cleared from the user card; you can only uncheck the mark manually using the

    /**
    * Register transactions made through the platform's payment system.
    *  <param name="orderId"> Transaction id </param>
    *  <param name="price"> Product price (in user's currency) </param>
    *  <param name="productId"> Product id (product name) </param>
    *  <param name="currencyCode"> Transaction currency (ISO 4217 format)</param>
    */
    DevToDev.SDK.RealPayment(string orderId, float price, string productId, string currencyCode)
    DevToDev.SDK.RealPayment("1836535032137741465" , 2.99f , "productId" , "USD" );
    /**
    * Register transactions made through the platform's payment system.
    *
    * @param {string} transactionId - transaction ID
    * @param {number} productPrice - product price (in user's currency)
    * @param {string} productName - product name
    * @param {string} transactionCurrencyISOCode - transaction currency (ISO 4217 format)
    */
    
    devtodev.realPayment(transactionId, productPrice, productName, transactionCurrencyISOCode);
    devtodev.realPayment("12345", 9.99, "Currency pack 2", "USD");
    /// <summary> Register transactions made through the platform's payment system. </summary>
    /// <param name="paymentId"> Transaction id </param>
    /// <param name="inAppPrice"> Product price (in user's currency) </param>
    /// <param name="inAppName"> Product id (product name) </param>
    /// <param name="inAppCurrencyISOCode"> Transaction currency (ISO 4217 format)</param>
    DevToDev.Analytics.RealPayment(string paymentId, float inAppPrice, string inAppName,
                                   string inAppCurrencyISOCode);
    /**
    * Register transactions made through the platform's payment system.
    *
    * @param NSString * paymentId - transaction ID
    * @param float inAppPrice - product price (in user's currency)
    * @param NSString * inAppName - product name
    * @param NSString * inAppCurrencyISOCode - transaction currency (ISO 4217 format)
    */
    [DevToDev realPayment: (NSString *) transactionId withInAppPrice:(float) inAppPrice 
             andInAppName: (NSString *) inAppName andInAppCurrencyISOCode: (NSString *) inAppCurrencyISOCode];
    /**
    * Register transactions made through the platform's payment system.
    * @param paymentId - transaction ID
    * @param inAppPrice - product price (in user's currency)
    * @param inAppName - product name
    * @param inAppCurrencyISOCode - transaction currency (ISO 4217 format)
    */
    DevToDev.realPayment(paymentId:String, inAppPrice:Number, inAppName:String,
                         inAppCurrencyISOCode:String);
    /**
    * In-app purchase with a definite article ID.
    *
    * @param purchaseId - unique purchase Id or name (max. 32 symbols)
    * @param purchaseType - purchase type or group (max. 96 symbols)
    * @param purchaseAmount - count of purchased goods
    * @param purchasePrice - cost of purchased goods (total cost -if several goods were purchased)
    * @param purchaseCurrency - currency name (max. 24 symbols)
    */
    [DevToDev inAppPurchase: (NSString *) purchaseId withPurchaseType: (NSString *) purchaseType
          andPurchaseAmount: (NSInteger) purchase Amount andPurchasePrice: (NSInteger) purchaseprice 
        andPurchaseCurrency: (NSString *) purchaseCurrency];
    NSMutableDictionary * resources = [[NSMutableDictionary alloc] init];
    [resources setObject:@100 forKey:@"currency1"];
    [resources setObject:@10 forKey:@"currency2"];
    //...and so on...
    
    [DevToDev inAppPurchase:(NSString )purchaseId withPurchaseType:(NSString )purchaseType 
          andPurchaseAmount:(NSInteger)purchaseAmount andResources: (NSDictionary *) resources];
    /**
    * In-app purchase with a definite article ID.
    * @param purchaseId - unique purchase Id or name (max. 32 symbols)
    * @param purchaseType - purchase type or group (max. 96 symbols)
    * @param purchaseAmount - count of purchased goods
    * @param purchasePrice - cost of purchased goods (total cost - if several goods were purchased)
    * @param purchaseCurrency - currency name (max. 24 symbols)
    */
    DevToDev.inAppPurchase(purchaseId:String, purchaseType:String, purchaseAmount:int, purchasePrice:int,
                           purchaseCurrency:String);
    var resources: Dictionary = new Dictionary();
    resources["currency_1"] = 120;
    resources["currency_2"] = 29;
    //...and so on...
    
    DevToDev.inAppPurchaseWithResources(purchaseId:String, purchaseType:String, purchaseAmount:int,
                                        resources:Dictionary);
    /// <param name="eventName"> Event name </param>
    DevToDev.Analytics.CustomEvent(string eventName);
    DevToDev.CustomEventParams customEventParams = new DevToDev.CustomEventParams();
    customEventParams.AddParam("double", 1.12);
    customEventParams.AddParam("int", 145);
    customEventParams.AddParam("long", 123L);
    customEventParams.AddParam("string","start");
    /// <param name="eventName">Event name</param>
    /// <param name="eventParams">Event parameters</param>
    DevToDev.Analytics.CustomEvent(string eventName, DevToDev.CustomEventParams eventParams);
    /**
    * @param String eventName - event name
    */
    [DevToDev customEvent: (NSString *) eventName];
    CustomEventParams * params_1 = [[CustomEventParams alloc] init];
    [params_1 putParam:@"date" withDate:[NSDate date]];
    [params_1 putParam:@"double" withDouble:123.1231231231231];
    [params_1 putParam:@"float" withFloat:123.123123f];
    [params_1 putParam:@"int" withInt:123];
    [params_1 putParam:@"long" withLong:6152437L];
    [param_1 putParam:@"string" withString:@"string"];
    /**
    * @param String eventName - event name
    * @param CustomEventParams params - event parameters
    */
    [DevToDev customEvent: (NSString *) eventName withParams: (CustomEventParams *) params];
    /**
    * Simple custom event
    * @param String eventName - event name
    */
    DevToDev.customEvent(eventName:String);
    var params:CustomEventParams = new CustomEventParams();
    /**
    * String type custom event parameter
    * @param paramName - parameter name
    * @param value - parameter value
    */
    params.putString(paramName:String , value:String);
    //Integer type custom event parameter
    params.putInt(paramName:String, 145:int);
    //Number type custom event parameter
    params.putFloat(paramName:String, 9.99:Number);
    /**
    * Custom event with params
    * @param eventName - event name
    * @param params - event parameters
    */
    DevToDev.customEventsWithParams(eventName:String, params:CustomEventParams);
    FAnalytics::Get().GetDefaultConfiguredProvider()->RecordEvent(const FString& EventName,
                                                     const TArray<FAnalyticsEventAttribute>& Attributes);
    /**
    * The method have to be used when entering the location.
    * @param String locationName - the name of location user entered.
    * @param LocationEventParams params - instance of location parameters class
    */
    DevToDev.startProgressionEvent(locationName, params);
    /**
    * The method have to be used when the location passing is over.
    * @param String locationName - the name of location user left.
    * @param LocationEventParams params - instance of location parameters class
    */
    DevToDev.endProgressionEvent(locationName, params);
    /**
      * Location level of difficulty (optional).
      * @param int difficultyLevel - level of difficulty
      */
      setDifficulty(difficultyLevel);
    
      /**
      * Previously visited location (optional).
      * @param String locationName -  previously visited location name
      */
      setSource(locationName);
    
      /**
      * State/result of the location passing (required).
      * @param boolean isCompleted -  true if location is successfuly passed
      */
      setSuccessfulCompletion(isCompleted);
    
      /**
      * Time spent in the location (optional).
      * In case the parameter is not specified by the developer, it will be automatically calculated
      * as the date difference between startProgressionEvent and endProgressionEvent method calls.
      * @param int duration - time in seconds
      */
      setDuration(duration);
    
      /**
      * User spendings within the location passing (optional).
      * @ param HashMap<String, Number> spent - user spendings. Key length max. 24 symbols.
      */
      setSpent(spent);
    
      /**
      * User earnings within the location passing (optional).
      * @param HashMap<String, Number> earned - user earnings. Key length max. 24 symbols.
      */
      setEarned(earned);
    /**
    * <param name="locationName">The name of location user entered</param>
    * <param name="locationParams">Instance of location parameters class</param>
    */
    DevToDev.SDK.StartProgressionEvent(string locationName, LocationEventParams locationParams);
    /**
    * <param name="locationName">The name of location user entered</param>
    * <param name="locationParams">Instance of location parameters class</param>
    */
    DevToDev.SDK.EndProgressionEvent(string locationName, LocationEventParams locationParams);
      /**
      * Location level of difficulty (optional).
      * <param name="difficultyLevel">Level of difficulty</param>
      */
      SetDifficulty(int difficultyLevel);
    
      /**
      * Previously visited location (optional).
      * <param name="locationName">Previously visited location name</param>
      */
      SetSource(string locationName);
    
      /**
      * State/result of the location passing (required).
      * <param name="isCompleted">True if location is successfuly passed</param>
      */
      SetSuccessfulCompletion(bool isCompleted);
    
      /**
      * Time spent in the location (optional).
      * In case the parameter is not specified by the developer, it will be automatically calculated
      * as the date difference between StartProgressionEvent and EndProgressionEvent method calls.
      * <param name="duration">Time in seconds</param>
      */
      SetDuration(long duration);
    
      /**
      * User spendings within the location passing (optional). Key (currency name) length max. 24 symbols.
      * <param name="spent">User spendings</param>
      */
      SetSpent(Dictionary<string, int> spent);
    
      /**
      * User earnings within the location passing (optional). Key (currency name) length max. 24 symbols.
      * <param name="earned">User earnings</param>
      */
      SetEarned(Dictionary<string, int> earned);
    /**
    * The method have to be used when entering the location.
    * @param {string} locationName - the name of location user entered.
    * @param {Object} startParams - location parameters object
    */
    devtodev.startProgressionEvent(locationName, startParams);
    /**
    * The method have to be used when the location passing is over.
    * @param {string} locationName - the name of location user left.
    * @param {Object} endParams - location parameters object
    */
    devtodev.endProgressionEvent(locationName, endParams);
      var params = {
          // Previously visited location (optional).
          "source" : "locationSource",
    
          // Location level of difficulty (optional).
          "difficulty" : 1,
    
          // Time spent in the location (optional).
          // In case the parameter is not specified by the developer, it will be automatically calculated
          // as the date difference between startProgressionEvent and endProgressionEvent method calls.
          "duration" : 80,
    
          // State/result of the location passing (required).
          "success" : true,
    
          //User spendings within the location passing (optional).
          "spent" : [
              {
                  "currency": "currency 1 name", // Currency name length max. 24 symbols.
                  "amount": 1   // objects with amount value less than 1 are ignored
              },
              {
                  "currency": "currency 2 name",
                  "amount": 2
              }
          ],
    
          // User earnings within the location passing (optional).
          "earned" : [
              {
                  "currency": "currency 1 name",
                  "amount": 1 // objects with amount value less than 1 are ignored
              },
              {
                  "currency": "currency 2 name",
                  "amount": 2
              }
          ]
      };
    /// <param name="eventId"> The name of location user entered </param>
    /// <param name="eventParams"> Instance of progression parameters class </param>
    DevToDev.Analytics.StartProgressionEvent(string eventId, ProgressionEventParams eventParams);
    /// <param name="eventId"> The name of location user left </param>
    /// <param name="eventParams"> Instance of progression parameters class </param>
    DevToDev.Analytics.EndProgressionEvent(string eventId, ProgressionEventParams eventParams);
      /// <summary> Location level of difficulty (optional). </summary>
      /// <param name="difficultyLevel"> Level of difficulty </param>
      SetDifficulty(int difficultyLevel);
    
      /// <summary> Previously visited location (optional). </summary>
      /// <param name="locationName"> Previously visited location name </param>
      SetSource(string locationName);
    
      /// <summary> State/result of the location passing (required). </summary>
      /// <param name="isCompleted"> True if location is successfuly passed </param>
      SetSuccessfulCompletion(bool isCompleted);
    
      /// <summary>Time spent in the location (optional).
      /// <para>In case the parameter is not specified by the developer, it will be automatically calculated
      /// as the date difference between StartProgressionEvent and EndProgressionEvent method calls.</para>
      /// </summary>
      /// <param name="duration"> Time in seconds </param>
      SetDuration(long duration);
    
      /// <summary> User spendings within the location passing (optional). Dictionary key max.length is 24 symbols.</summary>
      /// <param name="spent"> User spendings </param>
      SetSpent(Dictionary<string, int> spent);
    
      /// <summary> User earnings within the location passing (optional). </summary>
      /// <param name="earned"> User earnings </param>
      SetEarned(Dictionary<string, int> earned);
    /**
    * The method have to be used when entering the location.
    * @param String locationName - the name of location user entered.
    * @param LocationEventParams params - instance of location parameters class
    */
    [DevToDev startProgressionEvent: locationName withParameters: params];
    /**
    * The method have to be used when the location passing is over.
    * @param String locationName - the name of location user left.
    * @param LocationEventParams params - instance of location parameters class
    */
    [DevToDev endProgressionEvent: locationName withParameters: params];
    /**
      * Location level of difficulty (optional).
      * @param NSInteger difficultyLevel - level of difficulty
      */
      [params setDifficulty: difficultyLevel];
    
      /**
      * Previously visited location (optional).
      * @param NSString* locationName -  previously visited location name
      */
      [params setSource: locationName];
    
      /**
      * State/result of the location passing (required).
      * @param BOOL isCompleted -  true if location is successfuly passed
      */
      [params setIsSuccess: isCompleted];
    
      /**
      * Time spent in the location (optional).
      * In case the parameter is not specified by the developer, it will be automatically calculated
      * as the date difference between startProgressionEvent and endProgressionEvent method calls.
      * @param NSNumber* duration - time in seconds
      */
      [params setDuration: duration];
    
      /**
      * User spendings  within the location passing (optional).
      * @ param NSDictionary* spent - user spendings. Key max.length is 24 symbols.
      */
      [params setSpent: spent];
    
      /**
      * User earnings  within the location passing (optional).
      * @param NSDictionary* earned - user earnings.  Key max.length is 24 symbols.
      */
      [params setEarned: earned];
    /**
    * The method have to be used when entering the location.
    * @param locationName - the name of location user entered.
    * @param params - instance of location parameters class
    */
    DevToDev.StartProgressionEvent(locationName:String, params:LocationEventParams);
    /**
    * The method have to be used when the location passing is over.
    * @param locationName - the name of location user left.
    * @param params - instance of location parameters class
    */
    DevToDev.EndProgressionEvent(locationName:String, params:LocationEventParams);
      /**
      * Location level of difficulty (optional).
      * @param difficultyLevel - level of difficulty
      */
      SetDifficulty(difficultyLevel:int);
    
      /**
      * Previously visited location (optional).
      * @param locationName -  previously visited location name
      */
      SetSource(locationName:String);
    
      /**
      * State/result of the location passing (required).
      * @param isCompleted -  true if location is successfuly passed
      */
      SetSuccessfulCompletion(isCompleted:Boolean);
    
      /**
      * Time spent in the location (optional).
      * In case the parameter is not specified by the developer, it will be automatically calculated
      * as the date difference between StartProgressionEvent and EndProgressionEvent method calls.
      * @param duration - time in seconds
      */
      SetDuration(duration:int);
    
      /**
      * User spendings within the location passing (optional).
      * @ param spent - user spendings. Key max.length is 24 symbols.
      */
      SetSpent(spent:Dictionary);
    
      /**
      * User earnings within the location passing (optional).
      * @param earned - user earnings. Key max.length is 24 symbols.
      */
      SetEarned(earned:Dictionary);
    /**
    * The event allowing to track the stage of tutorial a player is on.
    * @param NSUInteger tutorialStep - the latest successfully completed tutorial step.
    */
    [DevToDev tutorialCompleted: (NSUInteger) tutorialStep];
    /**
    * The event allowing to track the stage of tutorial a player is on.
    * @param tutorialStep - the latest successfully completed tutorial step.
    */
    DevToDev.tutorialCompleted(tutorialStep:int);
    /**
    * Player has reached a new level
    * @param NSInteger level - level reached by the player.
    */
    [DevToDev levelUp: (NSInteger) level];
    /**
    * Player has reached a new level
    * @param int level - level reached by the player.
    */
    DevToDev.levelUp(int level);
    /**
    * <param name="level"> Level reached by the player </param>
    */
    DevToDev.SDK.Level(int level);
    /**
    * Player has reached a new level
    * @param {number} level - level reached by the player.
    */
    
    devtodev.levelUp(level);
    /// <summary> Player has reached a new level </summary>
    /// <param name="level"> Level reached by the player </param>
    DevToDev.Analytics.LevelUp(int level);
    /**
    * Player has reached a new level
    * @param NSUInteger level - level reached by the player.
    */
    [DevToDev levelUp: (NSUInteger) level];
    /**
    * Player has reached a new level
    * @param level - level reached by the player.
    */
    DevToDev.levelUp(level:int);
    /**
    * Player has reached a new level
    * @param NSInteger level - level reached by the player.
    * @param NSDictionary resources - dictionary with the currency names and amounts
    */
    NSDictionary * resources = @{@"Currency name 1" : @100, @"Currency name 2" : @10};
    [DevToDev levelUp: (NSUInteger) level withResources: withResources: (NSDictionary *) resources];
    /**
    * @param int level - level reached by the player
    * @param HashMap resources - hashmap with the currency names and amounts
    */
    HashMap resources = new HashMap<String, Integer>();
    resources.put("Currency name 1", 1000);
    resources.put("Currency name 2", 10);
    DevToDev.levelUp(level, resources);
    /**
    * <param name="level"> Player level </param>
    * <param name="resources">Dictionary with the currency names and amounts</param>
    */
    Dictionary<string, int> resources = new Dictionary<string, int>();
    resources.Add("Currency name 1", 1000);
    resources.Add("Currency name 2", 10);
    DevToDev.SDK.Level(level, resources);
    /**
    * @param {number} level - player/character's "level"
    * @param {Object} resources - Object with data about currencies. Optional.
    * @param {Object[]} resources.balance -  Account balance of in-game currency by the end of level. * Optional.
    * @param {Object[]} resources.balance[].currency - Game currency name
    * @param {Object[]} resources.balance[].amount - Game currency amount
    * @param {Object[]} resources.earned - Game currency earned during the level. Optional. 
    * @param {Object[]} resources.earned[].currency - Game currency name
    * @param {Object[]} resources.earned[].amount - Game currency amount
    * @param {Object[]} resources.spent - Game currency amount spent during the level. Optional.
    * @param {Object[]} resources.spent[].currency - Game currency name
    * @param {Object[]} resources.spent[].amount - Game currency amount
    * @param {Object[]} resources.bought - Game currency amount bought during the level. Optional.
    * @param {Object[]} resources.bought[].currency - Game currency name
    * @param {Object[]} resources.bought[].amount - Game currency amount 
    */
    
    devtodev.levelUp(level, resources);
    /// <summary> Player has reached a new level</summary>
    /// <param name="level"> Player level </param>
    /// <param name="resources"> Dictionary with the currency names and amounts </param>
    /// <example>
    /// 
    ///    Dictionary<string, int> resources = new Dictionary<string, int>();
    ///    resources.Add("Currency name 1", 1000);
    ///    resources.Add("Currency name 2", 10);
    /// 
    /// </example>
    DevToDev.Analytics.LevelUp(level, resources);
    /**
    * Player has reached a new level
    * @param NSInteger level - level reached by the player.
    * @param NSDictionary resources - dictionary with the currency names and amounts
    */
    NSDictionary * resources = @{@"Currency name 1" : @100, @"Currency name 2" : @10};
    [DevToDev levelUp: (NSUInteger) level withResources: withResources: (NSDictionary *) resources];
    /**
    * Player has reached a new level
    * @param level - level reached by the player.
    * @param resources - dictionary with the currency names and amounts
    */
    var resources: Dictionary = new Dictionary();
    resources["Currency name 1"] = 1000;
    resources["Currency name 2"] = 10;
    DevToDev.levelUpWithResources(level:int, resources:Dictionary);
    /* *
    * @param NSString * currencyName - currency name (max. 24 symbols)
    * @param NSInteger amount - the amount an account has been credited with.
    * @param AccrualType accrualType - the way the currency was obtained: earned or purchased
    */
    [DevToDev currencyAccrual: (NSInteger) amount withCurrencyName: (nonnull NSString *) currencyName
     andCurrencyType: (AccrualType) accrualType];
    /**
    * @param String currencyName - currency name (max. 24 symbols)
    * @param float currencyAmount - the amount an account has been credited with
    * @param AccrualType accrualType - the way the currency was obtained: earned or purchased
    */
    DevToDev.currencyAccrual(currencyName, currencyAmount, accrualType);
    /**
    * <param name="currencyName">Currency name (max. 24 symbols)</param>
    * <param name="currencyAmount ">The amount an account has been credited with</param>
    * <param name="accrualType">The way the currency was obtained: earned or purchased</param>
    */
    DevToDev.SDK.CurrencyAccrual(string currencyName, float currencyAmount, AccrualType accrualType);
    /// <param name="currencyName"> Currency name (max. 24 symbols) </param>
    /// <param name="currencyAmount "> The amount an account has been credited with </param>
    /// <param name="accrualType"> The way the currency was obtained: earned or purchased </param>
    DevToDev.Analytics.CurrencyAccrual(int amount, string currencyName, AccrualType accrualType);
    /**
    * @param NSString * currencyName - currency name (max. 24 symbols)
    * @param float amount - the amount an account has been credited with.
    * @param AccrualType accrualType - the way the currency was obtained: earned or purchased
    */
    /* *
    * @param NSString * currencyName - currency name (max. 24 symbols)
    * @param NSInteger amount - the amount an account has been credited with.
    * @param AccrualType accrualType - the way the currency was obtained: earned or purchased
    */
    [DevToDev currencyAccrual: (NSInteger) amount withCurrencyName: (nonnull NSString *) currencyName
     andCurrencyType: (AccrualType) accrualType];
    /**
    * @param currencyName - currency name (max. 24 symbols)
    * @param currencyAmount - the amount an account has been credited with
    * @param accrualType - the way the currency was obtained: earned or purchased
    */
    DevToDev.currencyAccrual(currencyName:String, currencyAmount:Number, accrualType:AccrualType);
    typedef enum {
    Earned,
    Purchased
    } AccrualType ;
    public enum AccrualType {
                              Earned,
                              Purchased
                            };
    public enum AccrualType {
                              Earned,
                              Purchased
    };
    public enum AccrualType {
        Earned,
        Purchased
    };
    typedef enum {
                  Earned,
                  Purchased
    } AccrualType;
    AccrualType.EARNED;
    AccrualType.PURCHASED;
    /**
    * Register transactions made through the platform's payment system.
    *
    * @param NSString * paymentId - transaction ID  (max. 64 symbols)
    * @param float inAppPrice - product price (in user's currency)
    * @param NSString * inAppName - product name
    * @param NSString * inAppCurrencyISOCode - transaction currency (ISO 4217 format)
    */
    [DevToDev realPayment: (NSString *) transactionId withInAppPrice:(float) inAppPrice 
    andInAppName: (NSString *) inAppName andInAppCurrencyISOCode: (NSString *) inAppCurrencyISOCode];
    /**
    * Register transactions made through the platform's payment system.
    *
    * @param String paymentId - transaction ID (max. 64 symbols)
    * @param float inAppPrice - product price (in user's currency)
    * @param String inAppName - product name
    * @param String inAppCurrencyISOCode - transaction currency 
    * (ISO 4217 format http://www.iso.org/iso/home/standards/currency_codes.htm Exapmle: "USD")
    */
    DevToDev.realPayment(String paymentId, float inAppPrice, String inAppName, 
                         String inAppCurrencyISOCode);
    /**
    * In-app purchase with a definite article ID.
    *
    * @param NSString purchaseId - unique purchase Id or name (max. 32 symbols)
    * @param NSString purchaseType - purchase type or group (max. 96 symbols)
    * @param NSInteger purchaseAmount - count of purchased goods
    * @param NSInteger purchasePrice - cost of purchased goods (total cost -if several goods were purchased)
    * @param NSString purchaseCurrency - currency name (max. 24 symbols)
    */
    [DevToDev inAppPurchase: (NSString *) purchaseId withPurchaseType: (NSString *) purchaseType 
    andPurchaseAmount: (NSInteger) purchase Amount andPurchasePrice: (NSInteger) purchaseprice 
    andPurchaseCurrency: (NSString *) purchaseCurrency];
    NSMutableDictionary * resources = [[NSMutableDictionary alloc] init];
    [resources setObject:@100 forKey:@"currency1"];
    [resources setObject:@10 forKey:@"currency2"];
    //...and so on...
    
    [DevToDev inAppPurchase:(NSString )purchaseId withPurchaseType:(NSString )purchaseType
     andPurchaseAmount:(NSInteger)purchaseAmount andResources: (NSDictionary *) resources];
    /**
    * In-app purchase with a definite article ID.
    *
    * @param purchaseId - unique purchase Id or name (max. 32 symbols)
    * @param purchaseType - purchase type or group (max. 96 symbols)
    * @param purchaseAmount - count of purchased goods
    * @param purchasePrice - cost of purchased goods (total cost - if several goods were purchased)
    * @param purchaseCurrency - currency name (max. 24 symbols)
    */
    DevToDev.inAppPurchase(String purchaseId, String purchaseType, int purchaseAmount, 
                           int purchasePrice, String purchaseCurrency);
    HashMap resources = new HashMap();
    resources.put("currency_1", 120);
    resources.put("currency_2", 29);
    DevToDev.inAppPurchase(String purchaseId, String purchaseType, int purchaseAmount, HashMap resources);
    /**
    * <param name="purchaseId"> Unique purchase ID or name (max. 32 symbols)</param>
    * <param name="purchaseType"> Purchase type or group (max. 96 symbols)</param>
    * <param name="purchaseAmount"> Number of purchased goods </param>
    * <param name="purchasePrice"> Cost of purchased goods (total cost - if several goods were purchased)</param>
    * <param name="purchasePriceCurrency"> Currency name (max. 24 symbols)</param>
    */
    DevToDev.SDK.InAppPurchase(string purchaseId, string purchaseType, int purchaseAmount,
                               int purchasePrice, string purchasePriceCurrency)
    DevToDev.SDK.InAppPurchase("sword", "weapons", 1, 200, "coins");
    Dictionary<string, int> resources = new Dictionary<string, int>();
    resources.Add("currency_1", 120);
    resources.Add("currency_2", 29);
    //...and so on...
    
    DevToDev.SDK.InAppPurchase(string purchaseId, string purchaseType, int purchaseAmount, 
                               Dictionary<string, int> resources);
    /**
    * Tracks in-app purchases.
    *
    * @param {string} purchaseId - unique purchase Id or name (max. 32 symbols)
    * @param {string} purchaseType - purchase type or group (max. 96 symbols)
    * @param {number} purchaseAmount - count of purchased goods
    * @param {Object[]} purchasePrice - array including the names and amounts of
    * the paid currencies (total cost - if several goods were purchased)
    * @param {string} purchasePrice[].currency - game currency name
    * @param {number} purchasePrice[].amount - currency amount
    */
    
    devtodev.inAppPurchase(purchaseId, purchaseType, purchaseAmount, purchasePrice);
    var purchasePrice = [
        {
            “currency” : "coins", //game currency name
            “amount” : 1000 //game currency amount 
        },
        {
            “currency” : "gold", //game currency name
            “amount” : 10 //game currency amount
        }
    ];
    
    devtodev.inAppPurchase(“cloak”, “clothes”, 1, purchasePrice);
    /// <summary> In-app purchase with a definite ID. </summary>
    /// <param name="purchaseId"> Unique purchase ID  or name (max. 32 symbols)</param>
    /// <param name="purchaseType"> Purchase type or group (max. 96 symbols)</param>
    /// <param name="purchaseAmount"> Number of purchased goods </param>
    /// <param name="purchasePrice"> Cost of purchased goods (total cost - if several goods were purchased)</param>
    /// <param name="purchasePriceCurrency"> Currency name (max. 24 symbols)</param>
    DevToDev.Analytics.InAppPurchase(string purchaseId, string purchaseType, int purchaseAmount,
                                     int purchasePrice, string purchaseCurrency);
    Dictionary<string, int> resources = new Dictionary<string, int>();
    resources.Add("currency_1", 120);
    resources.Add("currency_2", 29);
    //...and so on...
    
    DevToDev.Analytics.InAppPurchase(string purchaseId, string purchaseType, int purchaseAmount,
                                     Dictionary<string, int> resources);
    /**
    * @param NSString eventName - event name
    */
    [DevToDev customEvent: (NSString *) eventName];
    CustomEventParams * params_1 = [[CustomEventParams alloc] init];
    [params_1 putParam:@"double" withDouble:123.1231231231231];
    [params_1 putParam:@"float" withFloat:123.123123f];
    [params_1 putParam:@"int" withInt:123];
    [params_1 putParam:@"long" withLong:6152437L];
    [param_1 putParam:@"string" withString:@"string"];
    /**
    * @param NSString eventName - event name
    * @param CustomEventParams params - event parameters
    */
    [DevToDev customEvent: (NSString *) eventName withParams: (CustomEventParams *) params];
    /**
    * Simple custom event
    * @param String eventName - event name
    */
    DevToDev.customEvent(eventName);
    CustomEventParams params = new CustomEventParams();
    params.putDouble("double", 1.12);
    params.putFloat("float", 9.99f);
    params.putInteger("int", 145);
    params.putLong("long", 123L);
    params.putString("string","start");
    /**
    * Custom event with params
    * @param String eventName - event name
    * @param CustomEventParams params - event parameters
    */
    DevToDev.customEvent(eventName, params);
    /**
    * <param name="eventName"> Event name </param>
    */
    DevToDev.SDK.CustomEvent(string eventName);
    DevToDev.SDK.CustomEvent("bonus_used");
    /**
    * <param name="eventName">Event name</param>
    * <param name="eventParams">Event parameters</param>
    */
    DevToDev.SDK.CustomEvent(string eventName, CustomEventParams eventParams)
    var cep = new CustomEventParams();
    cep.AddParam("bonus_name", "your_awesome_bonus");
    DevToDev.SDK.CustomEvent("bonus_used", cep);
    /**
    * Tracks custom events.
    * @param {string} eventName - event name
    **/
    
    devtodev.customEvent(eventName);
    /**
    * Tracks custom events.
    * @param {string} eventName - event name (max. 72 symbols)
    * @param {Object[]} params - array of event parameters. Up to 20 params.
    * @param {string} params[].name - parameter name (max. 32 symbols)
    * @param {string} params[].type - parameter value type. Can be "double" or "string".
    * @param {string|number} params[].value - parameter value. (max. 255 symbols)
    **/
    
    devtodev.customEvent(eventName, params);
    var params = [
        {
            "name": "score",
            "type": "double",
            "value": 100500,
        },
        {
            "name": "type",
            "type": "string",
            "value": "fatality",
        },
        … //up to 10 parameters.
    ];
    
    devtodev.customEvent("win", params);
    /**
    * The method have to be used when entering the location.
    * @param String locationName - the name of location user entered.
    * @param LocationEventParams params - instance of location parameters class
    */
    [DevToDev startProgressionEvent: locationName withParameters: params];
    /**
    * The method have to be used when the location passing is over.
    * @param String locationName - the name of location user left.
    * @param LocationEventParams params - instance of location parameters class
    */
    [DevToDev endProgressionEvent: locationName withParameters: params];
      /**
      * Location level of difficulty (optional).
      * @param NSInteger difficultyLevel - level of difficulty
      */
      [params setDifficulty: difficultyLevel];
    
      /**
      * Previously visited location (optional).
      * @param NSString* locationName -  previously visited location name
      */
      [params setSource: locationName];
    
      /**
      * State/result of the location passing (required).
      * @param BOOL isCompleted -  true if location is successfuly passed
      */
      [params setIsSuccess: isCompleted];
    
      /**
      * Time spent in the location (optional).
      * In case the parameter is not specified by the developer, it will be automatically calculated
      * as the date difference between startProgressionEvent and endProgressionEvent method calls.
      * @param NSNumber* duration - time in seconds
      */
      [params setDuration: duration];
    
      /**
      * User spendings  within the location passing (optional).
      * @ param NSDictionary* spent - user spendings. Key length max. 24 symbols.
      */
      [params setSpent: spent];
    
      /**
      * User earnings  within the location passing (optional).
      * @param NSDictionary* earned - user earnings. Key length max. 24 symbols.
      */
      [params setEarned: earned];
    // The event allowing to track the stage of tutorial a player is on.
    // int32 step - the latest successfully completed tutorial step.
    
    UDevToDevBlueprintFunctionLibrary::TutorialCompleted(int32 step);
    // Player has reached a new level
    // int32 level - level reached by the player.
    
    UDevToDevBlueprintFunctionLibrary::LevelUp(int32 level);
    // Player has reached a new level
    // int32 level - level reached by the player.
    // TArray<FAnalyticsEventAttr> Attributes - dictionary with the currency names and amounts
    
    UDevToDevBlueprintFunctionLibrary::LevelUpWithAttributes(int32 level,
                                                             const TArray<FAnalyticsEventAttr>& Attributes);
    FAnalytics::Get().GetDefaultConfiguredProvider()->RecordCurrencyGiven(const FString& GameCurrencyType,
                                                       int GameCurrencyAmount,
                                                       const TArray<FAnalyticsEventAttribute>& EventAttrs);
    // Player comes to the third location on the map "Village" while following the game map. 
    // Create a parameters object
    LocationEventParams* params = [[LocationEventParams alloc] init];
    
    // Specify the known location parameters:
    // Passing on the first level of difficulty.
    [params setDifficulty:1];
    
    // Before entering this location gamer passed the third location on the map â&#128;&#156;Villageâ&#128;&#157; (optional).
    [params setSource: @"Vilage step 02"];
    
    //The location passing starts (required).
    [DevToDev startProgressionEvent:@"Vilage step 03" withParameters:params];
    
    // ... Player passing the location.
    
    // Player finishes passing of the third location on the map â&#128;&#156;Villageâ&#128;&#157;
    
    // The location is passed successfully (required).
    [params setIsSuccess: YES];
    
    // The passing took 189 seconds.
    [params setDuration:@189];
    
    // Location is passed for 54 turns. While the passing gamer used boost and bought extra 5 turns.
    NSDictionary* spent = @{
        @"Turns" : @54,
        @"Boost Bomb" : @1,
        @"Extra 5 Turns" : @1
    };
    [params setSpent: spent];
    
    // Gamer finished the passing with 3 stars and gained 5 coins and 1200 score.
    NSDictionary* earned = @{
        @"Stars" : @3,
        @"Score" : @1200,
        @"Coins" : @5
    };
    [params setEarned: earned]; 
    
    // The location passing is over (required).
    [DevToDev endProgressionEvent: @"Vilage step 03" withParameters: params];

    Earned

    TArray<FAnalyticsEventAttr>

    User earnings within the location passing (optional). Key max. length is 24 symbols.

    Spent

    TArray<FAnalyticsEventAttr>

    User spendings within the location passing (optional). Key max. length is 24 symbols.

    Location parameters

    Key

    Type

    Description

    success

    bool

    State/result of the location passing (required).

    source

    FString

    Previously visited location (optional).

    difficulty

    int32

    Location level of difficulty (optional).

    duration

    Code

    FString

    Product name

    In App Currency ISOCode

    FString

    Transaction currency (ISO 4217 format)

    int32

    Count of purchased goods

    Field

    Type

    Description

    purchaseType

    FString

    Purchase type or group (max. 96 symbols)

    purchasePrice

    int32

    Cost of purchased goods (total cost -if several goods were purchased)

    purchaseCurrency

    FString

    Currency name (max. 24 symbols)

    TArray<FAnalyticsEventAttr>

    Location parameters

    Field

    Type

    Description

    locationName

    FString

    The name of location user left

    Attributes

    TArray<FAnalyticsEventAttr>

    Location parameters

    setUserId
    method.

    To get the current value of the user ID, use the asynchronous method (void)deviceIdHandler:( void (^ _Nonnull)(NSString * _Nonnull))completion-Handler;

    setUserId
    method.

    To get the current value of the user ID, use the asynchronous method getDeviceId(block: (String) -> Unit)

    setUserId
    method.

    To get the current value of the user ID, use the asynchronous method getDeviceId(block: (String) -> Unit)

    SetUserId
    method.

    To get the current value of the user ID, use the asynchronous method:

    SetUserId
    method.

    To get the current value of the user ID, use the asynchronous method:

    To get the current value of the user ID, use the getUserIdmethod
    Blueprint

    Arguments
    Type
    Description

    userId

    FString

    User ID.

    To get the current value of the user ID, use the asynchronous method:

    Blueprint

    Arguments
    Type
    Description

    onResult

    • FAnalyticsDynamicGetterStringDelegate

    • FDTDGetterStringDelegate

    Callback

    DTDAnalytics.GetUserId(
    onResult:
    Callable)
    Arguments
    Type
    Description

    fromUserId

    FString

    From user ID

    toUserId

    FString

    To user ID

    method when the user reaches a new level. In this case, you must use the
    method.

    To get the user-level value stored by the SDK, use getCurrentLevel(completionHandler: @escaping (Int) -> Void) method

    To set the current value to the user level, use (void)currentLevel:(NSInteger)currentLevel; method:

    We recommend that you use the setCurrentLevel method immediately after using the setUserID method or specify the current user level in the initialization configuration (the level property of an instance of the DTDAnalyticsConfiguration).

    Do not use the setCurrentLevel method when the user reaches a new level. In this case, you must use the method.

    To get the user-level value stored by the SDK, use (void)currentLevelHandler:( void (^ _Nonnull)(NSInteger))completionHandler; method

    To set the current value to the user level, use method:

    We recommend that you use the setCurrentLevel method immediately after using the setUserID method or specify the current user level in the initialization configuration (the level property of an instance of the DTDAnalyticsConfiguration).

    Do not use the setCurrentLevel method when the user reaches a new level. In this case, you must use the levelUp method.

    To get the user-level value stored by the SDK, use getCurrentLevel(completionHandler: @escaping (Int) -> Void) method

    To set the current value to the user level, use method:

    We recommend that you use the setCurrentLevel method immediately after using the setUserID method or specify the current user level in the initialization configuration (the level property of an instance of the DTDAnalyticsConfiguration).

    Do not use the setCurrentLevel method when the user reaches a new level. In this case, you must use the levelUp method.

    To get the user-level value stored by the SDK, use getCurrentLevel(completionHandler: @escaping (Int) -> Void) method

    To set the current value to the user level, use method:

    We recommend that you use the SetCurrentLevel method immediately after using the SetUserID method or specify the current user level in the initialization configuration (the level property of an instance of the DTDAnalyticsConfiguration).

    Do not use the SetCurrentLevel method when the user reaches a new level. In this case, you must use the LevelUp method.

    To get the user-level value stored by the SDK, use GetCurrentLevel method:

    To set the current value to the user level, use method:

    We recommend that you use the SetCurrentLevel method immediately after using the SetUserID method or specify the current user level in the initialization configuration (the level property of an instance of the DTDAnalyticsConfiguration).

    Do not use the SetCurrentLevel method when the user reaches a new level. In this case, you must use the LevelUp method.

    To get the user-level value stored by the SDK, use GetCurrentLevel method:

    To set the current value to the user level, use method:

    We recommend that you use the setCurrentLevel method immediately after using the setUserID method or specify the current user level in the initialization configuration (the level property of an instance of the DTDAnalyticsConfiguration).

    Do not use the setCurrentLevel method when the user reaches a new level. In this case, you must use the levelUp method.

    To get the user-level value stored by the SDK, use getCurrentLevel method.

    To set the current value to the user level, use method:

    Blueprint

    Arguments
    Type
    Description

    level

    int32

    Current level.

    We recommend that you use the SetCurrentLevel method immediately after using the method or specify the current user level in the initialization configuration (the level property of an instance of the EDTDAnalyticsConfiguration).

    Do not use the SetCurrentLevel method when the user reaches a new level. In this case, you must use the method.

    To get the user-level value stored by the SDK, use method:

    Arguments
    Type
    Description

    To set the current value to the user level, use SetCurrentLevel(level: int) method:

    We recommend that you use the SetCurrentLevel method immediately after using the SetUserID method or specify the current user level in the initialization configuration (the level property of an instance of the GDDTDAnalyticsConfiguration).

    Do not use the SetCurrentLevel method when the user reaches a new level. In this case, you must use the LevelUp method.

    To get the user-level value stored by the SDK, use GetCurrentLevel(onResult: Callable) method

    Description

    cheater

    bool

    Cheater flag

    Description

    tester

    bool

    Tester flag

    Email

    setEmail(email: String)

    getEmail(completionHandler: @escaping (String?) -> Void)

    Phone

    setPhone(phone: String)

    getPhone(completionHandler: @escaping (String?) -> Void)

    Photo

    setPhoto(photo: String)

    getPhoto(completionHandler: @escaping (String?) -> Void)

    Gender

    setGender(gender: DTDGender)

    getGender(completionHandler: @escaping (DTDGender) -> Void)

    Age

    setAge(age: Int)

    getAge(completionHandler: @escaping (Int) -> Void

    Attention! We strongly discourage the storage of personal user data! If you plan to pass this data, be sure to indicate this in the ‘Nutrition label’ when submitting the application to the App Store review.

    Property

    Setter

    Getter

    Age

    [DTDUserCard setAge:(NSInteger)];

    [DTDUserCard getAgeHandler:^(NSInteger age) { }]

    Property

    Getter

    Setter

    Name

    setName(name: String)

    getName(handler: (String) -> Unit)

    Email

    setEmail(email: String)

    getEmail(handler: (String) -> Unit)

    Phone

    setPhone(phone: String)

    getPhone(handler: (String) -> Unit)

    Property

    Getter

    Setter

    Name

    setName(name: String)

    getName(handler: (String) -> Unit)

    Email

    setEmail(email: String)

    getEmail(handler: (String) -> Unit)

    Phone

    setPhone(phone: String)

    Property

    Getter

    Setter

    Name

    SetName(name: string)

    Task<string> GetName();

    Email

    SetEmail(email: string)

    Task<string> GetEmail()

    Phone

    SetPhone(phone: string)

    Task<string> GetPhone()

    Attention! We strongly discourage the storage of personal user data! If you plan to pass this data, be sure to indicate this in the ‘Nutrition label’ when submitting the application to the App Store review.

    Property

    Getter

    Setter

    Name

    SetName(name: string)

    GetName(Action<string> onGetName)

    Property

    Getter

    Setter

    Type

    Name

    getName()

    setName("John Doe")

    String

    Email

    getEmail()

    setEmail("[email protected]")

    String

    Phone

    Attention! We strongly discourage the storage of personal user data! If you plan to pass this data, be sure to indicate this in the ‘Nutrition label’ when submitting the application to the App Store review.

    Property
    Setter
    Getter

    Name

    SetName(name: FString)

    Example:

    When using the getValue method, note that the Any return type will need to be cast to the data type you want.

    This is how you can set properties on the current user profile:

    It is important to remember that the key for custom user properties has a length limit. If the limit is exceeded (from 1 to 64 characters), the key will be truncated to the maximum allowed length

    To get the current value stored in the user profile on the SDK, you need to use the method:

    (void)getValueWithKey:(NSString * _Nonnull)key :(void (^ _Nonnull)(id _Nullable))completionHandler;

    When using the getValue method, note that the Anyreturn type will need to be cast to the data type you want.

    This is how you can set properties on the current user profile:

    It is important to remember that the key for custom user properties has a length limit. If the limit is exceeded (from 1 to 64 characters), the key will be truncated to the maximum allowed length

    To get the current value stored in the user profile on the SDK, you need to use the method:

    getValue(key: String, handler: (Any?) -> Unit)

    When using the getValue method, note that the Anyreturn type will need to be cast to the data type you want.

    This is how you can set properties on the current user profile:

    It is important to remember that the key for custom user properties has a length limit. If the limit is exceeded (from 1 to 64 characters), the key will be truncated to the maximum allowed length

    To get the current value stored in the user profile on the SDK, you need to use the method:

    getValue(key: String, handler: (Any?) -> Unit)

    When using the getValue method, note that the Anyreturn type will need to be cast to the data type you want.

    This is how you can set properties on the current user profile:

    It is important to remember that the key for custom user properties has a length limit. If the limit is exceeded (from 1 to 64 characters), the key will be truncated to the maximum allowed length

    To get the current value stored in the user profile on the SDK, you need to use the method:

    When using the Get method, note that the object return type will need to be cast to the data type you want.

    This is how you can set properties on the current user profile:

    It is important to remember that the key for custom user properties has a length limit. If the limit is exceeded (from 1 to 64 characters), the key will be truncated to the maximum allowed length

    To get the current value stored in the user profile on the SDK, you need to use the method:

    When using the Get method, note that the object return type will need to be cast to the data type you want.

    This is how you can set properties on the current user profile:

    It is important to remember that the key for custom user properties has a length limit. If the limit is exceeded (from 1 to 64 characters), the key will be truncated to the maximum allowed length

    To get the current value stored in the user profile on the SDK, you need to use the method:

    This is how you can set properties on the current user profile:

    Blueprint
    Arguments
    Type
    Description

    key

    FString

    Parameter key.

    value

    bool

    Parameter value.

    Arguments
    Type
    Description

    Arguments
    Type
    Description

    Arguments
    Type
    Description

    It is important to remember that the key for custom user properties has a length limit. If the limit is exceeded (from 1 to 64 characters), the key will be truncated to the maximum allowed length

    To get the current value stored in the user profile on the SDK, you need to use methods:

    Arguments
    Type
    Description

    Arguments
    Type
    Description

    Arguments
    Type
    Description

    Arguments
    Type
    Description

    This is how you can set properties on the current user profile:

    It is important to remember that the key for custom user properties has a length limit. If the limit is exceeded (from 1 to 64 characters), the key will be truncated to the maximum allowed length

    To get the current values stored in the user profile on the SDK, you need to use the methods:

    TryGetBool(key: String, callback: Callable)

    TryGetFloat(key: String, callback: Callable)

    TryGetInt(key: String, callback: Callable)

    TryGetString(key: String, callback: Callable

    The isValid parameter is responsible for the presence of the requested key in the user card.

    Arguments
    Type
    Description

    key

    FString

    Parameter key.

    Blueprint

    Arguments
    Type
    Description

    keys

    TArray<FString>

    Parameter keys.

    method.

    Keep in mind that the cheater mark is not cleared from the user card; you can only uncheck the mark manually using the setCheater method.

    Keep in mind that the cheater mark is not cleared from the user card; you can only uncheck the mark manually using the setCheater method.

    Keep in mind that the cheater mark is not cleared from the user card; you can only uncheck the mark manually using the SetCheater method.

    Keep in mind that the cheater mark is not cleared from the user card; you can only uncheck the mark manually using the SetCheater method.

    Keep in mind that the cheater mark is not cleared from the user card; you can only uncheck the mark manually using the setCheater method.

    Blueprint

    Keep in mind that the cheater mark is not cleared from the user card; you can only uncheck the mark manually using the SetCheater method.

    Keep in mind that the cheater mark is not cleared from the user card; you can only uncheck the mark manually using the setCheater method.

    Property

    Getter

    Setter

    Name

    setName(name: String)

    getName(completionHandler: @escaping (String?) -> Void)

    setUserID
    personal data
    personal data
    setCheater
    Blueprint
    Blueprint
    Blueprint
    Blueprint
    DTDAnalytics.setUserId(userId: "Custom User ID")
    DTDAnalytics.getUserId { userId in
      // your code
    }
    DTDAnalytics.replace(fromUserId: "Old user id", toUserId: "New user id")
    [DTDAnalytics replaceFromUserId:@"Old user id" toUserId:@"New user id"];
    DTDAnalytics.replaceUserId(
        fromUserId = "Old user id", 
        toUserId = "New user id"
    )
    DTDAnalytics.INSTANCE.replaceUserId("Old user id", "New user id");
    DTDAnalytics.Replace(fromUserId: "Old user id", toUserId: "New user id");
    DTDAnalytics.Replace(fromUserId: "Old user id", toUserId: "New user id");
    window.devtodev.replace( "Old user id", "New user id")
    levelUp
    setCheater
    [DTDAnalytics userId:@"Custom User ID"];
    [DTDAnalytics userIdHandler:^(NSString * _Nonnull userId) {
      // your code
    }];
    DTDAnalytics.setUserId(userId = "Custom User ID")
    DTDAnalytics.getUserId { userId ->
      // your code
    }
    // Register transactions made through the platform's payment system.
    // FString transactionId - transaction ID
    // float inAppPrice - product price (in user's currency)
    // FString inAppName - product name
    // FString inAppCurrencyISOCode - transaction currency (ISO 4217 format)
    
    UDevToDevBlueprintFunctionLibrary::RealPayment(const FString& transactionId,
                                                   float inAppPrice,
                                                   const FString& inAppName,
                                                   const FString& inAppCurrencyISOCode);
    // In-app purchase with a definite article ID.
    // FString ItemId - unique purchase Id or name (max. 32 symbols)
    // int32 ItemQuantity - count of purchased goods
    //
    // FString purchaseType - purchase type or group (max. 96 symbols)
    // int32 purchasePrice - cost of purchased goods (total cost -if several goods were purchased)
    // FString purchaseCurrency - currency name (max. 24 symbols)
    
    FAnalytics::Get().GetDefaultConfiguredProvider()->RecordItemPurchase(const FString& ItemId,
                                                       int ItemQuantity,
                                                       const TArray<FAnalyticsEventAttribute>& Attributes);
    // Player comes to the third location on the map "Village" while following the game map. 
    // Create a parameters object
    LocationEventParams params = new LocationEventParams();
    
    // Specify the known location parameters:
    // Passing on the first level of difficulty.
    params.setDifficulty(1);
    
    // Before entering this location gamer passed the third location on the map “Village” (optional).
    params.setSource("Vilage step 02");
    
    //The location passing starts (required).
    DevToDev.startProgressionEvent("Vilage step 03", params);
    
    // ... Player passing the location.
    
    // Player finishes passing of the third location on the map “Village”
    
    // The location is passed successfully (required).
    params.setSuccessfulCompletion(true);
    
    // The passing took 189 seconds.
    params.setDuration(189);
    
    // Location is passed for 54 turns. While the passing gamer used boost and bought extra 5 turns.
    HashMap<String, Number> spent = new HashMap<String, Number>();
    spent.put("Turns", 54);
    spent.put("Boost Bomb", 1);
    spent.put("Extra 5 Turns", 1);
    params.setSpent(spent);
    
    // Gamer finished the passing with 3 stars and gained 5 coins and 1200 score.
    HashMap<String, Number> earned = new HashMap<String, Number>();
    earned.put("Stars", 3);
    earned.put("Score", 1200 );
    earned.put("Coins", 5);
    params.setEarned(earned); 
    
    // The location passing is over (required).
    DevToDev.endProgressionEvent("Vilage step 03", params);
    // Player comes to the third location on the map "Village" while following the game map. 
    // Create a parameters object
    LocationEventParams locationParams = new LocationEventParams();
    
    // Specify the known location parameters:
    // Passing on the first level of difficulty.
    locationParams.SetDifficulty(1);
    
    // Before entering this location gamer passed the third location on the map “Village” (optional).
    locationParams.SetSource("Vilage step 02");
    
    //The location passing starts (required).
    DevToDev.SDK.StartProgressionEvent("Vilage step 03", locationParams);
    
    // ... Player passing the location.
    
    // Player finishes passing of the third location on the map “Village”
    LocationEventParams locationParams = new LocationEventParams();
    
    // The location is passed successfully (required).
    locationParams.SetSuccessfulCompletion(true);
    
    // The passing took 189 seconds.
    locationParams.SetDuration(189);
    
    // Location is passed for 54 turns. While the passing gamer used boost and bought extra 5 turns.
    Dictionary<string, int> spent = new Dictionary<string, int>();
    spent["Turns"] = 54;
    spent["Boost Bomb"] = 1;
    spent["Extra 5 Turns"] = 1;
    locationParams.SetSpent(spent);
    
    // Gamer finished the passing with 3 stars and gained 5 coins and 1200 score.
    Dictionary<string, int> earned = new Dictionary<string, int>();
    earned["Stars"] = 3;
    earned["Score"] = 1200;
    earned["Coins"] = 5;
    locationParams.SetEarned(earned); 
    
    // The location passing is over (required).
    DevToDev.SDK.EndProgressionEvent("Vilage step 03", locationParams);
    // Player comes to the third location on the map "Village" while following the game map. 
    // Create a parameters object
    var params = {};
    
    // Specify the known location parameters:
    // Passing on the first level of difficulty.
    params["difficulty"] = 1;
    
    // Before entering this location gamer passed the third location on the map “Village” (optional).
    params["source"] = "Vilage step 02";
    
    //The location passing starts (required).
    devtodev.startProgressionEvent("Vilage step 03", params);
    
    // ... Player passing the location.
    
    // Player finishes passing of the third location on the map “Village”
    
    // The location is passed successfully (required).
    params["success"] = true;
    
    // The passing took 189 seconds.
    params["duration"] = 189;
    
    // Location is passed for 54 turns. While the passing gamer used boost and bought extra 5 turns.
    params["spent"] = [
        {
            "currency": "Turns",
            "amount": 54
        },
        {
            "currency": "Boost Bomb",
            "amount": 1
        },
        {
            "currency": "Extra 5 Turns",
            "amount": 1
        }
    ];
    
    // Gamer finished the passing with 3 stars and gained 5 coins and 1200 score.
    params["earned"] = [
        {
            "currency": "Stars",
            "amount": 3
        },
        {
            "currency": "Score",
            "amount": 1200
        },
        {
            "currency": "Coins",
            "amount": 5
        }
    ];
    
    // The location passing is over (required).
    devtodev.endProgressionEvent("Vilage step 03", params);
    // Player comes to the third location on the map "Village" while following the game map. 
    // Create a parameters object
    ProgressionEventParams locationParams = new ProgressionEventParams();
    
    // Specify the known location parameters:
    // Passing on the first level of difficulty.
    locationParams.SetDifficulty(1);
    
    // Before entering this location gamer passed the third location on the map “Village” (optional).
    locationParams.SetSource("Vilage step 02");
    
    //The location passing starts (required).
    DevToDev.Analytics.StartProgressionEvent("Vilage step 03", locationParams);
    
    // ... Player passing the location.
    
    // Player finishes passing of the third location on the map “Village”
    // Create a parameters object
    ProgressionEventParams locationParams = new ProgressionEventParams();
    
    // The location is passed successfully (required).
    locationParams.SetSuccessfulCompletion(true);
    
    // The passing took 189 seconds.
    locationParams.SetDuration(189);
    
    // Location is passed for 54 turns. While the passing gamer used boost and bought extra 5 turns.
    Dictionary<string, int> spent = new Dictionary<string, int>();
    spent["Turns"] = 54;
    spent["Boost Bomb"] = 1;
    spent["Extra 5 Turns"] = 1;
    locationParams.SetSpent(spent);
    
    // Gamer finished the passing with 3 stars and gained 5 coins and 1200 score.
    Dictionary<string, int> earned = new Dictionary<string, int>();
    earned["Stars"] = 3;
    earned["Score"] = 1200;
    earned["Coins"] = 5;
    locationParams.SetEarned(earned); 
    
    // The location passing is over (required).
    DevToDev.Analytics.EndProgressionEvent("Vilage step 03", locationParams);
    // Player comes to the third location on the map "Village" while following the game map. 
    // Create a parameters object
    LocationEventParams* params = [[LocationEventParams alloc] init];
    
    // Specify the known location parameters:
    // Passing on the first level of difficulty.
    [params setDifficulty:1];
    
    // Before entering this location gamer passed the third location on the map “Village” (optional).
    [params setSource: @"Vilage step 02"];
    
    //The location passing starts (required).
    [DevToDev startProgressionEvent:@"Vilage step 03" withParameters:params];
    
    // ... Player passing the location.
    
    // Player finishes passing of the third location on the map “Village”
    
    // The location is passed successfully (required).
    [params setIsSuccess: YES];
    
    // The passing took 189 seconds.
    [params setDuration:@189];
    
    // Location is passed for 54 turns. While the passing gamer used boost and bought extra 5 turns.
    NSDictionary* spent = @{
        @"Turns" : @54,
        @"Boost Bomb" : @1,
        @"Extra 5 Turns" : @1
    };
    [params setSpent: spent];
    
    // Gamer finished the passing with 3 stars and gained 5 coins and 1200 score.
    NSDictionary* earned = @{
        @"Stars" : @3,
        @"Score" : @1200,
        @"Coins" : @5
    };
    [params setEarned: earned]; 
    
    // The location passing is over (required).
    [DevToDev endProgressionEvent: @"Vilage step 03" withParameters: params];
    // Player comes to the third location on the map "Village" while following the game map. 
    // Create a parameters object
    var params:LocationEventParams = new LocationEventParams();
    
    // Specify the known location parameters:
    // Passing on the first level of difficulty.
    params.SetDifficulty(1);
    
    // Before entering this location gamer passed the third location on the map “Village” (optional).
    params.SetSource("Vilage step 02");
    
    //The location passing starts (required).
    DevToDev.StartProgressionEvent("Vilage step 03", params);
    
    // ... Player passing the location.
    
    // Player finishes passing of the third location on the map “Village”
    var params:LocationEventParams = new LocationEventParams();
    
    // The location is passed successfully (required).
    params.SetSuccessfulCompletion(true);
    
    // The passing took 189 seconds.
    params.SetDuration(189);
    
    // Location is passed for 54 turns. While the passing gamer used boost and bought extra 5 turns.
    var spent:Dictionary = new Dictionary();
    spent["Turns"] = 54;
    spent["Boost Bomb"] = 1;
    spent["Extra 5 Turns"] = 1;
    params.SetSpent(spent);
    
    // Gamer finished the passing with 3 stars and gained 5 coins and 1200 score.
    var earned:Dictionary = new Dictionary();
    earned["Stars"] = 3;
    earned["Score"] = 1200;
    earned["Coins"] = 5;
    params.SetEarned(earned); 
    
    // The location passing is over (required).
    DevToDev.EndProgressionEvent("Vilage step 03", params);
    // The method have to be used when entering the location.
    // FString locationName  - the name of location user entered.
    // TArray<FAnalyticsEventAttr> Attributes - location parameters
    UDevToDevBlueprintFunctionLibrary::StartProgressionEvent(const FString& locationName,
                                                             const TArray<FAnalyticsEventAttr>& Attributes);
    // The method have to be used when the location passing is over.
    // FString locationName  - the name of location user left.
    // TArray<FAnalyticsEventAttr> Attributes - location parameters
    // TArray<FAnalyticsEventAttr> Earned - user earnings within the location passing (optional)
    // TArray<FAnalyticsEventAttr> Spent - user spendings within the location passing (optional).
    UDevToDevBlueprintFunctionLibrary::EndProgressionEvent(const FString& locationName,
                                                           const TArray<FAnalyticsEventAttr>& Attributes,
                                                           const TArray<FAnalyticsEventAttr>& Earned,
                                                           const TArray<FAnalyticsEventAttr>& Spent);
    DTDAnalytics.ReplaceUserId("Old user id", "New user id")
    [DTDAnalytics currentLevel:2];
    DTDAnalytics.setCurrentLevel(currentLevel = 2)
    DTDAnalytics.INSTANCE.setCurrentLevel(2);
    DTDAnalytics.SetCurrentLevel(level: 3);
    DTDAnalytics.SetCurrentLevel(level: 3);
    window.devtodev.setCurrentLevel(2)
    UDTDAnalyticsBPLibrary::SetCurrentLevel(7);
    DTDAnalytics.SetCurrentLevel(2)
    DTDUserCard.SetCheater(true)
    DTDUserCard.SetTester(true)
    [DTDUserCard setString:@"key for string value" value:@"string value"];
    [DTDUserCard setInt:@"key for int value" value:10];
    [DTDUserCard setDouble:@"key for double value" value:12.5];
    [DTDUserCard setBool:@"key for bool value" value:true];
    [DTDUserCard getValueWithKey:@"key for value" :^(id object) {
      // your code
    }];
    DTDUserCard.set(key = "key for string value", value = "string value")
    DTDUserCard.set(key = "key for int value", value = 10)
    DTDUserCard.set(key = "key for double value", value = 12.5)
    DTDUserCard.set(key = "key for bool value", value = true)
    DTDUserCard.getValue(key = "key for value") { value ->
      // your code
    }
    DTDUserCard.INSTANCE.set("key for string value", "string value");
    DTDUserCard.INSTANCE.set("key for int value", 10);
    DTDUserCard.INSTANCE.set("key for double value", 12.5);
    DTDUserCard.INSTANCE.set("key for bool value", true);
    DTDUserCard.INSTANCE.getValue("key for value", value ->
           // your code
           null
    );j
    DTDUserCard.Set(key: "key for string value", value: "string value");
    DTDUserCard.Set(key: "key for int value", value: 10);
    DTDUserCard.Set(key: "key for double value", value: 12.5);
    DTDUserCard.Set(key: "key for bool value", value: true);
    var value = await DTDUserCard.Get(key: "key for value");
    switch (value)
    {
        case bool boolValue:
            break;
        case long longValue:
            break;
        case double doubleValue:
            break;
        case string stringValue:
            break;
    }
    DTDUserCard.Set(key: "key for string value", value: "string value");
    DTDUserCard.Set(key: "key for int value", value: 10);
    DTDUserCard.Set(key: "key for double value", value: 12.5);
    DTDUserCard.Set(key: "key for bool value", value: true);
    DTDUserCard.GetValue("key", value =>
    {
        switch (value)
        {
        case bool boolValue:
            break;
        case long longValue:
            break;
        case double doubleValue:
            break;
        case string stringValue:
            break;
        }             
    })
    DTDUserCard.Get(key: "key for value");
    window.devtodev.user.set("key for string value",  "string value")
    window.devtodev.user.set("key for int value", 10)
    window.devtodev.user.set("key for double value", 12.5)
    window.devtodev.user.set("key for bool value", true)
    window.devtodev.user.getValue("key for value") 
    UDTDUserCardBPLibrary::SetBool("BoolKey", true);
    DTDUserCard.SetString("key for string value", "string value")
    DTDUserCard.SetInt("key for int value", 10)
    DTDUserCard.SetFloat("key for double value", 12.5)
    DTDUserCard.SetBool("key for bool value", true)
    DTDUserCard.Unset("intKey")
    
    var arrayOfKeys = ["floatKey", "boolKey"]
    DTDUserCard.UnsetArray(arrayOfKeys)
    DTDUserCard.clearUser()
    DTDUserCard.INSTANCE.clearUser();
    DTDUserCard.ClearUser();
    DTDUserCard.ClearUser();
    window.devtodev.user.clearUser()
    UDTDUserCardBPLibrary::ClearUser();
    DTDUserCard.ClearUser()
    window.devtodev.setUserId("Custom User ID")
    DTDAnalytics.SetUserId("Custom User ID")
    DTDAnalytics.setCurrentLevel(value: 2)
    DTDUserCard.setCheater(cheater: true)
    [DTDUserCard setCheater:true];
    DTDUserCard.setCheater(cheater = true)
    DTDUserCard.INSTANCE.setCheater(true);
    DTDUserCard.SetCheater(cheater: true);
    DTDUserCard.SetCheater(cheater: true);
    window.devtodev.user.setCheater(true)
    DTDUserCard.setTester(tester: true)
    [DTDUserCard setTester:true];
    DTDUserCard.setTester(tester = true)
    DTDUserCard.INSTANCE.setTester(true);
    DTDUserCard.SetTester(tester: true);
    DTDUserCard.SetTester(tester: true);
    window.devtodev.user.setTester(true)
    DTDUserCard.set(key: "key for string value", value: "string value")
    DTDUserCard.set(key: "key for int value", value: 10)
    DTDUserCard.set(key: "key for double value", value: 12.5)
    DTDUserCard.set(key: "key for bool value", value: true)
    DTDUserCard.getValue(key: "key for value") { value in
      // your code
    }
    DTDUserCard.unset(property: "key for string value")
    DTDUserCard.unset(properties: ["key for string value", 
                                   "key for int value"])
    [DTDUserCard unsetProperty:@"key for string value"];
    [DTDUserCard unset:@[@"key for string value",
                         @"key for int value"]];
    DTDUserCard.unset(property = "key for string value")
    DTDUserCard.unset(property = listOf("key for string value", "key for int value"))
    DTDUserCard.INSTANCE.unset( "key for string value");
    
    ArrayList<String> properties = new ArrayList<>();
    properties.add("key for string value");
    properties.add("key for int value");
    DTDUserCard.INSTANCE.unset(properties);
    DTDUserCard.Unset(keys: "key 1");
    DTDUserCard.Unset(keys: "key 1", "key 2", "key 3");
    DTDUserCard.Unset(keys: "key 1");
    DTDUserCard.Unset(keys: "key 1", "key 2", "key 3");
    window.devtodev.user.unset("key for string value")
    window.devtodev.user.unset(["key for string value", "key for int value"])
    DTDUserCard.clearUser()
    [DTDUserCard clearUser];
    DTDAnalytics.INSTANCE.setUserId("Custom User ID");
    DTDAnalytics.INSTANCE.getUserId(userID ->
          // your code
          null
    );
    DTDAnalytics.SetUserId(userId: "Custom User ID")
    var userId = await DTDAnalytics.GetUserId();
    DTDAnalytics.SetUserId(userId: "Custom User ID")
    DTDAnalytics.GetUserId(id =>
    {
      //your code
    });
    var userId = window.devtodev.getUserId()
    UDTDAnalyticsBPLibrary::SetUserId("Name");
    auto onResult = new FDTDGetterStringDelegate();
    onResult->BindLambda([](const FString& value)
    {
    	// Your code...
    });
    UDTDAnalyticsBPLibrary::GetUserId(*onResult);
    DTDAnalytics.GetUserId(getUserIdHandler)
    
    func getUserIdHandler(userId: String): 
      print(userId)
    UDTDAnalyticsBPLibrary::ReplaceUserId("UserIdBefore", "UserIdAfter");
    DTDAnalytics.getCurrentLevel { level in
      // your code
    }
    UDTDUserCardBPLibrary::SetCheater(true);
    UDTDUserCardBPLibrary::SetTester(true);
    UDTDUserCardBPLibrary::Unset("Key");
    TArray<FString> keys;
    keys.Add("Key1");
    keys.Add("Key2");
    UDTDUserCardBPLibrary::UnsetArray(keys);

    int32

    Time spent in the location (optional). In case the parameter is not specified by the developer, it will be automatically сalculated as the date difference between Start Progression Event and End Progression Event method calls.

    onResult

    • FAnalyticsDynamicGetterIntDelegate

    • FDTDGetterIntDelegate

    Callback

    Email

    [DTDUserCard setEmail:(NSString * _Nonnull)];

    [DTDUserCard getEmailHandler:^(NSString * email) { }];

    Gender

    [DTDUserCard setGender:(enum Gender)];

    [DTDUserCard getGenderHandler:^(enum Gender gender) { }];

    Name

    [DTDUserCard setName:(NSString * _Nonnull)];

    [DTDUserCard getNameHandler:^(NSString * name) { }];

    Phone

    [DTDUserCard setPhone:(NSString * _Nonnull)];

    [DTDUserCard getPhoneHandler:^(NSString * phone) { }];

    Photo

    [DTDUserCard setPhoto:(NSString * _Nonnull)];

    [DTDUserCard getPhotoHandler:^(NSString * photo) { }];

    Photo

    setPhoto(photo: String)

    getPhoto(handler: (String) -> Unit)

    Gender

    setGender(gender: DTDGender)

    getGender(handler: (DTDGender) -> Void)

    Age

    setAge(age: Int)

    getAge(handler: (String) -> Unit)

    getPhone(handler: (String) -> Unit)

    Photo

    setPhoto(photo: String)

    getPhoto(handler: (String) -> Unit)

    Gender

    setGender(gender: DTDGender)

    getGender(handler: (DTDGender) -> Void)

    Age

    setAge(age: Int)

    getAge(handler: (String) -> Unit)

    Photo

    SetPhoto(photo: string)

    Task<string> GetPhoto()

    Gender

    SetGender(gender: DTDGender)

    Task<DTDGender> GetGender()

    Age

    SetAge(age: long)

    Task<long> GetAge()

    Email

    SetEmail(email: string)

    GetEmail(Action<string> onGetEmail)

    Phone

    SetPhone(phone: string)

    GetPhone(Action<string> onGetPhone)

    Photo

    SetPhoto(photo: string)

    GetPhoto(Action<string> onGetPhoto)

    Gender

    SetGender(gender: DTDGender)

    GetGender(Action<DTDGender> onGetGender)

    Age

    SetAge(age: long)

    GetAge(Action<string> onGetAge)

    getPhone()

    setPhone("+15555555555")

    String

    Photo

    getPhoto()

    setPhoto("https://domain.com/photo.jpg")

    String

    Gender

    getGender()

    setGender(gender)

    Age

    getAge()

    setAge(age)

    Int

    • GetName(delegate: FUserCardDynamicGetterStringDelegate)

    • GetName(delegate: FDTDGetterStringDelegate)

    Email

    SetEmail(email: FString)

    • GetEmail(delegate: FUserCardDynamicGetterStringDelegate)

    • GetEmail(delegate: FDTDGetterStringDelegate)

    Phone

    SetPhone(phone: FString)

    • GetPhone(delegate: FUserCardDynamicGetterStringDelegate)

    • GetPhone(delegate: FDTDGetterStringDelegate)

    Photo

    SetPhoto(photo: FString)

    • GetPhoto(delegate: FUserCardDynamicGetterStringDelegate)

    • GetPhoto(delegate: FDTDGetterStringDelegate)

    Gender

    SetGender(gender: EDTDGender)

    • GetGender(delegate: FUserCardDynamicGetterGenderDelegate)

    • GetGender(delegate: FDTDGetterGenderDelegate)

    Age

    SetAge(age: int64)

    • GetAge(delegate: FUserCardDynamicGetterLongDelegate)

    • GetAge(delegate: FDTDGetterLongDelegate)

    key

    FString

    Parameter key.

    value

    float

    Parameter value.

    key

    FString

    Parameter key.

    value

    int64

    Parameter value.

    key

    FString

    Parameter key.

    value

    FString

    Parameter value.

    key

    FString

    Parameter key.

    onResult

    • FUserCardDynamicGetterOptionalBoolDelegate

    • FDTDGetterOptionalBoolWithKeyDelegate

    Callback.

    key

    FString

    Parameter key.

    onResult

    • FUserCardDynamicGetterOptionalFloatDelegate

    • FDTDGetterOptionalFloatWithKeyDelegate

    Callback.

    key

    FString

    Parameter key.

    onResult

    • FUserCardDynamicGetterOptionalLongDelegate

    • FDTDGetterOptionalLongWithKeyDelegate

    Callback.

    key

    FString

    Parameter key.

    onResult

    • FUserCardDynamicGetterOptionalStringDelegate

    • FDTDGetterOptionalStringWithKeyDelegate

    Callback.

    levelUp
    SetUserID
    LevelUp
    Blueprint
    Blueprint
    Blueprint
    Blueprint
    Blueprint
    Blueprint
    Blueprint
    Blueprint
    Blueprint
    Blueprint
    [DTDAnalytics currentLevelHandler:^(NSInteger level) {
      // your code
    }];
    DTDAnalytics.getCurrentLevel { level ->
      // your code
    }
    DTDAnalytics.INSTANCE.getCurrentLevel ( currentLevel ->
           // your code 
           null
    );
    var currentLevel = await DTDAnalytics.GetCurrentLevel();
    DTDAnalytics.GetCurrentLevel(level =>
    {
      //your code
    });
    var currentLevel = window.devtodev.getCurrentLevel()
    auto onResult = new FDTDGetterIntDelegate();
    onResult->BindLambda([](int32 value)
    {
    	// Your code...
    });
    UDTDAnalyticsBPLibrary::GetCurrentLevel(*onResult);
    DTDAnalytics.GetCurrentLevel(getCurrentLevelHandler)
    
    func getCurrentLevelHandler(level: int):
        print("result is: " + str(level))
    UDTDUserCardBPLibrary::SetEmail("Email");
    auto onResult = new FDTDGetterStringDelegate();
    onResult->BindLambda([](const FString& value)
    {
    	// Your code...
    });
    UDTDUserCardBPLibrary::GetEmail(*onResult);
    UUDTDUserCardBPLibrary::SetFloat("FloatKey", 3.333);
    UDTDUserCardBPLibrary::SetLong("LongKey", 1000);
    UDTDUserCardBPLibrary::SetString("StringKey", "StringValue");
    FString key = FString(TEXT("Key"));
    auto onResult = new FDTDGetterOptionalBoolWithKeyDelegate();
    onResult->BindLambda([](bool success, const FString& key, bool value)
    {
    	// Your code...
    });
    UDTDUserCardBPLibrary::TryGetBool(key, *onResult);
    FString key = FString(TEXT("Key"));
    auto onResult = new FDTDGetterOptionalFloatWithKeyDelegate();
    onResult->BindLambda([](bool success, const FString& key, float value)
    {
    	// Your code...
    });
    UDTDUserCardBPLibrary::TryGetFloat(key, *onResult);
    FString key = FString(TEXT("Key"));
    auto onResult = new FDTDGetterOptionalIntWithKeyDelegate();
    onResult->BindLambda([](bool success, const FString& key, int64 value)
    {
    	// Your code...
    });
    UDTDUserCardBPLibrary::TryGetLong(key, *onResult);
    FString key = FString(TEXT("Key"));
    auto onResult = new FDTDGetterOptionalStringWithKeyDelegate();
    onResult->BindLambda([](bool success, const FString& key, const FString& value)
    {
    	// Your code...
    });
    UDTDUserCardBPLibrary::TryGetString(key, *onResult);
    DTDUserCard.TryGetInt("intKey", getIntHandler)
    DTDUserCard.TryGetString("intKey", getStringHandler)
    DTDUserCard.TryGetFloat("intKey", getFloatHandler)
    DTDUserCard.TryGetBool("intKey", getBoolHandler)
    
    func getIntHandler(isValid: bool, value: int):
    	print("isValid = " + str(isValid) + " integer = " + str(value))
    
    func getStringHandler(isValid: bool, value: String):
    	print("isValid = " + str(isValid) + " string = " + value)
    	
    func getFloatHandler(isValid: bool, value: float):
    	print("isValid = " + str(isValid) + " float = " + str(value))
    	
    func getBoolHandler(isValid: bool, value: bool):
    	print("isValid = " + str(isValid) + " bool = " + str(value))

    If you need to exclude transactions from such users from statistics, go to Users & Segments section in devtodev interface and mark the user manually. When you mark the user in devtodev interface, the system removes their transactions for the last 7 days the reports and recalculates the metrics.

    If you need to exclude transactions from such users from statistics, go to Users & Segments section in devtodev interface and mark the user manually. When you mark the user in devtodev interface, the system removes their transactions for the last 7 days the reports and recalculates the metrics.

    Description of A/B testing on the SDK side

    Step 1

    Some operations can take a while because the SDK operates asynchronously. These operations are:

    A/B test configuration.

    After you’ve created an A/B test in the web interface, the SDK will receive its configuration via the network. Use the remoteConfigWaiting property in the DTDRemoteConfig class to set the maximum time allowed for waiting for the A/B test configuration.

    remoteConfigWaitingis set in seconds

    By default, the remoteConfigWaiting value is equal to null. This means that the test configuration waiting time is unlimited. The limitation is important in case the test that you are about to execute can influence your app functioning.

    Example: DTDRemoteConfig.remoteConfigWaiting = 10

    In this case, the SDK will wait for the A/B test configuration for 10 seconds. If it won’t receive the config during the allocated time, it will notify the developer by using the onReceived(result: DTDRemoteConfigReceiveResult) method with Failure value and disable the A/B testing until the next SDK initialization.

    Waiting for an A/B test group.

    After the SDK finds a test, it will wait for a suitable group to include the user into and start the experiment.

    By default, waiting time (groupDefinitionWaiting) is 10 seconds.

    You can change this value up or down.

    Example: DTDRemoteConfig.groupDefinitionWaiting = 15

    In this case, the SDK will wait for a group for no more than 15 seconds. If It can't find a suitable test for 15 seconds, it will be cancelled and its activation will be impossible. After the next SDK initialization (app restart), the user will have another chance to participate in the test.

    The DTDRemoteConfig.remoteConfigWaiting and DTDRemoteConfig.groupDefinitionWaitingvalues can be set only prior to SDK initialization!

    Step 2

    In the DTDRemoteConfig class you need to set the default variables and their values using the DTDRemoteConfig.defaults property.

    The variable values in the defaults property do not change on the SDK side.

    After setting up DTDRemoteConfig.defaults, you can receive the variable values by using the DTDRemoteConfig.config property.

    When working with A/B tests, always use the config property to receive the actual variables and their values!

    If for some reasons, the device can not be included in the test (no network or network problems, devtodev did not assign it to a group, incorrect SDK implementation, etc.) DTDRemoteConfig.config will return default values. Using this approach, you can always shape the desirable UI and the business logic of the app.

    Step 3

    To work with A/B testing, use the DTDAnalytics.initializeWithAbTest initialization method.

    If you used DTDAnalytics.initialize previously, you need to replace it with DTDAnalytics.initializeWithAbTest as the SDK initialization method.

    The DTDRemoteConfigListener interface is added to the initializeWithAbTest method. It implements three methods:

    Step 4

    Methods implementation for working with A/B tests. When suitable conditions for the test occur, the following chain of events gets initiated:

    • Calling onReceived method

    • Calling onPrepareToChange method

    • Calling onChanged method

    OnReceived

    The onReceived method is triggered every time when the SDK loads A/B test configuration. The result argument returns the following:

    • Success if the configuration was loaded in the time provided. Time is set by the user in DTDRemoteConfig.remoteConfigWaiting.

    • Failure if the config wasn’t received during the provided time defined by the user in DTDRemoteConfig.remoteConfigWaiting.

    • Empty if the configuration was loaded in the time provided, but the list of experiments is empty. Time is set by the user in

    If the DTDRemoteConfig.remoteConfigWaiting’s default value is equal to null, the OnReceived method and DTDRemoteConfigReceiveResult.Success are called when the config is received. The time of the OnReceived call depends on the network speed.

    OnPrepareToChange

    The onPrepareToChange() method is designed to notify the developer that the configuration of the A/B tests will be changed (the test is found or canceled, switch to a user who is not participating in the experiment, etc. (see Some features in the behavior of interfaces)).

    SDK asynchronous behavior causes a gap in time between the moment the suitable conditions for the test occur and its possible activation. You need to keep this in mind when changing the app’s UI and UX and pause the user’s interaction with the app until it comes into action:

    • onChanged (see onChanged description) and a decision on test participation with a new config is made (see applyConfig description).

    • A timer on the developer’s side. The timer defines the time allocated for new config waiting. After the time has run out, waiting for the config should be canceled.

    See the implementation example (Scenario 1)

    After calling onPrepareToChange, the SDK executes an asynchronous request for entering the test. That will result in calling onChanged.

    In case of disruption of the Internet connection or loss of focus, the test will be considered canceled until the next app launch.

    onChanged

    The onChanged method is triggered in one of the following cases:

    • The DevToDev server offers to enroll in a test. onChanged() with DTDRemoteConfigChangeResult.success is called

    • The DevToDev server refuses to execute the test. onChanged() with DTDRemoteConfigChangeResult.failure is called

    If the onChanged method is triggered with status == DTDRemoteConfigChangeResult.success value, the possible options are:

    • Accept the configuration by calling the DTDRemoteConfig.applyConfig() method If the configuration is accepted, the default parameters and group parameters (issued by the server) will overlap. After that, the parameters of the new configuration will be available in DTDRemoteConfig.config (you can use them to change the behavior or the interface of the app).

    • Refuse enrollment in the test DTDRemoteConfig.resetConfig() If the test enrollment is refused or the time for decision making is up, the SDK will temporarily flag the test and it will become unavailable until the next initialization. After the next initialization, the test will be available for participation when its requirements are fulfilled.

    In the cases where status == DTDRemoteConfigChangeResult.failure, the DTDRemoteConfig.applyConfig() method will be triggered and the default configuration will stay in place.

    Examples of SDK integration intended for working with A/B tests

    Set the default values DTDRemoteConfig.defaults = ["button_color": "green", "button_size": "8"]

    Variable names have to be the same as in the ‘Group settings’ section of the web interface (see ‘web’ above). When the SDK gets included in the A/B test, the devtodev server automatically assigns it to a group.

    SDK will not use new variable values that are not set in DTDRemoteConfig.defaults

    Example of the DTDRemoteConfig.configvalue variants:

    Some features of A/B testing

    When the SDK finds a suitable test(s), it calls the onPrepareToChange method and reports it. Then the SDK starts a timer for 30 seconds and sends a request to the devtodev server. The server returns an experiment number and information about a group. Or the server answers that the user can not participate in the test. This process takes some time. The request and answer can take as little as milliseconds, or much longer (30 seconds maximum) if, for example, the device’s internet connection is bad. Below, you can find several use cases that may help you to solve this problem if your app’s interface is sensitive to waiting for the config from devtodev.

    Description of external interfaces

    DTDRemoteConfig

    When working with this class, the SDK provides synchronization of threads that aims at providing safe operation taking into account the asynchrony of the SDK.

    Property
    Description

    DTDRemoteConfigCollection

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

    Method
    Method

    DTDRemoteConfigValue

    Wrapper for working with remote configuration variables. It represents a method for data source identification, as well as methods for presenting values in the form of various data types.

    type
    Description
    Property
    type
    Description

    DTDRemoteConfigListener

    Not applied to Web SDK

    It implements methods that report the status of A/B tests.

    Method
    Description

    DTDRemoteConfigReceiveResult

    It is triggered every time the SDK loads the configuration of A/B tests.

    type
    Description

    DTDRemoteConfigChangeResult

    Notifies whether the configuration of the A/B test has been changed.

    type
    Description

    Data options returned by the configChanged method signature

    DTDRemoteConfigChangeResult
    Exception message
    Description

    Raw Export

    devtodev RAW export API

    To use RAW data export API you need to have an individual User API token, which can be found in the settings of space.

    You’ll see the block with User API token on the space settings page only in case if your tariff plan and access rights allow to use devtodev API. You can reset User API token or create it again on the same page.

    If some applications of the space are inaccessible to the owner of User API token, then the owner can’t get access to the export of data of these applications. But the access to specific metrics it not extended to the access of raw data.

    Data API 2.0

    Server API integration manual

    If, for some reason, you cannot use the devtodev SDK, or some events cannot be tracked on the client side, you can use the devtodev data API. Using the API, you can send a set of events that almost completely replicates the capabilities of the SDK. However, you will need to independently prepare data about the device/user and also independently track the start and duration of sessions.

    Request format

    • onReceived(result: DTDRemoteConfigReceiveResult)

    • onPrepareToChange()

    • onChanged(result: DTDRemoteConfigChangeResult, error: Error?)

    The DTDRemoteConfigListenermethods are not called in the main thread - you need to keep this in mind when interacting with UI elements of the app. Also, do not execute operations that block the thread in the DTDRemoteConfigListener methods, because this will disable the SDK operation (see examples).

    To work with A/B testing, use the [DTDAnalytics applicationKey:* abConfigListener:*] initialization method.

    If you used [DTDAnalytics applicationKey:*] previously, you need to replace it with [DTDAnalytics applicationKey:* abConfigListener:*] as the SDK initialization method.

    The DTDRemoteConfigListener interface is added to the [DTDAnalytics applicationKey:* abConfigListener:*] method. It implements three methods:

    • -(void)onReceivedResult:(enum DTDRemoteConfigReceiveResult)result

    • -(void)onPrepareToChange

    • -(void)onChangedResult:(enum DTDRemoteConfigChangeResult)result error:(NSError *)error

    The DTDRemoteConfigListenermethods are not called in the main thread - you need to keep this in mind when interacting with UI elements of the app. Also, do not execute operations that block the thread in the DTDRemoteConfigListener methods, because this will disable the SDK operation (see examples).

    To work with A/B testing, use the DTDAnalytics.initializeWithAbTest initialization method.

    If you used DTDAnalytics.initialize previously, you need to replace it with DTDAnalytics.initializeWithAbTest as the SDK initialization method.

    The IRemoteConfigListener interface is added to the initializeWithAbTest method. It implements three methods:

    • onReceived(result: DTDRemoteConfigReceiveResult)

    • onPrepareToChange()

    • onChanged(result: DTDRemoteConfigChangeResult, ex: Exception?)

    The DTDRemoteConfigListenermethods are not called in the main thread - you need to keep this in mind when interacting with UI elements of the app. Also, do not execute operations that block the thread in the DTDRemoteConfigListener methods, because this will disable the SDK operation (see examples).

    To work with A/B testing, use the DTDAnalytics.INSTANCE.initializeWithAbTest initialization method.

    If you used DTDAnalytics.INSTANCE.initialize previously, you need to replace it with DTDAnalytics.INSTANCE.initializeWithAbTest as the SDK initialization method.

    The IRemoteConfigListener interface is added to the initializeWithAbTest method. It implements three methods:

    • void onReceived(@NonNull DTDRemoteConfigReceiveResult result)

    • void onPrepareToChange()

    • void onChanged(@NonNull DTDRemoteConfigChangeResult result, @Nullable Exception ex)

    The DTDRemoteConfigListenermethods are not called in the main thread - you need to keep this in mind when interacting with UI elements of the app. Also, do not execute operations that block the thread in the DTDRemoteConfigListener methods, because this will disable the SDK operation (see examples).

    To work with A/B testing, use the DTDAnalytics.InitializeWithAbTests initialization method.

    If you used DTDAnalytics.Initialize previously, you need to replace it with DTDAnalytics.InitializeWithAbTests as the SDK initialization method.

    The DTDRemoteConfigListener interface is added to the InitializeWithAbTests method. It implements three methods:

    • OnReceived(DTDRemoteConfigReceiveResult result)

    • OnPrepareToChange()

    • OnChanged(DTDRemoteConfigChangeResult result, string exceptionMessage = null)

    The DTDRemoteConfigListenermethods are not called in the main thread - you need to keep this in mind when interacting with UI elements of the app. Also, do not execute operations that block the thread in the DTDRemoteConfigListener methods, because this will disable the SDK operation (see examples).

    To work with A/B testing, use the DTDAnalytics.InitializeWithAbTests initialization method.

    If you used DTDAnalytics.Initialize previously, you need to replace it with DTDAnalytics.InitializeWithAbTests as the SDK initialization method.

    The IDTDRemoteConfigListener interface is added to the InitializeWithAbTests method. It implements three methods:

    • OnReceived(DTDRemoteConfigReceiveResult result)

    • OnPrepareToChange()

    • OnChanged(DTDRemoteConfigChangeResult result, string error)

    The DTDRemoteConfigListenermethods are not called in the main thread - you need to keep this in mind when interacting with UI elements of the app. Also, do not execute operations that block the thread in the DTDRemoteConfigListener methods, because this will disable the SDK operation (see examples).

    Known Limitations: A/B Testing in Restricted Browser Environments

    When running the A/B testing module in an iframe, certain browsers with strict privacy settings (e.g., Safari, Firefox, Brave) may block access to storage mechanisms such as cookies or localStorage. This limitation prevents the proper functioning of A/B tests in these environments. SDK is checking the possibility of accessing storage and will turn off the A/B testing for such a user/device if that's the case.

    To work with A/B testing, use the devtodev.initializeWithAbTest initialization method.

    If you used devtodev.initialize previously, you need to replace it with devtodev.initializeWithAbTest as the SDK initialization method.

    The initializeWithAbTest method as a third parameter takes an object with 3 Callable as arguments, to work with A/B tests it is necessary to implement:

    • onReceived(result: DTDRemoteConfigReceiveResult)

    • onPrepareToChange()

    • onChanged(result: DTDRemoteConfigChangeResult, error: String)

    To work with A/B testing, use the UDTDAnalyticsBPLibrary::InitializeWithAbTest initialization method.

    Blueprint

    If you used UDTDAnalyticsBPLibrary::Initialize previously, you need to replace it with UDTDAnalyticsBPLibrary::InitializeWithAbTest as the SDK initialization method.

    Implements three delegate methods:

    • onRemoteConfigReceive(EDTDRemoteConfigReceiveResult result)

    • onRemoteConfigPrepareToChange()

    • onRemoteConfigChange(EDTDRemoteConfigChangeResult result, FString error)

    To work with A/B testing, use the DTDAnalytics.InitializeWithAbTest initialization method.

    If you used DTDAnalytics.initialize previously, you need to replace it with DTDAnalytics.initializeWithAbTest as the SDK initialization method.

    The InitializeWithAbTest method takes 3 Callable as arguments, to work with A/B tests it is necessary to implement:

    • onRemoteConfigReceive(result: GDDTDRemoteConfigReceiveResult.ReceiveResult)

    • onRemoteConfigPrepareToChange()

    • onRemoteConfigChange(result: GDDTDRemoteConfigChangeResult.ChangeResult, error: String)

    DTDRemoteConfig.remoteConfigWaiting
    .
    The device has no internet connection.
    onChanged()
    with
    DTDRemoteConfigChangeResult.failure
    is called
  • If the offer is received after the 30 second waiting time has expired onChanged() with DTDRemoteConfigChangeResult.failure is called

  • The test was previously activated (during the initialization) onChanged() with DTDRemoteConfigChangeResult.success is called

  • Key/Value
    defaults
    Control Group
    Group A
    Group B

    button_color

    green

    green

    blue

    gray

    button_size

    8

    8

    12

    Example of SDK initialization that includes A/B testing:

    Set the default values DTDRemoteConfig.defaults = @{"button_color": @"green", @"button_size": @"8"}

    Variable names have to be the same as in the ‘Group settings’ section of the web interface (see ‘web’ above). When the SDK gets included in the A/B test, the devtodev server automatically assigns it to a group.

    SDK will not use new variable values that are not set in DTDRemoteConfig.defaults

    Example of the DTDRemoteConfig.configvalue variants:

    Key/Value
    defaults
    Control Group
    Group A
    Group B

    Example of SDK initialization that includes A/B testing:

    Set the default values DTDRemoteConfig.defaults = mapOf("button_color" to "green", "button_size" to "8")

    Variable names have to be the same as in the ‘Group settings’ section of the web interface (see ‘web’ above). When the SDK gets included in the A/B test, the devtodev server automatically assigns it to a group.

    SDK will not use new variable values that are not set in DTDRemoteConfig.defaults

    Example of the DTDRemoteConfig.configvalue variants:

    Key/Value
    defaults
    Control Group
    Group A
    Group B

    Example of SDK initialization that includes A/B testing:

    Set the default values:

    Variable names have to be the same as in the ‘Group settings’ section of the web interface (see ‘web’ above). When the SDK gets included in the A/B test, the devtodev server automatically assigns it to a group.

    SDK will not use new variable values that are not set in DTDRemoteConfig.defaults

    Example of the DTDRemoteConfig.configvalue variants:

    Key/Value
    defaults
    Control Group
    Group A
    Group B

    Example of SDK initialization that includes A/B testing:

    Set the default values:

    Variable names have to be the same as in the ‘Group settings’ section of the web interface (see ‘web’ above). When the SDK gets included in the A/B test, the devtodev server automatically assigns it to a group.

    SDK will not use new variable values that are not set in DTDRemoteConfig.Defaults

    Example of the DTDRemoteConfig.Config value variants:

    Key/Value
    defaults
    Control Group
    Group A
    Group B

    Example of SDK initialization that includes A/B testing:

    Set the default values:

    Variable names have to be the same as in the ‘Group settings’ section of the web interface (see ‘web’ above). When the SDK gets included in the A/B test, the devtodev server automatically assigns it to a group.

    SDK will not use new variable values that are not set in DTDRemoteConfig.Defaults

    Example of the DTDRemoteConfig.Config value variants:

    Key/Value
    defaults
    Control Group
    Group A
    Group B

    Example of SDK initialization that includes A/B testing:

    Set the default values DTDRemoteConfig.SetDefaults()

    Variable names have to be the same as in the ‘Group settings’ section of the web interface (see ‘web’ above). When the SDK gets included in the A/B test, the devtodev server automatically assigns it to a group.

    SDK will not use new variable values that are not set in DTDRemoteConfig.defaults

    Example of the DTDRemoteConfig value variants:

    Key/Value

    defaults

    Control Group

    Group A

    Group B

    button_color

    Example of SDK initialization that includes A/B testing:

    Set the default values:

    Blueprint

    Variable names have to be the same as in the ‘Group settings’ section of the web interface (see ‘web’ above). When the SDK gets included in the A/B test, the devtodev server automatically assigns it to a group.

    SDK will not use new variable values that are not set in DTDRemoteConfig.defaults

    Example of the DTDRemoteConfig.configvalue variants:

    Key/Value
    defaults
    Control Group
    Group A
    Group B

    Example of SDK initialization that includes A/B testing:

    Set the default values DTDRemoteConfig.SetDefaults()

    Variable names have to be the same as in the ‘Group settings’ section of the web interface (see ‘web’ above). When the SDK gets included in the A/B test, the devtodev server automatically assigns it to a group.

    SDK will not use new variable values that are not set in DTDRemoteConfig.defaults

    Example of the DTDRemoteConfig value variants:

    Key/Value
    defaults
    Control Group
    Group A
    Group B

    Example of SDK initialization that includes A/B testing:

    resetConfig()

    It cancels a suitable or running test

    cacheTestExperiment()

    A debug method for saving a test experiment after restarting the application

    Property
    Description

    remoteConfigWaiting: double

    Wait time for A/B test configuration.

    Default value - 0.0 (measured in seconds)

    groupDefinitionWaiting: double

    Wait time for test group.

    Default value - 10.0 (measured in seconds)

    defaults: NSDictionary<NSString *,id>

    Variables and their default values

    config: DTDRemoteConfigCollection

    Wrapper for remote parameters in the form of a collection. It allows access to the configuration values by using the subscripting syntaxis.

    Actual variables and their values for working with A/B tests

    (void) applyConfig

    Property
    Description

    remoteConfigWaiting: Double

    Wait time for A/B test configuration.

    Default value - 0.0 (measured in seconds)

    groupDefinitionWaiting: Double

    Wait time for test group.

    Default value - 10.0 (measured in seconds)

    defaults: Map<String, Any>

    Variables and their default values

    config: DTDRemoteConfigCollection

    Wrapper for remote parameters in the form of a collection. It allows access to the configuration values by using the subscripting syntaxis.

    Actual variables and their values for working with A/B tests

    applyConfig()

    Property
    Description

    setRemoteConfigWaiting()

    Wait time for A/B test configuration.

    Default value - 0.0 (measured in seconds)

    getRemoteConfigWaiting()

    Wait time for test group.

    setGroupDefinitionWaiting()

    Wait time for test group.

    Default value - 10.0 (measured in seconds)

    getGroupDefinitionWaiting()

    Get time for test group.

    setDefaults(Map<String, ? extends Object>)

    Property
    Description

    double RemoteConfigWaiting {get;set;}

    Wait time for A/B test configuration.

    Default value - 0.0 (measured in seconds)

    double GroupDefinitionWaiting {get;set}

    Wait time for test group.

    Default value - 10.0 (measured in seconds)

    Dictionary<string, object> DTDRemoteConfig.Defaults {get;set}

    Variables and their default values

    DTDRemoteConfigCollection DTDRemoteConfig.Config

    Wrapper for remote parameters in the form of a collection. It allows access to the configuration values by using the subscripting syntaxis.

    Actual variables and their values for working with A/B tests

    void ApplyConfig()

    Property
    Description

    RemoteConfigWaiting: double

    Wait time for A/B test configuration.

    Default value - 0.0 (measured in seconds)

    GroupDefinitionWaiting: double

    Wait time for test group.

    Default value - 10.0 (measured in seconds)

    Defaults: IDictionary<String, object>

    Variables and their default values

    Config: DTDRemoteConfigCollection

    Wrapper for remote parameters in the form of a collection. It allows access to the configuration values by using the subscripting syntaxis.

    Actual variables and their values for working with A/B tests

    ApplyConfig()

    Property
    Description

    remoteConfigWaiting

    Wait time for A/B test configuration.

    Default value - 0 (measured in seconds)

    groupDefinitionWaiting

    Wait time for test group.

    Default value - 15 (measured in seconds)

    defaults

    Variables and their default values

    config

    Wrapper for remote parameters in the form of a collection. It allows access to the configuration values by using the subscripting syntaxis.

    Actual variables and their values for working with A/B tests

    applyConfig()

    It applies the A/B testing configuration. After the call, the default parameters get matched with the group parameters.

    resetConfig()

    Property
    Description

    SetRemoteConfigWaiting(float value)

    Wait time for A/B test configuration.

    Default value - 0.0 (measured in seconds)

    float GetRemoteConfigWaiting()

    Wait time for test group.

    SetGroupDefinitionWaiting(float value)

    Wait time for test group.

    Default value - 10.0 (measured in seconds)

    GetGroupDefinitionWaiting()

    Get time for test group.

    SetDefaults(const FDTDRemoteConfigDefaults& defaults)

    Property
    Description

    void SetRemoteConfigWaiting(int value)

    Wait time for A/B test configuration.

    Default value - 0 (measured in seconds)

    int GetRemoteConfigWaiting()

    Wait time for test group.

    void SetGroupDefinitionWaiting(int value)

    Wait time for test group.

    Default value - 10 (measured in seconds)

    int GetGroupDefinitionWaiting()

    Get time for test group.

    void SetDefaults(defaults: GDDTDRemoteConfigDefaults)

    Return true if current configuration has value for a key.

    Int32Value

    Int32

    Gets the value as an Int32.

    Int64Value

    Int64

    Gets the value as an Int64.

    integerValue

    Int

    Gets the value as an Int.

    boolValue

    Bool

    Gets the value as a Bool.

    Property
    type
    Description

    stringValue

    NSString

    Gets the value as an optional string.

    floatValue

    float

    Gets the value as a Float.

    doubleValue

    double

    Gets the value as a Double.

    Property
    type
    Description

    stringValue

    String?

    Gets the value as an optional string.

    floatValue

    Float

    Gets the value as a Float.

    doubleValue

    Double

    Gets the value as a Double.

    Property
    type
    Description

    getStringValue

    String

    Gets the value as an optional string.

    getFloatValue

    float

    Gets the value as a Float.

    getDoubleValue

    double

    Gets the value as a Double.

    Property
    type
    Description

    StringValue

    string

    Gets the value as an optional string.

    FloatValue

    float

    Gets the value as a float.

    DoubleValue

    double

    Gets the value as a double.

    Property
    type
    Description

    StringValue

    String

    Gets the value as an optional string.

    FloatValue

    float

    Gets the value as a float.

    DoubleValue

    double

    Gets the value as a double.

    Property
    Type
    Description

    StringValue

    String

    Gets the value as an optional string.

    FloatValue

    Number

    Gets the value as a float number

    DoubleValue

    Number

    Gets the value as a double number

    LongValue

    String

    Property
    type
    Description

    StringValue

    FString

    Gets the value as an string.

    FloatValue

    float

    Gets the value as a float.

    LongValue

    int64

    Gets the value as an long.

    Property
    type
    Description

    GetStringValue

    String

    Gets the value as an string.

    GetFloatValue

    float

    Gets the value as a float.

    GetIntValue

    int

    Gets the value as an int.

    Method
    Description

    -(void)onReceivedResult:

    (enum DTDRemoteConfigReceiveResult)result

    It is triggered every time the SDK loads an A/B test configuration. If the remoteConfigWaiting has a default value (null), onReceived does not get called

    -(void)onPrepareToChange

    It notifies the developer about coming changes in the A/B test configuration.

    - (void)onChangedResult:

    (enum DTDRemoteConfigChangeResult)result

    error:(NSError *)error

    It notifies the developer that the configuration has changed. Or about the reason why the configuration could not change.

    Method
    Description

    onReceived(result: DTDRemoteConfigReceiveResult)

    It is triggered every time the SDK loads an A/B test configuration. If the remoteConfigWaiting has a default value (null), onReceived does not get called

    onPrepareToChange()

    It notifies the developer about coming changes in the A/B test configuration.

    onChanged(result: DTDRemoteConfigChangeResult, ex: Exception?)

    It notifies the developer that the configuration has changed. Or about the reason why the configuration could not change.

    Method
    Description

    onReceived(DTDRemoteConfigReceiveResult result)

    It is triggered every time the SDK loads an A/B test configuration. If the remoteConfigWaiting has a default value (null), onReceived does not get called

    onPrepareToChange()

    It notifies the developer about coming changes in the A/B test configuration.

    onChanged( DTDRemoteConfigChangeResult result, Exception ex)

    It notifies the developer that the configuration has changed. Or about the reason why the configuration could not change.

    IDTDRemoteConfigListener

    Method
    Description

    void OnReceived(DTDRemoteConfigReceiveResult result);

    It is triggered every time the SDK loads an A/B test configuration. If the remoteConfigWaiting has a default value (null), OnReceived does not get called

    void OnPrepareToChange();

    It notifies the developer about coming changes in the A/B test configuration.

    void OnChanged(DTDRemoteConfigChangeResult result, string exceptionText = null);

    It notifies the developer that the configuration has changed. Or about the reason why the configuration could not change.

    IDTDRemoteConfigListener

    Method
    Description

    OnReceived(DTDRemoteConfigReceiveResult result)

    It is triggered every time the SDK loads an A/B test configuration. If the remoteConfigWaiting has a default value (null), OnReceived does not get called

    OnPrepareToChange()

    It notifies the developer about coming changes in the A/B test configuration.

    OnChanged(DTDRemoteConfigChangeResult result, string error)

    It notifies the developer that the configuration has changed. Or about the reason why the configuration could not change.

    FDTDRemoteConfigReceiveResultDelegate

    It is triggered every time the SDK loads an A/B test configuration. If the remoteConfigWaiting has a default value (null), OnReceived does not get called

    Blueprint

    FDTDRemoteConfigPrepareToChangeDelegate

    It notifies the developer about coming changes in the A/B test configuration.

    Blueprint

    FDTDRemoteConfigChangeResultDelegate

    It notifies the developer that the configuration has changed. Or about the reason why the configuration could not change.

    Method
    Description

    onRemoteConfigReceive(result: GDDTDRemoteConfigReceiveResult.ReceiveResult):

    It is triggered every time the SDK loads an A/B test configuration. If the remoteConfigWaiting has a default value (null), onReceived does not get called

    onRemoteConfigPrepareToChange()

    It notifies the developer about coming changes in the A/B test configuration.

    onRemoteConfigChange(result: GDDTDRemoteConfigChangeResult.ChangeResult, error: String)

    It notifies the developer that the configuration has changed. Or about the reason why the configuration could not change.

    [A/B-Test Module] The Server refused to conduct the test.

    If the server refused to provide a test available for participation.

    DTDRemoteConfigChangeResult.Failure

    [A/B-Test Module] Offer from devtodev not received within the allotted N seconds.

    The backend offer is returned after a deadline e.g. due to a bad internet connection.

    DTDRemoteConfigChangeResult.Failure

    [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, duea bad internet connection or network problems.

    DTDRemoteConfigChangeResult.Failure

    [A/B-Test Module] In the process of receiving an offer from the server, the user was changed. Offer has been canceled.

    When replacing the user with another user at the time of receiving an offer.

    DTDRemoteConfigChangeResult.Failure

    [A/B-Test Module] Offer refused, because application went into the background.

    If a suitable test is found but the user made the app go into the background before the offer was received.

    remoteConfigWaiting: Double

    Wait time for A/B test configuration.

    Default value - 0.0 (measured in seconds)

    groupDefinitionWaiting: Double

    Wait time for test group.

    Default value - 10.0 (measured in seconds)

    defaults: [String, Any]

    Variables and their default values

    config: DTDRemoteConfigCollection

    Wrapper for remote parameters in the form of a collection. It allows access to the configuration values by using the subscripting syntaxis.

    Actual variables and their values for working with A/B tests

    applyConfig()

    hasKey(key)

    Return true if current configuration has value for a key.

    values

    Returns the plain object of the current configuration with key/values pairs

    DTDRemoteConfigSource.Default

    The variable is set by default.

    DTDRemoteConfigSource.Remote

    The variable is set by the test group.

    DTDRemoteConfigSource.Empty

    The variable is not found in the remote configuration.

    stringValue

    String?

    Gets the value as an optional string.

    floatValue

    Float

    Gets the value as a Float.

    doubleValue

    Double

    Gets the value as a Double.

    onReceived(result: DTDRemoteConfigReceiveResult)

    It is triggered every time the SDK loads an A/B test configuration. If the remoteConfigWaiting has a default value (null), onReceived does not get called

    onPrepareToChange()

    It notifies the developer about coming changes in the A/B test configuration.

    onChanged(result: DTDRemoteConfigChangeResult,

    error: Error?)

    It notifies the developer that the configuration has changed. Or about the reason why the configuration could not change.

    DTDRemoteConfigReceiveResult.Success

    It is triggered when the configuration of the A/B test is received.

    DTDRemoteConfigReceiveResult.Failure

    It is triggered if the SDK did not manage to get the A/B test configurations for the time period set in remoteConfigWaiting.

    DTDRemoteConfigReceiveResult.Empty

    It is triggered when the configuration of the A/B test is received, but the list of experiments is empty.

    DTDRemoteConfigReceiveResult.Success

    Configuration is changed.

    DTDRemoteConfigReceiveResult.Failure

    Configuration change attempt failed.

    DTDRemoteConfigChangeResult.Success

    null

    When receiving an offer from the backend.

    DTDRemoteConfigChangeResult.Success

    null

    When launching the SDK with a previously started test.

    DTDRemoteConfigChangeResult.Success

    null

    When replacing the user with another user who previously started the test.

    It applies the A/B testing configuration. After the call, the default parameters get matched with the group parameters.

    hasKey(key: String)

    DTDRemoteConfigChangeResult.Failure

    Please attend, if you use several spaces, in every space user has an individual User API token.

    The pattern of communication with devtodev RAW data export API

    The process of getting data consists of three steps:

    • job assignment

    • getting the status of performance

    • getting the report file by link

    Let’s have a look at these steps.

    Job assignment

    This step is the most important, and its description is the longest one.

    The request of assignment should be sent to:

    Where

    • user_token - individual User API token of the user. It could be sent with both GET and POST methods.

    • v1 - current version of API

    The request content is sent with POST method in JSON format.

    The body of the request contains the following properties:

    Property

    Type

    Description

    user_token

    string

    Individual user API token. It can be found on the space settings page. It is possible to send it with both POST and GET methods.

    app_id

    string

    Application identificator. It can be found in the application settings in the Integration section.

    start_date

    number

    Start date of the request interval. Attention! Data is sent in UNIX-time format. Don’t forget to add/subtract the number of correction seconds to get data according to your timezone.

    end_date

    List of available events and their identificators

    Every standard event in devtodev SDK corresponds with two-letters identificator. In case of custom events, the name of the event is the identificator. Also you can use group identificators (start with @).

    Event ID

    Event name

    lu

    Level up

    rp

    Real Payment

    tr

    Tutorial step

    ip

    Ingame purchase

    sc

    Social network connect

    Custom events filtering

    If you’re interested in the export of custom events according to some specific conditions, you should set the objects with the following properties in the array of the event list:

    Property

    Type

    Description

    name

    string

    Name of custom event

    filters

    object

    Object with the filtering conditions.

    Names of the object properties correspond with the name of custom event parameters. It is possible to apply filters to each of them.

    Example

    "filters":{ "paramName1":{ "gt": 5 // Events where paramName1>5. Have a look at List of comparison operators. } }

    alias

    string

    Name of file in the report where to put the event with filter. The default name is set in the “name” property. Attention! If you export several equal events with different filters, the “alias” property is obligatory.

    User card filtering

    If you want to export events from a specific audience, you can filter them by the properties of the user card. Only the events that occurred with the filtered users will be included in the exported data.

    Caution! Filtering uses the property values of the user card at the time of the data export request.

    The names of the properties are the same as the column names in the "users" table of the project, which you can find in the SQL report.

    The table shows the fields by which filtering is possible:

    Property
    Type
    Description

    devtodevid

    string

    Unique identifier of the user which is used in devtodev and assigned to the user when he launches the app for the first time.

    customuid

    string

    User ID, assigned by the developer.

    idfa

    string

    iOS advertising identifier

    List of comparison operators which can be applied in filter

    The filter can be described with an object, where the following comparison operators can be the properties:

    API operator

    Math operator

    Description

    gt

    >

    Greater than

    lt

    <

    Less than

    eq

    =

    Equal

    gte

    The empty object describes the filter which selects all the events where the parameter value is not null or empty string.

    Examples

    POST

    Or another way with event group identificator.

    The format of the response to the job assignment

    The response in case of successful start execution:

    Where

    • status_code - is the HTTP status code

    • data - an identificator which is assigned to the job

    If you get this response, you can go to the next stage - request of job status (else go to Error handling).

    Getting the status of performance

    With the job identificator, you can request its current status, using the following request:

    The body of request can contain the following properties:

    Property

    Type

    Description

    user_token

    string

    Individual user API token. It can be found on the space settings page. It is possible to send it with both POST and GET methods.

    job_id

    string

    Job identificator which can be got from the response of job assignment.

    The format of the response to the request of status

    The response to the request of status can be one of the following:

    • The job is in the queue

    • The job is in progress

    • The job is done

    • Error of job performance

    Let’s explore every variant.

    The job is in the queue

    The job is in progress

    The job is done

    To get the report use the link in the URL property.

    The report file is zip-archive with files in .CSV format. Every file contains data about one event and/or alias (which was set in the filter in job assignment).

    If there are no events according to the query conditions, the response will be the following:

    Error handling

    The error can raise either at the moment of job assignment or at the moment of checking the status. The format of response in case of error is the same: In case there is an error, a response is made in the following format:

    where

    • status_code (number) - a general status of an error

    • errors (array) - an array of error descriptions

    • code (number) - the exact code of an error from the table of errors

    • msg (string) - a brief description of an error

    The list of possible errors is given in a table.

    Status code

    Code

    Value of "msg" field

    Error description

    400

    2

    Request body is empty

    Empty body of the request. There is no POST data in the request.

    400

    3

    Malformed json

    JSON error in the body of the equest. Fix the formatting error.

    401

    Limitations

    There are the following limitations to devtodev RAW data export API:

    Limitations

    Value

    Max number of job assignments per 24 hours per space

    100

    Max number of job assignments per 24 hours per user

    50

    Max number of simultaneous job assignments per user

    5

    Max timeout between user job assignments

    5 seconds

    Max time to perform one job

    -

    List of available events

    Below you can find the description of each table for events available for export.

    Users

    This file is always attached to the exported file. The table contains a list of users who performed the exported events. It also contains their characteristics available at the time of the export.

    Column name
    Description

    devtodev ID

    Numeric user identifier in the projects’s Users table

    Main ID

    Main user identifier

    Created

    User registration date. Unix timestamp

    Paying

    Flags payers

    Cheater

    Flags cheaters

    GameSessions

    This file contains entries on session starts and user activity times for the selected export period. The table contains session starts (rows with filled session_starts field) and time when the app was in focus (rows with filled activity_duration field).

    Column name
    Description

    devtodev ID

    Numeric user identifier in the project’s Users table

    time

    Event (session start or end of activity) start date. Unix timestamp in milliseconds

    session_starts

    Session start. Marked as 1 when session started.

    activity_duration

    Duration of user activity (time the app was in focus)

    isTester

    At the time of event start the user was a Tester

    IngamePurchase

    This file contains entries on virtual purchases in the app.

    Column name
    Description

    devtodev ID

    Numeric user identifier in the project's Users table

    time

    Event (session start or end of activity) start date. Unix timestamp in milliseconds

    level

    User level at the time of event start

    item_type

    Item category

    item

    Item name

    Install

    A user registration event used in the devtodev database. Usually, the registration date is the same as the date of the first launch of the app.

    Column name
    Description

    devtodev ID

    Numeric user identifier in the project’s Users table

    install_date

    User registration date. Unix timestamp

    LevelUp

    Event for when the user reaches a certain game level.

    Column name
    Description

    devtodev ID

    Numeric user identifier in the project’s Users table

    time

    Event (session start or end of activity) start date. Unix timestamp in milliseconds

    level

    User level at the time of event start

    isTester

    At the time of event start the user was a Tester

    cheat

    At the time of event start the user was a Cheater

    NewUserUpdate

    Some user properties updates for the selected export period. (devtodev ID, logged IP, os_version).

    Column name
    Description

    devtodev ID

    Numeric user identifier in the project’s Users table

    logged ip

    User IP address. Last digit of the address is anonymized

    os_version

    Version of the user operating system

    Payments

    List of user transactions. Purchases for real currency.

    Column name
    Description

    devtodev ID

    Numeric user identifier in the project’s Users table

    date

    Event (session start or end of activity) start date. Unix timestamp in milliseconds

    level

    User level at the time of event start

    transaction_id

    Unique transaction identifier

    transaction_name

    Item name or SKU

    Progression

    This file contains a list of triggered Progression events.

    Column name
    Description

    devtodev ID

    Numeric user identifier in the project’s Users table

    time

    Event (session start or end of activity) start date. Unix timestamp in milliseconds

    level

    User level at the time of event start

    location

    Game location name

    success

    Is location successfully completed (true/false)

    SocialNetworkConnects

    Social media connection events.

    Column name
    Description

    devtodev ID

    Numeric user identifier in the project’s Users table

    time

    Event (session start or end of activity) start date. Unix timestamp in milliseconds

    level

    User level at the time of event start

    social

    Name of social media

    isTester

    At the time of event start the user was a Tester

    SocialNetworkPosts

    Social media publication events.

    Column name
    Description

    devtodev ID

    Numeric user identifier in the projest’s Users table

    time

    Event (session start or end of activity) start date. Unix timestamp in milliseconds

    level

    User level at the time of event start

    social

    Name of social media

    reason

    Reason for publication or title of publication

    SubscriptionPayments

    Subscription events: buying, renewal, and refund. This file contains only subscription events that impact revenue.

    Column name
    Description

    devtodev ID

    Numeric user identifier in the project’s Users table

    date

    Event (session start or end of activity) start date. Unix timestamp in milliseconds

    level

    User level at the time of event start

    transaction_id

    Unique transaction identifier

    transaction_name

    Subscription name or SKU

    Subscriptions

    All subscription events.

    Column name
    Description

    devtodev ID

    Numeric user identifier in the project’s Users table

    date

    Event (session start or end of activity) start date. Unix timestamp in milliseconds

    level

    User level at the time of event start

    transaction_id

    Unique transaction identifier

    transaction_name

    Subscription name or SKU

    Tutor

    Tutorial steps events.

    Column name
    Description

    devtodev ID

    Numeric user identifier in the project’s Users table

    time

    Event (session start or end of activity) start date. Unix timestamp in milliseconds

    level

    User level at the time of event start

    complete_state

    Number of the completed step of the tutorial.

    Also has predefined values:

    0 – the user skipped the tutorial

    -1 – the user started the tutorial

    -2 – the user finished the tutorial

    isTester

    At the time of event start the user was a Tester

    UDIDs

    This file contains information about changes of user and device identifiers during the selected export period.

    Column name
    Description

    devtodev ID

    Numeric user identifier in the project’s Users table

    Main ID

    Main user identifier

    IDFV

    iOS vendor identifier

    IDFA

    iOS advertising identifier

    Crossplatform User ID

    User ID set by developer

    UserUpdate

    This file contains data on user characteristics and devices updates for the selected export period.

    Column name
    Description

    devtodev ID

    Numeric user identifier in the project’s Users table

    Main ID

    Main user identifier

    IDFV

    iOS vendor identifier

    IDFA

    iOS advertising identifier

    Crossplatform User ID

    User ID set by developer

    AdImpression

    This file contains app’s ad impression events. Data from ad networks or the Ad Impression event.

    Column name
    Description

    devtodev ID

    Numeric user identifier in the project’s Users table

    date

    Event (session start or end of activity) start date. Unix timestamp in milliseconds

    level

    User level at the time of event start

    placement

    Place where the ad unit is located

    network

    Name of ad network responsible for placement

    CustomEvent.{EventName}

    This file contains custom events with {EventName} name.

    Column name
    Description

    devtodev ID

    Numeric user identifier in the project’s Users table

    date

    Event (session start or end of activity) start date. Unix timestamp in milliseconds

    level

    User level at the time of event start

    isTester

    At the time of event start the user was a Tester

    cheat

    At the time of event start the user was a Cheater

    URL:

    https://api.devtodev.com/v2/analytics/report?appId=sampleApplicationId

    Field
    Required
    Description

    appId

    Yes

    devtodev application ID

    POST data is received by the server in compressed or text form, depending on the value of the "Content-type" field in the HTTP header.

    All the transferred data must be in UTF8 encoding.

    The archive must contain JSON with one or more events for one or several users.

    The packet size cannot exceed 2 MB in its uncompressed state. Packages that exceed this size can't be processed.

    Headers:

    Header
    Type
    Required
    Description

    Content-type

    String

    Yes

    The possible values for the "Content-type" field are:

    • application/json - for sending uncompressed JSON data

    • application/zstd - for sending data compressed using

    • application/gzip

    Example POST Data:

    The reports object contains:

    Parameter
    Type
    Required
    Description

    deviceId

    String (64)

    Yes

    Current (actual) Device ID

    previousDeviceId

    String (64)

    Should be sent if the Device ID is changed

    Previous Device ID

    userId

    Each packet consists of the following fields:

    Parameter
    Type
    Required
    Description

    language

    String (3)

    Yes

    User/device language (ISO 639-1, ISO 639-2, ISO 639-3)

    country

    String (2)

    Yes

    User/device country (ISO_3166-1_alpha-2). Only needed in the server-server protocol

    ip

    Response

    The server responds with code 200 if everything is OK.

    HTTP Code
    State

    413

    Incorrect size of the data packet (exceeds the maximum)

    400

    devtodev App ID is absent

    401

    Incorrect devtodev App ID

    403

    Administrative restrictions on data received from a client

    400

    The error of data unpacking

    {

    "error_message":"Wrong GZIP format"

    }

    Service Events

    Device Info

    Contains information about the user's device.

    You must send the Device Info event as the first event when a new user is created. Without it, the user will not be registered in the system!

    It is also desirable to send this event at the beginning of each session.

    Parameter
    Platform
    Required
    Type
    Description

    code

    Any

    Yes

    String

    The unique ID of the event. Takes the value "di"

    timestamp

    Any

    Yes

    Example

    Session Start

    Sends information about the start of a new session.

    In order to track sessions fully, you also need to send a User Engagement event.

    Parameter
    Required
    Type
    Description

    code

    Yes

    String

    The unique ID of the event. Takes the value "ss"

    timestamp

    Yes

    Long

    The date of the new session start. Unix timestamp in milliseconds

    level

    Example

    User Engagement

    Sends information about the duration of user activity. This can be either the full length of the session or a part of the user's session while the application was in focus.

    Parameter
    Required
    Type
    Description

    code

    Yes

    String

    The unique ID of the event. Takes the value "ue"

    timestamp

    Yes

    Long

    The date the event was generated. Unix timestamp in milliseconds

    level

    Example

    Setting User Tracking Status (GDPR)

    This event denies/allows tracking of user data and also implements the right to be forgotten in accordance with the requirements of the GDPR.

    A developer must use this event in case a user doesn’t want their data to be sent and processed in the devtodev system.

    When calling the event with the parameter "trackingAllowed": false, it is a command to the server to delete all user’s personal data that has been collected by devtodev from this app and a command to block the collection of any data of this user in future.

    The user will remain in the devtodev system only as an impersonal unit in the previously aggregated metrics.

    If it is set to “true”, tracking can be enabled again. In this case, the user will be considered new.

    Parameter
    Required
    Type
    Description

    code

    Yes

    String

    The unique ID of the event. Takes the value "ts"

    trackingAllowed

    Yes

    Bool

    Enable (true) or disable (false) user tracking

    timestamp

    Example

    Alive

    Service event. Not obligatory. Ping event for the server. It is required to correctly display a player online if more than 5 minutes have passed since his last sent event.

    Parameter
    Required
    Type
    Description

    code

    Yes

    String

    The unique ID of the event. Takes the value "al"

    timestamp

    Yes

    Long

    The date the event was generated. Unix timestamp in milliseconds

    sessionId

    Example

    User properties

    People

    Each devtodev project can have up to 30 custom user properties.

    Attention! We strongly recommend that you do not use these properties to transfer and store data that fits the definition of personal data!

    Parameter
    Required
    Type
    Description

    code

    Yes

    String

    The unique ID of the event. Takes the value "pl"

    timestamp

    Yes

    Long

    The date the event was generated. Unix timestamp in milliseconds

    level

    Predefined user properties (in addition to 30 custom user properties).

    Property
    Required
    Type
    Description

    tester

    No

    bool

    Set true if user is a tester. This user's data will not be used to calculate metrics.

    cheater

    No

    bool

    Set true if user is a cheater. This user's data will not be used to calculate metrics.

    If you need to exclude cheaters/testers transactions from statistics, go to Users & Segments section in devtodev interface and mark the user manually. When you mark the user in devtodev interface, the system removes their transactions for the last 7 days the reports and recalculates the metrics.

    Example

    Basic Events

    Custom Event

    If you want to track non-basic events (below), you can create custom events of your own. How you are going to apply them, depends solely on you.

    devtodev supports not more than 300 custom event names in a single project. Events that exceed the limit of custom event names will be discarded. Try to integrate the tracked actions by type to the event name level, and move the characteristic tags to the parameters.

    For example, if you need to track purchases of “Paper” and “Pen” items, you don’t need to create two events with the names “Paper Purchase” and “Pen Purchase”. Instead. create a single “Purchase” event and add an “Item” parameter with the appropriate value of “Paper” or “Pen”. This way, you can use just one event to track many items.

    For a string parameter, you can use no more than 50,000 unique values ​​for the entire history of events. If the number of unique values exceeds the limit, the parameter gets locked by the system and is discarded from the received data. Therefore, we don’t recommend using highly variable parameters like user IDs or time as string values ​​(moreover, they are automatically added to the event).

    We strongly recommend that you do not change the data type passed in the same parameter. If you change the data type in a parameter, it will be duplicated with the same name, which may cause issues while processing reports.

    We strongly recommend that you do not use custom event properties to transfer and store data that fits the definition of personal data!

    Parameter
    Required
    Type
    Description

    code

    Yes

    String

    The unique ID of the event. Takes the value "ce"

    timestamp

    Yes

    Long

    The date the event was generated. Unix timestamp in milliseconds

    level

    Example

    Real Payment

    To track payments in a real currency, dispatch this event right after the system validates that the payment went through successfully. The event is fundamental and mandatory for all the app metrics related to monetization.

    By default (easy to change in the app’s settings), the devtodev server invalidates transactions with previously-used identifiers. Additionally, the server performs identifier checks based on their outer appearance in order to avoid obvious fraud.

    Parameter
    Required
    Type
    Description

    code

    Yes

    String

    The unique ID of the event. Takes the value "rp"

    timestamp

    Yes

    Long

    The date the event was generated. Unix timestamp in milliseconds

    level

    Example

    Onboarding (tutorial)

    Tracks the progress of the user's initial training (tutorial). The event is used to build a funnel through which users go through learning stages (steps). Each step is represented by an integer greater than 0. If you plan to add additional intermediate learning steps, you can number the steps initially, for example, in increments of 10. Additionally, three constants are used to describe the start of training, successful completion of training, and skipping the entire training process.

    Recommended sequence for dispatching events:

    1. Training started (-1).

    2. Sequential sending of events at the entrance to the next learning step (1...N).

    3. Completion of training after passing the last step of training (-2).

    The tutorial skip logic assumes that a single event with a value of 0 is sent, replacing the entire sequence described above.

    Parameter
    Required
    Type
    Description

    code

    Yes

    String

    The unique ID of the event. Takes the value "tr"

    timestamp

    Yes

    Long

    The date the event was generated. Unix timestamp in milliseconds

    level

    Example

    Virtual Currency Payment

    This event is for games only.

    Pass this event after every purchase if you want to track in-app (virtual) currency spends and items’ popularity. You can apply this event to both games and any apps with virtual currency.

    Parameter
    Required
    Type
    Description

    code

    Yes

    String

    The unique ID of the event. Takes the value "vp" ("ip" in the previous version of the API)

    timestamp

    Yes

    Long

    The date the event was generated. Unix timestamp in milliseconds

    level

    Example

    Currency Accrual

    This event is for games only.

    The event involves the accumulation of in-game currency or resources. It contains data on the amount of in-game currency earned or purchased by the user. It is highly undesirable to send this data transactionally. Instead, please send aggregated data for a specific period, such as 5-10 minutes. Additionally, the accumulation of data must be interrupted, and an event should be sent if the user's level has changed.

    Attention! The total number of tracked unique resources (virtual currencies) cannot exceed 30 items throughout the project's lifespan.

    Furthermore, apart from grouping by type (earned/purchased), there is also a grouping by the source of income.

    Parameter
    Required
    Type
    Description

    code

    Yes

    String

    The unique ID of the event. Takes the value "ca"

    timestamp

    Yes

    Long

    The date the event was generated. Unix timestamp in milliseconds

    level

    Current Balance

    This event is for games only.

    This event is used to generate a preset game report called Economy Balance (currency balance with grouping by days). This report shows the approximate amount of virtual currency on users' hands, which is useful for planning and checking the results of campaigns aimed at removing excess virtual currency from the application.

    This event should not be sent more than once per day for a user.

    Parameter
    Required
    Type
    Description

    code

    Yes

    String

    The unique ID of the event. Takes the value "cb"

    balance

    Yes

    Object<String(24),Number>

    User's balances of virtual currencies or resources at the time the event was generated

    timestamp

    Example

    Level Up

    This event is for games only.

    Leveling up the user (player). The event is triggered when the user reaches a new level. In addition o the achieved level number, you can also include data on the balance of virtual currencies or resources at the time the new level was reached, as well as the total amount of currencies or resources spent, earned and purchased by the user during the previous level progression.

    Attention! The total number of tracked unique resources (virtual currencies) cannot exceed 30 items throughout the entire life of the project.

    Parameter
    Required
    Type
    Description

    code

    Yes

    String

    The unique ID of the event. Takes the value "lu"

    timestamp

    Yes

    Long

    The date the event was generated. Unix timestamp in milliseconds

    level

    Example

    Progression Event

    This event is for games only.

    First of all, the progression event is used in games with short (within one game session) areas/game levels, for example, match 3 games. You can use the event to collect data on how well or how fast users complete levels, how difficult it is for them, how many resources they gained or spent, and other relevant parameters.

    Parameter
    Required
    Type
    Description

    code

    Yes

    String

    The unique ID of the event. Takes the value "pe"

    timestap

    Yes

    Long

    The date the event was generated. Unix timestamp in milliseconds

    level

    parameters object:

    Parameter
    Required
    Type
    Description

    difficulty

    No

    Int

    An optional difficulty value

    source

    No

    String (40)

    The name of the previous progression event used for connecting events together. E.g. a previous area visited by the player

    success

    Example

    All events generated during the passage of the location are recommended to be marked with the key "inProgress," the value of which indicates the name of the location (Progression event name).

    Example

    Secondary Events

    Referral

    Tracking the source of the application installation. Sent once per user. Does not need to be sent if ad trackers such as AppsFlyer are integrated in the project settings or the devtodev custom postback API is used.

    Parameter
    Required
    Type
    Description

    code

    Yes

    String

    The unique ID of the event. Takes the value "rf"

    timestamp

    Yes

    Long

    The date the event was generated. Unix timestamp in milliseconds

    source

    Example

    Ad Impression

    The event is used for individual tracking of ad revenue on user devices. This method is used if there are CPI data available on the client device (they can be obtained from the ad network SDK).

    Do not apply this event if you use ad networks that utilize the server-server protocol for sending ad revenue data (ironSource, AppLovin MAX, and Fyber networks) and you already set up this method of data collection because if you use both data sources, your revenue data may be duplicated.

    Parameter
    Required
    Type
    Description

    code

    Yes

    String

    The unique ID of the event. Takes the value "adrv"

    timestamp

    Yes

    Long

    The date the event was generated. Unix timestamp in milliseconds

    source

    Example

    Social Connect

    The event tracks the user's connection to a social network. It is sent at the moment when the application receives information about the successful authorization of the user on the social network.

    Parameter
    Required
    Type
    Description

    code

    Yes

    String

    The unique ID of the event. Takes the value "sc"

    timestamp

    Yes

    Long

    The date the event was generated. Unix timestamp in milliseconds

    level

    Example

    Preset social network codes

    Social Network
    Code

    Vkontakte

    vk

    Twitter

    tw

    Facebook

    fb

    Google Plus

    gp

    WhatsApp

    wp

    Social Post

    The event tracks the user's posts on the social network. It is dispatched when a success report is received from the network, if possible.

    Parameter
    Required
    Type
    Description

    code

    Yes

    String

    The unique ID of the event. Takes the value "sp"

    timestamp

    Yes

    Long

    The date the event was generated. Unix timestamp in milliseconds

    level

    Example

    Example

    An example of a package describing a single session of a single user.

    Note: devtodev does not accept data that is older than 7 days and data that is more than 3 hours in the future. If you want to use this example, you will need to edit the timestamp parameters.

    HashMap<String, Object> map = new HashMap<>();
    map.put("button_color", "green");
    map.put("button_size", "8");
    DTDRemoteConfig.INSTANCE.setDefaults(map);
    DTDRemoteConfig.Defaults = new Dictionary<string, object>
    {
       {"button_color", "green"},
       {"button_size", 8}
    };
    DTDRemoteConfig.Defaults = new Dictionary<string, object> 
    {
      ["button_color"] = "green",
      ["button_size"] = 8
    };
    // Implementation defaults params
    FDTDRemoteConfigDefaults defaults;
    defaults.StringDefaults.Add("button_color", "green");
    defaults.IntegerDefaults.Add("button_size", 8);
    DECLARE_DELEGATE_OneParam(FDTDRemoteConfigReceiveResultDelegate, EDTDRemoteConfigReceiveResult);
    DECLARE_DELEGATE(FDTDRemoteConfigPrepareToChangeDelegate);
    import UIKit
    import DTDAnalytics
    
    @main
    class AppDelegate: UIResponder, UIApplicationDelegate, DTDRemoteConfigListener {
        func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
            // Config timeout, optional
            DTDRemoteConfig.remoteConfigWaiting = 10.0
            // Group timeout, optional
            DTDRemoteConfig.groupDefinitionWaiting = 15.0
            // Implementation defaults params
            DTDRemoteConfig.defaults = ["button_color": "green",
                                        "button_size": "8"]
            // Initialize SDK with ab test
            DTDAnalytics.initializeWithAbTest(applicationKey: "appKey",
                                              abConfigListener: self)
            return true
        }
    
        // The method is called when the configuration has been received
        func onReceived(result: DTDRemoteConfigReceiveResult) {
            print("Configuration received, result \(result)")
        }
    
        // Called when experiment is found
        func onPrepareToChange() {
            print("Experiment found")
        }
    
        // Called when SDK receives config
        func onChanged(result: DTDRemoteConfigChangeResult, error: Error?) {
            print("DTDRemoteConfigChangeResult: \(result)")
            if let error = error {
                print("DTDRemoteConfigError: \(error.localizedDescription)")
            }
            DTDRemoteConfig.applyConfig()
            // Use here or notify config listeners that actualConfig is ready
            let btnColorRemoteValue = DTDRemoteConfig.config["button_color"].stringValue
            let btnSizeRemoteValue = DTDRemoteConfig.config["button_size"].stringValue
            print("button color: \(btnColorRemoteValue)")
            print("button size: \(btnSizeRemoteValue)")
        }
    }
    ​https://devtodev.com/api/v1/rawexport/setjob?user_token=USER_API_TOKEN
    https://devtodev.com/api/v1/rawexport/setjob
    {
    	"user_token": "USER_API_TOKEN", // It is obligatory, if it’s not sent with GET.
    	"app_id": "af0606ed-bbdc-065a-952c-0d92561f107c",  // Obligatory. Application identificator.
    	"start_date": 1464709200,  // Obligatory. Unix time of export start date.
    	"end_date": 1464739200,  // Obligatory. Unix time of export end date.
    	"events": [ //Not obligatory. List of events to export.
    		"tr", // Export all  Tutorial step events
    		"customEvent1Name",  // Export all customEvent1Name events
            //{"name": "customEvent1Name"} - alternative way with the same result
    		{
    			"name": "customEvent2Name",
    			"filters": {  // Object with filter conditions
    				"paramName1": {
    					// 5<paramName1<=10 and paramName1!=8
    					"lte": 10,
    					"gt": 5,
    					"neq": 8
    				},
    				"paramName2": {
    					//Parameter paramName2 is equal to the one of elements.
    					"eq": ["a","b","c"] 
    				},
    				"paramName3": {} // paramName3 is not empty.
    			}
    		}
    	],
    	"user_card_filter": { //user card properties filter object
    		"paramName1": { //name of preset or custom user card parameter
    			"lte": 10,
    			"gt": 5,
    			"neq": 8
    		},
    		"paramName2": { //name of preset or custom user card parameter
    			"eq": ["a", "b", "c"]
    		}
    	}
    }
    {
    	"user_token": "USER_API_TOKEN",
    	"app_id": "af0606ed-bbdc-065a-952c-0d92561f107c",
    	"start_date": 1464709200,
    	"end_date": 1464739200,
    	"events": ["@custom_events"] //Export all custom events
    }
    {
    	"status_code": 200,
    	"data": "JOB_ID"
    }
    https://devtodev.com/api/v1/rawexport/getprogress?user_token=USER_API_TOKEN
    {  
    	"status_code":202,
    	"data":{  
    		"status":"pending",
    		"percent_complete":0
    	}
    }
    {  
    	"status_code":200,
    	"data":{  
    		"status":"running",
    		"percent_complete":10
    	}
    }
    {  
    	"status_code":201,
    	"data":{  
    		"status":"complete",
    		"percent_complete":100,
    		"result":{  
    			"msg":"Report file is ready",
    			"format":"csv",  // Format of report files in archive
    			"url":"https://devtodev.com/api/v1/rawexport/download/?job_id=JOB_ID&user_token=USER_API_TOKEN",
    			"ttl": 100500, // Report lifetime from the end of the performance to the removal
    			"expire":123434534534  // Date when the report will be removed (in UNIX-time format)
    		}
    	}
    }
    {  
    	"status_code":200,
    	"data":{  
    		"status":"complete",
    		"percent_complete":100,
    		"result":{  
    			"msg":"Unfortunately the result of your request is empty. Please try to change the report conditions."
    		}
    	}
    }
    {
        "status_code": 400,
        "errors": [{
            "code": 3,
            "msg": "Error description"
        }]
    }
    {  
        "reports" : [
            {
                "deviceId" : "deviceId",
                "previousDeviceId" : "samplePreviousMainId",
                "userId" : "sampleUserId",
                "previousUserId" : "samplePreviousUserId",
                "devtodevId": 6123517,
                "packages" : [
                    {
                        "language" : "en",
                        "country": "US",
                        "ip": "154.12.121.11",
                        "appVersion" : "1.0",
                        "appBuildVersion": "30",
                        "sdkVersion" : "2.0",
                        "sdkCodeVersion" : 20,
                        "bundle" : "com.example.application",
                        "installationSource" : "",
                        "engine": "Unity",
                        "events" : [
                            ... //see Events section
                        ]
                    },
                    {
                        ...
                    }
                ]  
            },
            ...
        ]
    }
    {
    	"code": "di",
    	"timestamp": 1694783505123,
    	"osVersion": "10.2.2",
    	"os": "iOS",
    	"displayPpi": 401,
    	"displayResolution": "1920x1080",
    	"dispalyDiagonal": "5.5",
    	"manufacturer": "Apple",
    	"model": "iPhone8,2",
    	"userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_2) AppleWebKit/602.3.12 (KHTML, like Gecko) Version/10.0.2 Safari/602.3.12",
    	"timeZoneOffset": 7200,
    	"idfv": "30FE1CE1-1125-4657-97B0-638744C3C6D1",
    	"idfa": "0A60DCF2-3186-4801-9192-D8CFA995DD6D"
    }
    {
        "code": "ss",
        "timestamp": 1694783505123,
        "level": 1
    }
    {
        "code": "ue",
        "timestamp": 1694783735122,
        "level": 1,
        "length": 230,
        "sessionId": 1694783505123
    }
    {
        "code": "ts",
        "trackingAllowed": false,
        "timestamp": 1694783565763
    }
    {
        "code": "al",
        "timestamp": 1694783607178
    }
    {
    	"code": "pl",
    	"timestamp": 1694783511089,
    	"sessionId": 1694783505123,
    	"level": 2,
    	"parameters": {
    		"name": "John Doe",
    		"cheater": false,
    		"age": 21
    	}
    }
    {
    	"code": "ce",
    	"timestamp": 1694783521034,
    	"sessionId": 1694783505123,
    	"level": 2,
    	"name": "eventName",
    	"parameters": {
    		"intParameter": 134,
    		"stringParameter": "hello",  //255 symbols max
    		"doubleParameter": 12.98
    	}
    }
    {
    	"code": "rp",
    	"timestamp": 1694783605101,
    	"sessionId": 1694783505123,
    	"level": 2,
    	"productId": "starter_pack",
    	"orderId": "GPA.100054271428",
    	"price": 19.99,
    	"currencyCode": "USD"
    }
    {
    	"code": "tr",
    	"timestamp": 1694783715371,
    	"sessionId": 1694783505123,
    	"level": 2,
    	"step": -1
    }
    {
    	"code": "vp",
    	"timestamp": 1694783555833,
    	"sessionId": 1694783505123,
    	"level": 2,
    	"purchaseId": "house_327",
    	"purchaseAmount": 2,
    	"purchasePrice": {
    		"coins": 500,
    		"wood": 2
    	},
    	"purchaseType": "buildings"
    }
    {
    	"code": "ca",
    	"timestamp": 1694783625364,
    	"sessionId": 1694783505123,
    	"level": 2,
    	"bought": {
    		"sourceA": {
    			"resource1": 1231,
    			"resource2": 1231
    		},
    		"sourceB": {
    			"resource1": 12,
    			"resource2": 31
    		},
    		"sourceC": {
    			"resource2": 40,
    			"resource1": 10
    		}
    	},
    	"earned": {
    		"sourceA": {
    			"resource1": 1231,
    			"resource2": 1231
    		},
    		"sourceD": {
    			"resource1": 1231,
    			"resource2": 1231
    		}
    	}
    }
    {
    	"code": "cb",
    	"timestamp": 1694783615284,
    	"sessionId": 1694783505123,
    	"level": 2,
    	"balance": {
    		"money1": 123,
    		"money2": 11
    	}
    }
    {
    	"code": "lu",
    	"timestamp": 1694783675815,
    	"sessionId": 1694783505123,
    	"level": 2,
    	"balance": {
    		"money1": 123,
    		"money2": 11
    	},
    	"spent": {
    		"money1": 12,
    		"money2": 2,
    		"wood": 12
    	},
    	"earned": {
    		"crystals": 5
    	},
    	"bought": {
    		"wood": 200
    	}
    }
    {
    	"code": "pe",
    	"timestamp": 1694783885625,
    	"sessionId": 1694783505123,
    	"level": 2,
    	"name": "MyAwesomeLocation",
    	"parameters": {
    		"source": "location1",
    		"difficulty": 1,
    		"success": true,
    		"duration": 180
    	},
    	"spent": {
    		"money1": 12,
    		"money2": 2,
    		"wood": 12
    	},
    	"earned": {
    		"money1": 8,
    		"money2": 2,
    		"stone": 1
    	}
    }
    {
    	"code": "rp",
    	"timestamp": 1694783895114,
    	"sessionId": 1694783505123,
    	"level": 2,
    	"productId": "starter_pack",
    	"orderId": "GPA.100054271428",
    	"price": 19.99,
    	"currencyCode": "USD",
    	"inProgress": ["MyAwesomeLocation"]
    }
    {
    	"code": "rf",
    	"timestamp": 1694783915948,
    	"sessionId": 1694783505123,
    	"source": "google",
    	"campaign": "christmas",
    	"content": "sale for ducks",
    	"medium": "traff",
    	"term": "a,b,c,d,e"
    }
    {
    	"code": "adrv",
    	"timestamp": 1694783775187,
    	"sessionId": 1694783505123,
    	"source": "IronSource",
    	"ad_network": "Facebook",
    	"placement": "End of the round",
    	"ad_unit": "TestAdUnit",
    	"revenue": 0.3434
    }
    {
    	"code": "sc",
    	"timestamp": 1694783815155,
    	"sessionId": 1694783505123,
    	"level": 2,
    	"socialNetwork": "fb"
    }
    {
    	"code": "sp",
    	"timestamp": 1694783905478,
    	"sessionId": 1694783505123,
    	"level": 2,
    	"socialNetwork": "fb",
    	"postReason": "quest_completed"
    }
    {
    	"reports": [{
    		"deviceId": "0A60DCF2-3186-4801-9192-D8CFA995DD6D",
    		"userId": "u_100500",
    		"packages": [{
    			"language": "en",
    			"country": "US",
    			"appVersion": "1.0",
    			"events": [{
    					"code": "di",
    					"osVersion": "10.2.2",
    					"os": "iOS",
    					"displayPpi": 401,
    					"displayResolution": "1920x1080",
    					"dispalyDiagonal": "5.5",
    					"manufacturer": "Apple",
    					"model": "iPhone8,2",
    					"userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_2) AppleWebKit/602.3.12 (KHTML, like Gecko) Version/10.0.2 Safari/602.3.12",
    					"timeZoneOffset": 7200,
    					"idfv": "30FE1CE1-1125-4657-97B0-638744C3C6D1",
    					"idfa": "0A60DCF2-3186-4801-9192-D8CFA995DD6D",
    					"timestamp": 1694783505122
    				}, {
    					"code": "ss",
    					"timestamp": 1694783505123,
    					"level": 1
    				}, {
    					"code": "pl",
    					"timestamp": 1694783511089,
    					"sessionId": 1694783505123,
    					"level": 1,
    					"parameters": {
    						"name": "John Doe",
    						"cheater": false,
    						"age": 21
    					}
    				}, {
    					"code": "tr",
    					"timestamp": 1694783715371,
    					"sessionId": 1694783505123,
    					"level": 1,
    					"step": -1
    				}, {
    					"code": "tr",
    					"timestamp": 1694783725344,
    					"sessionId": 1694783505123,
    					"level": 1,
    					"step": 1
    				}, {
    					"code": "lu",
    					"timestamp": 1694783736345,
    					"sessionId": 1694783505123,
    					"level": 2,
    					"balance": {
    						"money1": 123,
    						"money2": 11
    					}
    				}, {
    					"code": "tr",
    					"timestamp": 1694783741456,
    					"sessionId": 1694783505123,
    					"level": 2,
    					"step": 2
    				}, {
    					"code": "tr",
    					"timestamp": 1694783752425,
    					"sessionId": 1694783505123,
    					"level": 2,
    					"step": -2
    				}, {
    					"code": "ce",
    					"timestamp": 1694783773675,
    					"sessionId": 1694783505123,
    					"level": 2,
    					"name": "eventName",
    					"parameters": {
    						"intParameter": 134,
    						"stringParameter": "hello",
    						"doubleParameter": 12.98
    					}
    				},
    				{
    					"code": "rp",
    					"timestamp": 1694783798278,
    					"sessionId": 1694783505123,
    					"level": 2,
    					"productId": "com.example.application.starterpack",
    					"orderId": "280001601071201",
    					"price": 19.99,
    					"currencyCode": "USD"
    
    				}, {
    					"code": "ue",
    					"timestamp": 1694783898278,
    					"level": 2,
    					"length": 393,
    					"sessionId": 1694783505123
    				}
    			]
    		}]
    	}]
    }

    number

    End date of the request interval. Data is sent in UNIX-time format.

    events

    array

    List of events to export. The element of the array can be in the following formats:

    • Event identificator (string). Have a look at the list of available events and their identificators.

    • Event group identificator (string). Have a look at the list of available events and their identificators.

    • Object with the name of custom event where it is possible to assign alias and to add filters. Have a look at Custom events filtering.

    The empty array means the export of all the available events.

    sp

    Social network post

    gs

    Sessions

    uu

    User update

    ud

    UDIDs

    pe

    Progression event

    rg

    Registration (install date)

    sbs

    Subscription (all actions with subscriptions)

    adrv

    Ad impressions. Data from ad networks or the Ad Impression event.

    @basic_events

    All events below

    sbs_payments

    Subscription (payments only)

    Any custom event name

    Custom event

    @custom_events

    All custom events

    idfv

    string

    iOS vendor identifier

    advertisingid

    string

    Advertising ID (Android, Windows)

    androidid

    string

    Android ID

    created

    int

    Unix timestamp (ms) when the user opened the app for the first time

    lasttime

    int

    Unix timestamp (ms) of the last user payment

    publisher

    string

    Acquisition. The name of the publisher/ad network that led to the acquisition of the user

    campaign

    string

    Acquisition. The name of the ad campaign that resulted in acquiring the user

    placement

    string

    Acquisition. Place where the ad unit is located

    ad

    string

    Acquisition. Name of ad unit/banner

    firstpaymentdate

    int

    Unix timestamp (ms) of the first user payment

    lastpaymentdate

    int

    Unix timestamp (ms) of the last user payment

    paymentcount

    int

    Number of payments that the user have made in the app

    paymentsum

    number

    Total sum of payments in USD

    sbsfirstpaymentdate

    int

    Unix timestamp of the first subscription payment date

    sbspaymentcount

    int

    The number of subscription payments made by the user in the app

    sbspaymentsum

    number

    Total amount of subscription payments in USD

    cheater

    boolean

    Whether the user has cheated or not (true/false

    tester

    boolean

    Whether the user is a tester or not (true/false)

    appversion

    string

    Current version of the app

    firstappversion

    string

    App version installed by the user at the time of registration

    sdkversion

    string

    Current version of devtodev SDK

    level

    int

    Current user level

    locale

    string

    User/device language (ISO 639-1, ISO 639-2, ISO 639-3)

    country

    string

    User/device country (ISO_3166-1_alpha-2)

    timezoneoffset

    int

    User timezone offset from UTC in milliseconds

    pushavailable

    boolean

    True if there is a push token in devtodev DB

    _AnyCustomUserPropertyName The prefix "_" here should be used to indicate that this is a custom property

    string, number, boolean

    Custom user card property, assigned by the developer.

    >=

    Greater than or equal

    lte

    <=

    Less than or equal

    neq

    !=

    Not equal

    eq

    In the list

    neq

    Not in the list

    11

    Authorization error. Wrong user token %user_token value%

    Authorization error. The set token is wrong. “User_token” field. User API token.

    401

    12

    Authorization error.

    User_token is not set.

    Authorization error. “User_token” field is absent. User API token should be set either as a parameter in GET string of request or in POST body of request.

    400

    6

    Invalid app id %app id value%

    The requested project can not be found. Unknown application. This error can raise when a user makes a mistake with the app id or when the application with this ID was removed.

    403

    13

    Access denied. You have no access to the app %app id value%

    The error of access. User has no access to this application.

    403

    14

    Access denied. You have no access to the report file %file id value%

    The error of access. User has no access to this file. This error can raise if you have no access to the application used in the previously created request.

    403

    15

    Access denied. You have no access to API.

    The error of access. No access to User API token. This error can happen when the access rights were changed (in consequence of changing the user role or tariff plan)

    403

    16

    Access denied. You have no access to RAW data export API.

    The error of access. This error can raise when you have no rights to RAW data export API for your user role or tariff plan.

    400

    4

    Field not found: %field_name%

    An obligatory field can not be found. You need to complement the request with this field.

    403

    17

    Quota exceeded. %% concurrent requests per user has been reached.

    The limit of simultaneous jobs per user is exceeded. Wait until one of the jobs will be finished and repeat the request.

    429

    18

    Too many requests. %% requests per day per user quota has been exhausted.

    The daily limit of the user is exceeded. You can send the next request at the beginning of the next day.

    429

    19

    Too many requests. %% requests per day per space quota has been exhausted.

    The daily space limit is exceeded. You can send the next request at the beginning of the next day.

    429

    20

    Too many requests. One request per %% seconds per user quota has been exhausted.

    The limit of request frequency is exceeded. You have to wait for the time set in the error text.

    404

    21

    File not found. The requested report file had been expired and was deleted.

    The requested file can not be found. This can be when the file was removed due to the end of its lifetime. You have to repeat the job assignment and wait for the new file.

    400

    10

    Incorrect report time frame interval:

    start_date and end_date can not be earlier than 90 days from now.

    Incorrect report time frame interval.

    Both start and end date should be later than 90 days from now.

    400

    22

    The job_id you requested is not found.

    The job_id you requested is not found or the job was done and the report lifetime is exceeded.

    400

    5

    Field %field_name% has type %received_type% but %expected_type% expected

    The data type is not equal to the expected data type. Check the correspondence of the request with the format set in the documentation. Possible data types: boolean, integer, float, number (integer+float), string.

    400

    7

    Unknown format: %format%

    You have set the unknown export file format.

    400

    9

    Event alias duplicated: %alias%

    The event alias is duplicated. There should be no equal custom events without alias and no equal aliases in the request.

    400

    8

    Unknown event: %event_name%

    Unknown event. This error can raise when the event is not in @basic_event group or the event doesn’t exist or the event is blocked custom event.

    500

    1

    Unknown Error

    Unknown error. Please contact devtodev technical support.

    Max lifetime of the report file

    24 hours

    Max number of report file downloads

    10

    Tester

    Flags testers

    Level

    Current user level

    AppVersion

    Current app version

    Language

    User’s device locale

    Country

    User country (set by IP address)

    Device manufacturer

    Device manufacturer

    Device name

    Device trademark name

    Crossplatform User ID

    User ID set by developer

    Channel

    Acquisition. User acquisition channel

    InstallSource

    Android. Android installer bundle

    User agent

    User agent

    Screen resolution

    Screen resolution of the device or app’s workspace

    OS version

    Version of the operating system

    IDFV

    iOS vendor identifier

    IDFA

    iOS advertising identifier

    OPEN_UDID

    OpenUDID

    username

    User name. Preset using the User card

    useremail

    User email. Preset using the User card

    userphoto

    User photo url. Preset using the User card

    userphone

    User telephone number. Preset using the User card

    AdCampaign

    Acquisition. The name of the ad campaign that resulted in acquiring the user

    Time zone offset

    User timezone offset from UTC in milliseconds

    OsVersion

    Version of the user’s operating system

    segments

    List of user custom segments containing this user. Segments separated by comma

    Agency

    Acquisition. Ad mediator name (Sub-publisher)

    Keyword

    Acquisition. Ad keywords and/or keywords that led to install

    Placement

    Acquisition. Place where the ad unit is located

    Site

    Acquisition. Website or app where the ad was placed

    Ad group

    Acquisition. Name of ad group

    Ad

    Acquisition. Name of ad unit/banner

    segments

    Comma separated segment names. The state at the time the data was exported.

    abtests

    Comma separated names of AB test groups. The state at the time the data was exported.

    Custom property field name 1

    User card custom property

    Custom property field name N

    User card custom property

    cheat

    At the time of event start the user was a Cheater

    level

    User level at the time of event start

    install_date

    User registration date. Unix timestamp

    app_version

    Application version. The data at the moment the event was generated.

    segments

    Comma separated segment names. The data at the moment the event was generated.

    abtests

    Comma separated names of AB test groups. The data at the moment the event was generated.

    count

    Amount of items the user bought

    cheat

    At the time of event start the user was a Cheater

    isTester

    At the time of event start the user was a Tester

    install_date

    User registration date. Unix timestamp

    Virtual currency name 1

    Amount of virtual currency spent on the item (overall)

    Virtual currency name N

    Amount of virtual currency spent on the item (overall)

    app_version

    Application version. The data at the moment the event was generated.

    segments

    Comma separated segment names. The data at the moment the event was generated.

    abtests

    Comma separated names of AB test groups. The data at the moment the event was generated.

    install_date

    User registration date. Unix timestamp

    spent

    Amount of virtual currency spent by user on the previous level. List of currency names and amounts separated by comma.

    Example: “Coins: 90, Gold: 10”

    earned

    Amount of virtual currency earned by user on the previous level. List of currency names and amounts separated by comma.

    Example: “Coins: 90, Gold: 10

    balance

    Amount of virtual currency on user balance at the time of reaching a new level. List of currency names and amounts separated by comma.

    Example: “Coins: 90, Gold: 10

    bought

    Amount of virtual currency bought by user for real currency on the previous level. List of currency names and amounts separated by comma.

    Example: “Coins: 90, Gold: 10

    app_version

    Application version. The data at the moment the event was generated.

    segments

    Comma separated segment names. The data at the moment the event was generated.

    abtests

    Comma separated names of AB test groups. The data at the moment the event was generated.

    amount_in_usd

    Amount of real currency spent by user. Converted into USD using exchange rate at the moment the event is received by the devtodev server

    status

    Is transaction valid or not

    install_date

    User registration date. Unix timestamp

    isTester

    At the time of event start the user was a Tester

    cheat

    At the time of event start the user was a Cheater

    app_version

    Application version. The data at the moment the event was generated.

    segments

    Comma separated segment names. The data at the moment the event was generated.

    abtests

    Comma separated names of AB test groups. The data at the moment the event was generated.

    prev_location

    Name of the previous location

    duration

    Time spent on completing the location

    difficulty

    Location difficulty (numeric)

    isTester

    At the time of event start the user was a Tester

    cheat

    At the time of event start the user was a Cheater

    install_date

    User registration date. Unix timestamp

    spent

    Amount of virtual currency or resources spent by user upon completing the location. List of currency names and amounts separated by comma.

    Example: “Coins: 90, Gold: 10

    earned

    Amount of virtual currency or resources earned by user upon completing the location. List of currency names and amounts separated by comma.

    Example: “Coins: 90, Gold: 10

    app_version

    Application version. The data at the moment the event was generated.

    segments

    Comma separated segment names. The data at the moment the event was generated.

    abtests

    Comma separated names of AB test groups. The data at the moment the event was generated.

    cheat

    At the time of event start the user was a Cheater

    install_date

    User registration date. Unix timestamp

    app_version

    Application version. The data at the moment the event was generated.

    segments

    Comma separated segment names. The data at the moment the event was generated.

    abtests

    Comma separated names of AB test groups. The data at the moment the event was generated.

    isTester

    At the time of event start the user was a Tester

    cheat

    At the time of event start the user was a Cheater

    install_date

    User registration date. Unix timestamp

    app_version

    Application version. The data at the moment the event was generated.

    segments

    Comma separated segment names. The data at the moment the event was generated.

    abtests

    Comma separated names of AB test groups. The data at the moment the event was generated.

    amount_in_usd

    Amount of real currency spent by user. Converted into USD using exchange rate at the moment the event is received by the devtodev server

    install_date

    User registration date. Unix timestamp

    isTester

    At the time of event start the user was a Tester

    cheat

    At the time of event start the user was a Cheater

    app_version

    Application version. The data at the moment the event was generated.

    segments

    Comma separated segment names. The data at the moment the event was generated.

    abtests

    Comma separated names of AB test groups. The data at the moment the event was generated.

    action

    An action to subscription. Examples:

    purchased

    changed renewal status

    changed renewal pref

    renewed

    is_trial

    Is subscription a trial or not

    is_payment_received

    Is payment successfully received or not

    started_at

    Subscription start date

    expired_at

    Subscription end date

    amount_in_usd

    Amount of real currency spent by user. Converted into USD using exchange rate at the moment the event is received by the devtodev server

    install_date

    User registration date. Unix timestamp

    isTester

    At the time of event start the user was a Tester

    cheat

    At the time of event start the user was a Cheater

    app_version

    Application version. The data at the moment the event was generated.

    segments

    Comma separated segment names. The data at the moment the event was generated.

    abtests

    Comma separated names of AB test groups. The data at the moment the event was generated.

    cheat

    At the time of event start the user was a Cheater

    install_date

    User registration date. Unix timestamp

    app_version

    Application version. The data at the moment the event was generated.

    segments

    Comma separated segment names. The data at the moment the event was generated.

    abtests

    Comma separated names of AB test groups. The data at the moment the event was generated.

    Push token

    User push token

    logged

    Date of user’s last activity

    level

    User level at the time of event start

    language

    User device locale

    country

    User country (from IP address)

    sdk_version

    SDK version at the time of data update

    app_version

    App version at the time of data update

    created

    User registration date. Unix timestamp

    paying

    Is user a payer or not

    device_name

    Device trademark name

    cheater

    At the time of event start the user was a Cheater

    osversion

    Version of the operating system at the time of the data update

    unit

    Name of ad unit/banner

    source

    Ad impression data source

    revenue

    Ad impression revenue in USD

    install_date

    User registration date. Unix timestamp

    isTester

    At the time of event start the user was a Tester

    cheat

    At the time of event start the user was a Cheater

    install_date

    User registration date. Unix timestamp

    app_version

    Application version. The data at the moment the event was generated.

    segments

    Comma separated segment names. The data at the moment the event was generated.

    abtests

    Comma separated names of AB test groups. The data at the moment the event was generated.

    custom parameter name 1

    Custom parameter value (string, number or boolean)

    custom parameter name N (up to 30)

    Custom parameter value (string, number or boolean)

    - for
    sending data compressed using gzip algorithms

    String (64)

    When accounting by User ID

    Current (actual) User ID

    previousUserId

    String (64)

    Should be sent if the User ID is changed (renamed)

    Previous User ID

    devtodevId

    Long

    No

    Numeric user/device account ID in the devtodev database. We recommend using this identifier if you are using SDK and API at the same time. Get this identifier on the SDK side, save it on your server and use it to send data via API. Sending other identifiers is not necessary in this case.

    packages

    Array

    Yes

    Packets with events that happened to the user (see below)

    String (15)

    No

    The IP address of the device. Only needed in the server-server protocol.

    appVersion

    String (64)

    No

    App version

    appBuildVersion

    String

    No

    Application build version. A string value is used for compatibility across all platforms

    sdkVersion

    String

    No

    Version of the integrated SDK

    bundle

    String

    No

    Application bundle

    engine

    String

    No

    Application platform/engine (Native, Unity, Unreal, Air)

    installationSource

    String

    No

    Only for Android. The installer bundle is passed as the value. It determines where the application was installed from (apk, Google Play, Amazon, etc). Used to detect and prevent illegal apk installs

    events

    Array

    Yes

    Events in the order they are generated (details below)

    400

    The error of JSON format

    {

    "error_message":"Wrong JSON format"

    }

    200

    OK. Data received

    Long

    The date the event was generated. Unix timestamp in milliseconds

    osVersion

    Any

    No

    String

    Operating system version

    os

    Any

    No

    String

    OS family name (Android, iOS, Mac, Windows…)

    displayPpi

    Any

    No

    Int

    Screen pixel density

    displayResolution

    Any

    No

    String

    Screen resolution in pixels, sent as "longSide"x"shortSide" ("1024x768")

    displayDiagonal

    Any

    No

    Double

    Screen size (inches)

    manufacturer

    Android, iOS, Mac

    No

    String

    Device manufacturer (Apple, Samsung)

    model

    Android, iOS, Mac

    No

    String

    Device model. Value of the name (iOS), model (Android)

    deviceType

    Any

    No

    Int

    Device Type: 0 - Unknown 1 - Phone 2 - Tablet 3 - Desktop 4 - Watch 5 - TV 6 - Simulator

    timeZoneOffset

    Any

    No

    Int

    The UTC offset (or time offset) in seconds

    rooted

    Any

    No

    Int

    Is the device rooted

    isLimitAdTrackingEnabled

    iOS, Android, Windows

    No

    Bool

    Whether ad tracking restriction is enabled

    userAgent

    Any

    No

    String

    User-Agent header

    idfv

    iOS, Mac

    No

    String

    identifierForVendor

    idfa

    iOS, Mac

    No

    String

    advertisingIdentifier

    androidId

    Android

    No

    String

    Device SSAID. Not recommended for use

    advertisingId

    Android, Windows

    No

    String

    Advertising ID

    serialId

    Android, Windows

    No

    String

    Build.Serial (Android). Not recommended for use.

    uuid

    Android, Windows,

    iOS, macOS

    No

    String

    RFC 4122

    CFUUID | Apple Developer Documentation

    instanceId

    Android

    No

    String

    InstanceId.Get();

    sandboxState

    iOS

    No

    Int

    Information about how the application is built: undefined (0) / sandbox (1) / production (2)

    Yes

    Int

    User (player) level at the time the event was generated

    sessionId

    No

    Long

    The ID of the current session. This is a kind of key by which you can link all user events that occurred during one session. This can be a session start timestamp, or you can use some custom numeric ID, or the user's session sequence number if you have that data.

    Yes

    Int

    User (player) level at the time the event was generated

    length

    Yes

    Int

    Full session duration or a part of a user session in seconds

    sessionId

    No

    Long

    The ID of the current session

    Yes

    Long

    The date the event was generated. Unix timestamp in milliseconds

    No

    Long

    The ID of the current session

    Yes

    Int

    User (player) level at the time the event was generated

    parameters

    Yes

    Object<Key, Value>

    User characteristics in key-value format. May contain predefined (tester, cheater, name, email, phone, photo, gender, age) and custom user propery names.

    User custom property values can be a number, a string (up to 500 symbols), or a boolean value

    sessionId

    No

    Long

    The ID of the current session

    Yes

    Int

    User (player) level at the time the event was generated

    name

    Yes

    String (72)

    Custom event name

    parameters

    No

    Object<Key (32),Value>

    Custom event parameters

    sessionId

    No

    Long

    The ID of the current session

    Yes

    Int

    User (player) level at the time the event was generated

    productId

    Yes

    String (255)

    Product name. We recommend using the product SKU

    orderId

    Yes

    String (64)

    Unique transaction ID

    price

    Yes

    Double

    Price in user currency

    currencyCode

    Yes

    String (3)

    ISO 4217 alphabetic code of the user's currency

    sessionId

    No

    Long

    The ID of the current session

    Yes

    Int

    User (player) level at the time the event was generated

    step

    Yes

    Int

    Tutorial step number or predefined values (0, -1, -2 see above)

    sessionId

    No

    Long

    The ID of the current session

    Yes

    Int

    User (player) level at the time the event was generated

    purchaseAmount

    Yes

    Int

    Number of items purchased

    purchasePrice

    Yes

    Object<String (24), Number>

    Currency and price of the purchased items (or currencies, if there are multiple)

    purchaseType

    Yes

    String (96)

    The group to which the item belongs

    purchaseId

    Yes

    String (32)

    Item ID or name

    sessionId

    No

    Long

    The ID of the current session

    Yes

    Int

    User (player) level at the time the event was generated

    bought

    Yes one or both (bought and earned)

    Object<String, Object<String (24), Number>>

    Resources of the type "bought" (purchased) aggregated over a specific period by source and resource name

    earned

    Yes one or both (bought and earned)

    Object<String, Object<String (24), Number>>

    Resources of type "earned" aggregated over a specific period by source and resource name

    sessionId

    No

    Long

    The ID of the current session

    Yes

    Long

    The date the event was generated. Unix timestamp in milliseconds

    level

    Yes

    Int

    User (player) level at the time the event was generated

    sessionId

    No

    Long

    The ID of the current session

    Yes

    Int

    User (player) level achieved by the user

    balance

    No

    Object<String (24), Number>

    User's balances of virtual currencies or resources at the time of leveling up

    spent

    No

    Object<String (24), Number>

    Total expenses of resources (virtual currency) by the user during level progression

    In the SDK, this data is aggregated from virtual currency payment events

    earned

    No

    Object<String (24), Number>

    The total amount of resources (virtual currency) earned by the user during level progression

    In the SDK, this data is aggregated from CurrencyAccrual events with the "earned" type

    bought

    No

    Object<String (24), Number>

    The total number of resources (virtual currency) purchased by the user for real currency during level progression

    In the SDK, data is aggregated from the CurrencyAccrual events with the "bought" type

    sessionId

    No

    Long

    The ID of the current session

    Yes

    Int

    User (player) level at the time the event was generated

    parameters

    Yes

    Object

    Event parameters. See below

    spent

    No

    Object<String (24), Number>

    Resources consumed during an area completion

    earned

    No

    Object<String (24), Number>

    Resources earned during an area completion.

    name

    Yes

    String (40)

    The name of the event. It is usually the number or the name of the area/location/level.

    sessionId

    No

    Long

    The ID of the current session

    Yes

    Bool

    The completion event result: “true” if successful, “false” if unsuccessful/lost

    duration

    Yes

    Long

    Time in seconds taken to complete the area

    No

    String

    Ad network name

    campaign

    No

    String

    Ad campaign name

    content

    No

    String

    Campaign content (for example, for A/B testing of site elements or contextual ads)

    medium

    No

    String

    Traffic type

    term

    No

    String

    Campaign search term (for example, PPC keywords)

    sessionId

    No

    Long

    The ID of the current session

    No

    String (100)

    Impression data source name

    ad_network

    Yes

    String (100)

    The name of the ad network that delivered the impression

    placement

    No

    String (100)

    Placement of the banner

    ad_unit

    No

    String (100)

    Banner title

    revenue

    Yes

    Double

    Reward for displaying a banner in USD

    sessionId

    No

    Long

    The ID of the current session

    Yes

    Int

    User (player) level at the time the event was generated

    socialNetwork

    Yes

    String

    Code or name of the social network. May contain predefined values (see below)

    sessionId

    No

    Long

    The ID of the current session

    Viber

    vb

    Evernote

    en

    Google Mail

    gm

    LinkedIn

    in

    Pinterest

    pi

    Qzone

    qq

    Reddit

    rt

    Renren

    rr

    Tumblr

    tb

    Yes

    Int

    User (player) level at the time the event was generated

    socialNetwork

    Yes

    String

    Code or name of the social network. May contain predefined values (see above)

    postReason

    Yes

    String

    Reason for posting

    sessionId

    No

    Long

    The ID of the current session

    Facebook's standard algorithm

    14

    14

    14

    button_color

    green

    green

    blue

    gray

    button_size

    8

    8

    button_color

    green

    green

    blue

    gray

    button_size

    8

    8

    button_color

    green

    green

    blue

    gray

    button_size

    8

    8

    12

    14

    button_color

    green

    green

    blue

    gray

    button_size

    8

    8

    12

    14

    button_color

    green

    green

    blue

    gray

    button_size

    8

    8

    12

    14

    green

    green

    blue

    gray

    button_size

    8

    8

    12

    14

    button_color

    green

    green

    blue

    gray

    button_size

    8

    8

    12

    14

    button_color

    green

    green

    blue

    gray

    button_size

    8

    8

    12

    14

    It applies the A/B testing configuration. After the call, the default parameters get matched with the group parameters.

    (void) resetConfig

    It cancels a suitable or running test

    (void) cacheTestExperiment

    A debug method for saving a test experiment after restarting the application

    It applies the A/B testing configuration. After the call, the default parameters get matched with the group parameters.

    resetConfig()

    It cancels a suitable or running test

    cacheTestExperiment()

    A debug method for saving a test experiment after restarting the application

    Set map of variables and their default values

    Map<String,Object> getDefaults()

    Get map of variables and their default values

    DTDRemoteConfigCollection getConfig()

    Wrapper for remote parameters in the form of a collection. It allows access to the configuration values by using the subscripting syntaxis.

    Actual variables and their values for working with A/B tests

    applyConfig()

    It applies the A/B testing configuration. After the call, the default parameters get matched with the group parameters.

    resetConfig()

    It cancels a suitable or running test

    cacheTestExperiment()

    A debug method for saving a test experiment after restarting the application

    It applies the A/B testing configuration. After the call, the default parameters get matched with the group parameters.

    void ResetConfig()

    It cancels a suitable or running test

    void CacheTestExperiment()

    A debug method for saving a test experiment after restarting the application

    It applies the A/B testing configuration. After the call, the default parameters get matched with the group parameters.

    ResetConfig()

    It cancels a suitable or running test

    CacheTestExperiment()

    A debug method for saving a test experiment after restarting the application

    It cancels a suitable or running test

    cacheTestExperiment()

    A debug method for saving a test experiment after restarting the application

    Set USTRUCT with variables and their default values

    TMap<FString, FDTDRemoteConfigValue> GetConfig()

    Actual variables and their values for working with A/B tests

    FDTDRemoteConfigValue GetRemoteConfigValue(const FString& key)

    Return actual value for variable key

    bool HasKey(const FString& key)

    Verify if there are any values associated with the variable key in the remote configuration.

    ApplyConfig()

    It applies the A/B testing configuration. After the call, the default parameters get matched with the group parameters.

    ResetConfig()

    It cancels a suitable or running test

    CacheTestExperiment()

    A debug method for saving a test experiment after restarting the application

    Set GDDTDRemoteConfigDefaults with variables and their default values

    GDDTDRemoteConfigValue GetRemoteConfigValue(key: String)

    Return actual value for variable key

    bool HasKey(key: String)

    Verify if there are any values associated with the variable key in the remote configuration.

    void ApplyConfig()

    It applies the A/B testing configuration. After the call, the default parameters get matched with the group parameters.

    void ResetConfig()

    It cancels a suitable or running test

    void CacheTestExperiment()

    A debug method for saving a test experiment after restarting the application

    Int32Value

    long

    Gets the value as an long.

    Int64Value

    long long

    Gets the value as an long long.

    integerValue

    NSInteger

    Gets the value as an NSInteger.

    boolValue

    BOOL

    Gets the value as a Bool.

    intValue

    Int

    Gets the value as an int.

    longValue

    Long

    Gets the value as an long.

    booleanValue

    Boolean

    Gets the value as a Bool.

    getIntValue

    int

    Gets the value as an Int.

    getLongValue

    long

    Gets the value as an Long.

    getBooleanValue

    Boolean

    Gets the value as a Boolean.

    LongValue

    long

    Gets the value as an long.

    IntValue

    int

    Gets the value as an int.

    BoolValue

    bool

    Gets the value as a bool.

    Int16Value

    Int16

    Gets the value as an Int16

    Int32Value

    Int32

    Gets the value as an Int32

    Int64Value

    Int64

    Gets the value as an Int64

    BoolValue

    bool

    Gets the value as a bool.

    Gets the value as a long

    NB: can be simulated only via string in JavaScript

    IntValue

    Number

    Gets the value as a number

    BoolValue

    Boolean

    Gets the value as a boolean

    IntValue

    int32

    Gets the value as an int.

    BoolValue

    bool

    Gets the value as a bool.

    Source

    EDTDRemoteConfigSource

    Gets source of the value.

    GetBoolValue

    bool

    Gets the value as a bool.

    GetSource

    GDDTDRemoteConfigSource.Source

    Gets source of the value.

    Blueprint
    Blueprint
    Blueprint

    12

    12

    window.devtodev.initializeWithAbTest(
        appKey, 
        {
            userId: userId,
            logLevel: logLevel,
            trackingAvailability: true,
        },
        {
            onReceived: function(result) {
                console.log('onReceived callback', result)
            },
            onPrepareToChange: function() {
                console.log('onPrepareToChange callback')
            },
            onChanged: function(result, error) {
                console.log('onChanged callback', result, error)
            }
        }
    )
    DECLARE_DELEGATE(FDTDRemoteConfigPrepareToChangeDelegate);
    DECLARE_DELEGATE_OneParam(FDTDRemoteConfigReceiveResultDelegate, EDTDRemoteConfigReceiveResult);
    DECLARE_DELEGATE_TwoParams(FDTDRemoteConfigChangeResultDelegate, EDTDRemoteConfigChangeResult, const FString&);
    #import "AppDelegate.h"
    #import "DTDAnalytics/DTDAnalytics-Swift.h"
    
    @interface AppDelegate () <DTDRemoteConfigListener>
    
    @end
    
    @implementation AppDelegate
    
    - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
        // Config timeout, optional
        DTDRemoteConfig.remoteConfigWaiting = 10.0;
        // Group timeout, optional
        DTDRemoteConfig.groupDefinitionWaiting = 15.0;
        // Implementation defaults params
        DTDRemoteConfig.defaults = @{@"button_color": @"green",
                                     @"button_size": @"8"};
        [DTDAnalytics applicationKey:@"appKey" abConfigListener:self];
        return YES;
    }
    
    // The method is called when the configuration has been received
    - (void)onReceivedResult:(enum DTDRemoteConfigReceiveResult)result {
        NSLog(@"Configuration received, result %ld", (long)result);
    }
    
    // Called when experiment is found
    - (void)onPrepareToChange {
        NSLog(@"Experiment found");
    }
    
    - (void)onChangedResult:(enum DTDRemoteConfigChangeResult)result error:(NSError *)error {
        NSLog(@"DTDRemoteConfigChangeResult: %ld", (long)result);
        if (error) {
            NSLog(@"DTDRemoteConfigError: %@", error.localizedDescription);
        }
        [DTDRemoteConfig applyConfig];
        // Use here or notify config listeners that actualConfig is ready
        NSString *btnColorRemoteValue = DTDRemoteConfig.config[@"button_color"].stringValue;
        NSString *btnSizeRemoteValue = DTDRemoteConfig.config[@"button_size"].stringValue;
        NSLog(@"button color: %@", btnColorRemoteValue);
        NSLog(@"button size: %@", btnSizeRemoteValue);
    }
    @end
    class MainActivity : AppCompatActivity(), DTDRemoteConfigListener {
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_main)
            
            // Config timeout, optional
            DTDRemoteConfig.remoteConfigWaiting = 10.0
            // Group timeout, optional
            DTDRemoteConfig.groupDefinitionWaiting = 15.0
            // Implementation defaults params
            DTDRemoteConfig.defaults = mapOf("button_color" to "green", "button_size" to "8")
            // Initialize SDK with ab test
            DTDAnalytics.initializeWithAbTest(appKey = "appKey", context = this, abConfigListener = this)
        } 
        
        /**
         * The method is called when the configuration has been received
         */
        override fun onReceived(result: DTDRemoteConfigReceiveResult) {
           Log.d("D2D", "Configuration received") 
        }
        
        /**
         * Called when experiment is found
         */
        override fun onPrepareToChange() {
           Log.d("D2D", "Experiment found") 
        }
    
        /**
         * called when SDK receives config
         */
        override fun onChanged(result: DTDRemoteConfigChangeResult, ex: Exception?) {
            Log.d("D2D", "DTDRemoteConfigChangeResult: ${result.name}")
            ex?.let {
                Log.d("D2D", "DTDRemoteConfigErr: ${ex.message}")
            }
            DTDRemoteConfig.applyConfig()
            // Use here or notify config listeners that actualConfig is ready
            val btnColorRemoteValue = DTDRemoteConfig.config["button_color"].stringValue
            val btnSizeRemoteValue = DTDRemoteConfig.config["button_size"].stringValue
            Log.d("D2D", "button color: $btnColorRemoteValue")
            Log.d("D2D", "button size: $btnSizeRemoteValue")
        }
    }
    public class MainActivityJava extends AppCompatActivity implements DTDRemoteConfigListener {
    
        @Override
        protected void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            // Config timeout, optional
            DTDRemoteConfig.INSTANCE.setRemoteConfigWaiting(10.0);
            // Group timeout, optional
            DTDRemoteConfig.INSTANCE.setGroupDefinitionWaiting(15.0);
            // Implementation defaults params
            HashMap<String, Object> map = new HashMap<>();
            map.put("button_color", "green");
            map.put("button_size", "8");
            // Initialize SDK with ab test
            DTDRemoteConfig.INSTANCE.setDefaults(map);
        }
    
        @Override
        public void onReceived(@NonNull DTDRemoteConfigReceiveResult result) {
            Log.d("D2D", "Configuration received");
        }
    
        @Override
        public void onPrepareToChange() {
            Log.d("D2D", "Experiment found");
        }
    
        @Override
        public void onChanged(@NonNull DTDRemoteConfigChangeResult result, @Nullable Exception ex) {
            Log.d("D2D", "DTDRemoteConfigChangeResult: " + result.name());
            if (ex != null) {
                Log.d("D2D", "DTDRemoteConfigErr: " + ex);
            }
    
            DTDRemoteConfig.INSTANCE.applyConfig();
            // Use here or notify config listeners that actualConfig is ready
            String btnColorRemoteValue = DTDRemoteConfig.INSTANCE.getConfig().get("button_color").getStringValue();
            String btnSizeRemoteValue = DTDRemoteConfig.INSTANCE.getConfig().get("button_size").getStringValue();
            Log.d("D2D", "button color: " + btnColorRemoteValue);
            Log.d("D2D", "button size: " + btnSizeRemoteValue);
        }
    }
    using System.Collections.Generic;
    using DevToDev.Analytics;
    using DevToDev.Analytics.ABTest;
    using UnityEngine;
    
    namespace AbTesting
    {
        public class Example : MonoBehaviour, IDTDRemoteConfigListener
        {
            private void Start()
            {
                DontDestroyOnLoad(this);
                // Config timeout, optional
                DTDRemoteConfig.RemoteConfigWaiting = 10.0;
                // Group timeout, optional
                DTDRemoteConfig.GroupDefinitionWaiting = 15.0;
                // Implementation defaults params
                DTDRemoteConfig.Defaults = new Dictionary<string, object>
                {
                    {"button_color", "green"},
                    {"button_size", 8}
                };
    
                // Initialize SDK with ab test
                DTDAnalytics.InitializeWithAbTests("app_key", this);
            }
    
            // The method is called when the configuration has been received
            public void OnReceived(DTDRemoteConfigReceiveResult result)
            {
                Debug.Log($"Configuration received, result {result}");
            }
            
            // Called when experiment is found
            public void OnPrepareToChange()
            {
                Debug.Log("Experiment found");
            }
    
            // Called when SDK receives config
            public void OnChanged(DTDRemoteConfigChangeResult result, string exceptionText = null)
            {
                Debug.Log($"DTDRemoteConfigChangeResult: {result}");
                if (exceptionText != null)
                {
                    Debug.Log($"DTDRemoteConfigError: {exceptionText}");
                }
    
                // Use here or notify config listeners that actualConfig is ready
                DTDRemoteConfig.ApplyConfig();
                var buttonColorRemoteValue = DTDRemoteConfig.Config["button_color"].StringValue();
                var buttonSizeRemoteValue = DTDRemoteConfig.Config["button_size"].IntValue();
                Debug.Log($"button color: {buttonColorRemoteValue}");
                Debug.Log($"button size: {buttonSizeRemoteValue}");
            }
        }
    }
    
    using DevToDev.Analytics;
    using System.Collections.Generic;
    using System.Diagnostics;
    
    class MyRemoteConfigListener : IDTDRemoteConfigListener
    {
        /**
         * The method is called when the configuration has been received
         */
        public void OnReceived(DTDRemoteConfigReceiveResult result)
        {
          Debug.WriteLine("Configuration has been received!");
        }
        
        /**
         * Called when experiment is found
         */
        public void OnPrepareToChange()
        {
          Debug.WriteLine("Experiment was found!");
        }
        
        /**
         * Called when SDK receives config
         */
        public void OnChanged(DTDRemoteConfigChangeResult result, string error)
        {
          Debug.WriteLine($"Result: {result}");
          if (result == DTDRemoteConfigChangeResult.Failure)
          {
            Debug.WriteLine($"Error: {error}");
          }
          
          DTDRemoteConfig.ApplyConfig();
          DTDRemoteConfig.ApplyConfig();
          var buttonColorRemoteValue = DTDRemoteConfig.Config["button_color"].StringValue;
          var buttonSizeRemoteValue = DTDRemoteConfig.Config["button_size"].IntValue;
          Debug.WriteLine($"button color: {buttonColorRemoteValue}");
          Debug.WriteLine($"button size: {buttonSizeRemoteValue}");
          // Use here or notify config listeners that config is ready
        }
    }
      
    public static class Program
    {
        public static void Main(string[] args)
        {
            // Config timeout, optional
            DTDRemoteConfig.RemoteConfigWaiting = 10;
            
            // Group timeout, optional
            DTDRemoteConfig.GroupDefinitionWaiting = 15;
            
            // Implementation defaults params
            DTDRemoteConfig.Defaults = new Dictionary<string, object> 
            {
              ["button_color"] = "green",
              ["button_size"] = 8
            };
            
            // Create listener
            var listener = new MyRemoteConfigListener();
            
            // Initialize SDK with ab test
            DTDAnalytics.InitializeWithAbTest("appKey", listener);
        }
    }
    window.devtodev.remoteConfig.defaults = {
       button_color: 'green',
       button_size:  8
    }
    window.devtodev.initializeWithAbTest(
        appKey, 
        {
            userId: userId,
            logLevel: logLevel,
            trackingAvailability: trackingAvailability,
        },
        {
            onReceived: function(result) {
                console.log('onReceived callback', result)
            },
            onPrepareToChange: function() {
                console.log('onPrepareToChange callback')
            },
            onChanged: function(result, error) {
                console.log('onChanged callback', result, error)
                window.devtodev.remoteConfig.applyConfig()
                var config = window.devtodev.remoteConfig.config // DTDRemoteConfigCollection
                var buttonSizeRemoteValue =  window.devtodev.remoteConfig.config["button_size"].intValue;
                var buttonColorRemoteValue = window.devtodev.remoteConfig.config["button_color"].stringValue;
                console.log("button color: ", buttonColorRemoteValue);
                console.log("button size: ", buttonSizeRemoteValue);
            }
        }
    )
    #include "DTDAnalytics/Public/DTDAnalyticsBPLibrary.h"
    #include "DTDAnalytics/Public/DTDRemoteConfigBPLibrary.h"
    
    UDTDAnalyticsBPLibrary::SetLogLevel(EDTDLogLevel::Debug);
    
    // Config timeout, optional
    UDTDRemoteConfigBPLibrary::SetRemoteConfigWaiting(10.0f);
    // Group timeout, optional
    UDTDRemoteConfigBPLibrary::SetGroupDefinitionWaiting(15.0f);
    
    // Implementation defaults params
    FDTDRemoteConfigDefaults defaults;
    defaults.StringDefaults.Add("button_color", "green");
    defaults.IntegerDefaults.Add("button_size", 8);
    
    UDTDRemoteConfigBPLibrary::SetDefaults(defaults);
    
    // The method is called when the configuration has been received
    const auto onConfigReceive = new FDTDRemoteConfigReceiveResultDelegate();
    onConfigReceive->BindLambda([=](EDTDRemoteConfigReceiveResult result)
    {	
    	UE_LOG(LogTemp, Warning, TEXT("Configuration received, result %s"), *UEnum::GetValueAsString(result));
    });
    
    // Called when experiment is found
    const auto onPrepareToChange = new FDTDRemoteConfigPrepareToChangeDelegate();
    onPrepareToChange->BindLambda([=]()
    {
    	UE_LOG(LogTemp, Warning, TEXT("Experiment found"));
    });
    
    // Called when SDK receives config
    const auto onConfigChange = new FDTDRemoteConfigChangeResultDelegate();
    onConfigChange->BindLambda([=](EDTDRemoteConfigChangeResult result, const FString& error)
    {
    	UE_LOG(LogTemp, Warning, TEXT("DTDRemoteConfigChangeResult: %s"), *UEnum::GetValueAsString(result));
    	if (!error.IsEmpty()) {
    		UE_LOG(LogTemp, Warning, TEXT("DTDRemoteConfigError: %s"), *error);
    	}
    
    	// Use here or notify config listeners that actualConfig is ready
    	UDTDRemoteConfigBPLibrary::ApplyConfig();
    
    	FString ButtonColorRemoteValue = UDTDRemoteConfigBPLibrary::GetRemoteConfigValue("button_color").StringValue;
            int32 buttonSizeRemoteValue = UDTDRemoteConfigBPLibrary::GetRemoteConfigValue("button_size").IntegerValue;
    	UE_LOG(LogTemp, Warning, TEXT("button color: : %s"), *ButtonColorRemoteValue);
    	UE_LOG(LogTemp, Warning, TEXT("button size:: %d"), buttonSizeRemoteValue);
    });
    
    // Initialize SDK with ab test
    UDTDAnalyticsBPLibrary::InitializeWithAbTest("AppKey", *onConfigChange, *onPrepareToChange, *onConfigReceive);
    func _ready():
        	# Config timeout, optional
    	DTDRemoteConfig.SetRemoteConfigWaiting(10)
    	# Group timeout, optional
    	DTDRemoteConfig.SetGroupDefinitionWaiting(10)
        	# Implementation defaults params
    	var defaults = GDDTDRemoteConfigDefaults.new()
    	defaults.AddStringValue("button_color", "green")
    	defaults.AddIntegerValue("button_size", 8)
    	DTDRemoteConfig.SetDefaults(defaults)
        	# Initialize SDK with ab test
    	DTDAnalytics.SetLogLevel(GDDTDLogLevel.Debug)
    	DTDAnalytics.InitializeWithConfigWithAbTest(
    		"appKey",
    		onRemoteConfigChange,
    		onRemoteConfigPrepareToChange,
    		onRemoteConfigReceive)
    
    # The method is called when the configuration has been received
    func onRemoteConfigReceive(result: GDDTDRemoteConfigReceiveResult.ReceiveResult):
    	match result:
    		GDDTDRemoteConfigReceiveResult.ReceiveResult.Failure:
    			print("onRemoteConfigReceive result = Failure")
    			
    		GDDTDRemoteConfigReceiveResult.ReceiveResult.Success:
    			print("onRemoteConfigReceive result = Success")
    			
    		GDDTDRemoteConfigReceiveResult.ReceiveResult.Empty:
    			print("onRemoteConfigReceive result = Empty")
    			
    		_:
    			print("onRemoteConfigReceive result = Unknown")
    
    # Called when experiment is found		
    func onRemoteConfigPrepareToChange():
    	print("Experiment found")
    			
    # Called when SDK receives config
    func onRemoteConfigChange(result: GDDTDRemoteConfigChangeResult.ChangeResult, error: String):
    	if !error.is_empty(): 
    		print("error = " + error)
    		
    	match result:
    		GDDTDRemoteConfigChangeResult.ChangeResult.Failure:
    			print("onRemoteConfigChange result = Failure")	
    			
    		GDDTDRemoteConfigChangeResult.ChangeResult.Success:
    			
    			print("onRemoteConfigChange result = Success")
    		_: 
    			print("onRemoteConfigChange result = Unknown")
    			
    	DTDRemoteConfig.ApplyConfig()
    	
    	# Use here or notify config listeners that actualConfig is ready
    	var btnColorRemoteValue = DTDRemoteConfig.GetRemoteConfigValue("button_color").GetStringValue()
    	var btnSizeRemoteValue = DTDRemoteConfig.GetRemoteConfigValue("button_size").GetStringValue()
    	print("button color: " + btnColorRemoteValue)
    	print("button size: " + btnSizeRemoteValue)
    DECLARE_DELEGATE_TwoParams(FDTDRemoteConfigChangeResultDelegate, EDTDRemoteConfigChangeResult, const FString&);

    A/B testing examples

    Example №1

    Hypothesis

    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.

    Test criteria

    The criteria for test inclusion is starting the tutorial. The DTDAnalytics.tutorial(step: Int) event with step = -1 is the trigger.

    Groups

    Control group: a 10 step tutorial, no purchase screens

    Group А: an 11 step tutorial. At step number 5, we will offer a special offer.

    Implementation

    Example №2

    Hypothesis

    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.

    Test criteria

    The criteria for test inclusion is the app install date and the “payer” status.

    Groups

    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

    Implementation

    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:

    Note

    When receiving the configuration, the SDK always calls the onReceivedResult

    Example №3

    Hypothesis

    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.

    Test criteria

    New users who have not started the tutorial yet.

    Groups

    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

    Implementation

    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:

    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:

    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:

    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:

    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:

    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()
            }
        }
    }
    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);
        }
    }
        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();
        }
    }
    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();
            }
        }
    }
    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();
    }
    devtodev.remoteConfig.defaults = {
       maximumDiscount: false,
    }
    devtodev.remoteConfig.groupDefinitionWaiting = 10
    devtodev.remoteConfig.remoteConfigWaiting = 15
    devtodev.initializeWithAbTest(
        appKey, 
        {
            userId: userId,
            logLevel: logLevel,
            trackingAvailability: trackingAvailability,
        },
        {
            // Process the result of waiting for A/B test configuration
            onReceived: function(result) {
                // It is not used in current example
            },
            // Prepare the app UI for changing the remote configuration
            onPrepareToChange: function() {
                // Display the progress indicator
                ui.showSpinner()
            },
            // Apply the values of the assigned group
            onChanged: function(result, error) {
                ui.hideSpinner()
                switch (result) {
                  case DTDRemoteConfigChangeResult.Failure:
                      // Error processing
                      console.error(error);
                      break;
                  case DTDRemoteConfigChangeResult.Success:
                      // Apply new values
                      devtodev.remoteConfig.applyConfig()
                      break;
                }
                updateAppUI();
            }
        }
    )
    function updateAppUI() {
        var maximumDiscount = window.devtodev.remoteConfig.config['maximumDiscount'].boolValue
        if (maximumDiscount) {
            // Display a discount badge clicking on which invokes a purchase window popup
        }
    }
    // 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()
    // 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})");
        }
    }
    devtodev.remoteConfig.defaults = {
       showStore: false,
    }
    devtodev.remoteConfig.groupDefinitionWaiting = 10
    devtodev.initializeWithAbTest(
        appKey, 
        {
            userId: userId,
            logLevel: logLevel,
            trackingAvailability: trackingAvailability,
        },
        {
            // Process the result of waiting for A/B test configuration
            onReceived: function(result) {
                // It is not used in current example
            },
            // Prepare the app UI for changing the remote configuration
            onPrepareToChange: function() {
                // Display the progress indicator
                ui.showSpinner()
            },
            // Apply the values of the assigned group
            onChanged: function(result, error) {
                ui.hideSpinner()
                switch (result) {
                  case DTDRemoteConfigChangeResult.Failure:
                      // Error processing
                      console.error(error);
                      break;
                  case DTDRemoteConfigChangeResult.Success:
                      // Apply new values
                      devtodev.remoteConfig.applyConfig()
                      break;
                }
            }
        }
    )
    function startTutorial() {
        devtodev.tutorial(parseInt(-1))
    }
    function setTutorialStep(step) {
        devtodev.tutorial(parseInt(step))
        var showStore = window.devtodev.remoteConfig.config['showStore'].boolValue
        if (showStore && step == 5) {
              // Offer purchasing of a special offer
              store.showSpecialOffer();
        }
    }
    // 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
     // 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
            }
        }
    }
    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
    // 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
        }
    }
    devtodev.remoteConfig.defaults = {
       difficulty: 3,
       stepCount: 10
    }
    devtodev.remoteConfig.remoteConfigWaiting = 10
    devtodev.initializeWithAbTest(
        appKey, 
        {
            userId: userId,
            logLevel: logLevel,
            trackingAvailability: trackingAvailability,
        },
        {
            // Process the result of waiting for A/B test configuration
            onReceived: function(result) {
                // It is not used in current example
            },
            // Prepare the app UI for changing the remote configuration
            onPrepareToChange: function() {
            },
            // Apply the values of the assigned group
            onChanged: function(result, error) {
                ui.hideSpinner()
                switch (result) {
                  case DTDRemoteConfigChangeResult.Failure:
                      // Error processing
                      console.error(error);
                      break;
                  case DTDRemoteConfigChangeResult.Success:
                      // Apply new values
                      devtodev.remoteConfig.applyConfig()
                      var config = window.devtodev.remoteConfig.config // DTDRemoteConfigCollection
                      break;
                }
                realStartTutorial();
            }
        }
    )
    function startTutorial() {
        devtodev.tutorial(parseInt(-1))
        // Display the progress indicator
        ui.showSpinner()
    }
    // Initiate tutorial completion
    function realStartTutorial() {
        // Hide the download progress indicator
        ui.hideSpinner();
        var stepCount = window.devtodev.remoteConfig.config['stepCount'].intValue
        var difficulty = window.devtodev.remoteConfig.config['difficulty'].intValue
        // Initiate the tutorial by using the remote values (difficulty and stepCount)
        runTutorial(stepCount, difficulty);
    }
    // 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
    -(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()
    }

    Basic methods

    Please take a look at our Expert tips before integrating the events.

    Real Payment

    To track payments in a real currency, dispatch this event right after the system validates that the payment went through successfully. The event is fundamental and mandatory for all the app metrics related to monetization.

    For purchases made through the App Store and Google Play Market, for in-app payments is available starting from SDK version 2.5.0 and higher. To enable automatic data collection, provide valid credentials in the settings section under Payments integration → IAP auto tracking. Once the credentials are successfully verified, all new purchases made in your application will be automatically sent as a Real Payment event (with the source specified as auto).

    We recommend avoiding simultaneous event sending through automatic tracking and manual submission of the basic Real Payment event, as this may lead to data duplication.

    Parameter
    Type
    Restrictions
    Description

    By default (easy to change in the app’s settings) devtodev server invalidates transactions with previously-used identifiers. Besides, the server performs identifier checks by its outer appearance in order to avoid obvious fraud.

    If you want to exclude fraud payments from your reports altogether, before creating ‘Real Currency Payment’ event, use devtodev anti-cheat feature.

    Custom Events

    If you want to track non-basic events, you can create custom events of your own. How you are going to apply them, depends solely on you.

    Attention! We strongly recommend that you do not use custom event properties to transfer and store data that fits the definition of !

    If you want to pass custom parameters, use DTDCustomEventParameters class instance.

    Parameter
    Type
    Restrictions
    Description

    devtodev supports no more than 300 custom event names in a single project (see ). Events that exceed the limit of custom event names will be discarded. Try to integrate the tracked actions by type to the event name level, and move the characteristic tags in the parameters.

    For example, if you need to track purchasing “Paper” and “Pen” items, then you don’t need to create two events with the names “Paper Purchase” and “Pen Purchase”. Create a “Purchase” event and add an “Item” parameter to it with the appropriate “Paper” or “Pen” value. This way, you can use just one event to track many items.

    For a string parameter, you can use no more than 50,000 unique values ​​for the entire history of events. If the number of unique values exceeds the limit, the parameter gets locked by the system and gets discarded from the received data. Therefore, we don’t recommend using highly variable parameters like user IDs or time as string values ​​(moreover, they are automatically added to the event).

    Subscriptions

    The described method is available beginning with version 2.1.0!

    Tracking of subscriptions is now available for Apple App Store and Google Play only.

    Please note that in order to track subscriptions, you need to do the following:

    1. Call the subscriptionPayment method (described below)

    Onboarding (tutorial)

    The event allows you to track tutorial completion and identify the stages where you lose new users.

    We recommend tracking the starting point (value -1) before beginning the first tutorial stage, then passing the counting number of every completed stage after its completion (integers larger than 0), and at the end, marking the moment of the last tutorial stage completion (value -2).

    If your app has an option of skipping the tutorial and the user has used it, then it’s necessary to send a refusal value (value 0) only.

    The method takes on the step value with an integer type.

    Value
    Meaning

    Level up

    This event is for games only. It is worthwhile to integrate this event into a game type project, as specified in the . In projects with the “app” type, game events will not be tracked and displayed in the interface, even if they are integrated. You can verify and change the project type in Settings → .

    The event allows you to analyze the distribution of players over different game levels, monitor the in-game currency balance by levels. You can find more information about the right moment to use LevelUp event .

    The event should be dispatched right after the level-up. The number of the level reached is passed to the level parameter.

    To monitor the average account balance of in-game currency by the end of each level, dispatch in-game currencies (resources) names and their amounts to the method signature:

    Attention! The number of tracked in-game currencies or resources (their unique names) should not exceed 30 at all times.

    Parameter
    Type

    Current Balance

    This event is for games only. It is worthwhile to integrate this event into a game type project, as specified in the . In projects with the “app” type, game events will not be tracked and displayed in the interface, even if they are integrated. You can verify and change the project type in Settings → .

    To track the average balance of in-game currency disregarding the level up event, pass the list of in-game currency (resource) names and their amount to the method signature:

    Currency Accrual

    This event is for games only. It is worthwhile to integrate this event into a game type project, as specified in the . In projects with the “app” type, game events will not be tracked and displayed in the interface, even if they are integrated. You can verify and change the project type in Settings → .

    You need to dispatch the event after every game account balance refill if you want to track the average in-game currency amount acquired or earned by the players for a certain timeframe or during a level playthrough.

    Parameter
    Type
    Restrictions
    Description

    Virtual Currency Payment

    This event is for games only. It is worthwhile to integrate this event into a game type project, as specified in the . In projects with the “app” type, game events will not be tracked and displayed in the interface, even if they are integrated. You can verify and change the project type in Settings → .

    Pass this event after every purchase if you want to track in-game currency spends and items’ popularity. You can apply this event to both games and any apps with virtual currency.

    In case the item is sold for more than one currency/resource, you need to build a dictionary with all the names and amounts of the currencies/resources.

    Parameter
    Type
    Restrictions
    Description

    Progression event

    This event is for games only. It is worthwhile to integrate this event into a game type project, as specified in the . In projects with the “app” type, game events will not be tracked and displayed in the interface, even if they are integrated. You can verify and change the project type in Settings → .

    First of all, the progression event is used in games with short (within one game session) areas/game levels, e.g. match 3 games. You can use the event to collect data on how well or how fast users complete levels, how difficult it is for them, how many resources they gained or spent, and other parameters.

    There are two methods in working with progression event:

    • startProgressionEvent

    • finishProgressionEvent

    When a player spawns at a location, the following method is called:

    The user can only be in one area at a time. When moving to another area (including nested ones), the previous location must be completed. Information about events that have not been completed by calling the finishProgressionEvent method during a game session (the finishProgressionEvent method call is not integrated, or the user uses the cached app, or the app has crashed) is not included in the statistics.

    If you want to use this event to track actions that take more than one game session, you can prepare the required data and call both methods when the action is completed (successfully or unsuccessfully). For example, you can use this event to track the main questline.

    price

    double

    from Double.min to Double.max

    The item price in the transaction currency.

    productId

    string

    from 1 to 255 symbols

    Item name. We recommend using a bundle or names in the same language.

    [DTDAnalytics realCurrencyPaymentWithOrderId:@"Order ID"
                                           price:12.5
                                       productId:@"Product ID"
                                    currencyCode:@"USD"];
    Parameter
    Type
    Restrictions
    Description

    orderId

    NSString

    from 1 to 65 symbols

    A unique transaction ID. Use transactionIdentifier property value in SKPaymentTransaction object in a complete transaction receipt.

    currencyCode

    NSString

    precisely 3 symbols

    Transaction currency () e.g. USD, EUR etc.

    DTDAnalytics.realCurrencyPayment(
        orderId = "Order ID",
        price = 12.5,
        productId = "Product ID",
        currencyCode = "USD"
    )
    Parameter
    Type
    Restrictions
    Description

    orderId

    string

    from 1 to 65 symbols

    A unique transaction ID.

    currencyCode

    string

    precisely 3 symbols

    Transaction currency () e.g. USD, EUR etc.

    How to find the transaction ID in GooglePlay transaction?

    Find the INAPP_PURCHASE_DATA object In the JSON fields that are returned in the response data for a purchase order. A unique transaction identifier is the value of orderId property in INAPP_PURCHASE_DATA object. If the order is a test purchase made via the In-app Billing Sandbox, orderId property will be empty.

    Parameter
    Type
    Restrictions
    Description

    orderId

    string

    from 1 to 65 symbols

    A unique transaction ID.

    currencyCode

    string

    precisely 3 symbols

    Transaction currency () e.g. USD, EUR etc.

    How to find the transaction ID in GooglePlay transaction?

    Find the INAPP_PURCHASE_DATA object In the JSON fields that are returned in the response data for a purchase order. A unique transaction identifier is the value of orderId property in INAPP_PURCHASE_DATA object. If the order is a test purchase made via the In-app Billing Sandbox, orderId property will be empty.

    Parameter
    Type
    Restrictions
    Description

    orderId

    string

    from 1 to 65 symbols

    A unique transaction ID.

    currencyCode

    string

    precisely 3 symbols

    Transaction currency () e.g. USD, EUR etc.

    Parameter
    Type
    Restrictions
    Description

    orderId

    string

    from 1 to 65 symbols

    A unique transaction ID.

    currencyCode

    string

    precisely 3 symbols

    Transaction currency () e.g. USD, EUR etc.

    Parameter
    Type
    Restrictions
    Description

    orderId

    string

    from 1 to 65 symbols

    A unique transaction ID.

    currencyCode

    string

    precisely 3 symbols

    Transaction currency () e.g. USD, EUR etc.

    Blueprint
    Parameter
    Type
    Restrictions
    Description

    orderId

    FString

    from 1 to 65 symbols

    A unique transaction ID.

    currencyCode

    FString

    precisely 3 symbols

    Parameter
    Type
    Restrictions
    Description

    orderId

    String

    from 1 to 65 symbols

    A unique transaction ID.

    currencyCode

    String

    precisely 3 symbols

    Transaction currency () e.g. USD, EUR etc.

    key - from 1 to 32 symbols

    value - see below

    Custom event parameters.

    The following data types can be passed using the DTDCustomEventParameters object:

    Type
    Restrictions

    int

    from Int64.min to Int64.max

    string

    from 1 to 255 symbols

    bool

    true/false

    double

    from Double.min to Double.max

    If you want to pass custom parameters, use DTDCustomEventParameters class instance.

    Parameter
    Type
    Restrictions
    Description

    eventName

    NSString

    from 1 to 72 symbols

    Custom event name.

    parameters

    The following data types can be passed using the DTDCustomEventParameters object:

    Type
    Restrictions

    If you want to pass custom parameters, use DTDCustomEventParameters class instance.

    Parameter
    Type
    Restrictions
    Description

    eventName

    string

    from 1 to 72 symbols

    Custom event name.

    parameters

    The following data types can be passed using the DTDCustomEventParameters object:

    Type
    Restrictions

    If you want to pass custom parameters, use DTDCustomEventParameters class instance.

    Parameter
    Type
    Restrictions
    Description

    eventName

    string

    from 1 to 72 symbols

    Custom event name.

    parameters

    The following data types can be passed using the DTDCustomEventParameters object:

    Type
    Restrictions

    If you want to pass custom parameters, use DTDCustomEventParameters class instance.

    Parameter
    Type
    Restrictions
    Description

    eventName

    string

    from 1 to 72 symbols

    Custom event name.

    parameters

    The following data types can be passed using the DTDCustomEventParameters object:

    Type
    Restrictions

    If you want to pass custom parameters, use DTDCustomEventParameters class instance.

    Parameter
    Type
    Restrictions
    Description

    eventName

    string

    from 1 to 72 symbols

    Custom event name.

    parameters

    The following data types can be passed using the DTDCustomEventParameters object:

    Type
    Restrictions

    If you want to pass custom parameters, use an object with parameters.

    Parameter
    Type
    Restrictions
    Description

    eventName

    string

    from 1 to 72 symbols

    Custom event name.

    parameters

    object

    key - from 1 to 32 symbols

    value - see below

    The following data types can be passed using parameters object:

    Type
    Restrictions
    Blueprint

    Parameter
    Type
    Restrictions
    Description

    eventName

    FString

    from 1 to 72 symbols

    Custom event name.

    If you want to pass custom parameters, use:

    Parameter
    Type
    Restrictions
    Description

    The following data types can be passed using the DTDCustomEventParameters object:

    Type
    Restrictions

    If you want to pass custom parameters, use GDDTDCustomEventParameters class instance.

    Parameter
    Type
    Restrictions
    Description

    eventName

    string

    from 1 to 72 symbols

    Custom event name.

    parameters

    The following data types can be passed using the GDDTDCustomEventParameters object:

    Type
    Restrictions

    We strongly recommend that you do not change the data type passed in the same parameter. If you change the data type in a parameter, it will be duplicated with the same name, which may cause issues while processing reports.

    Configure data transfer from the App Store Connect
  • Configure integration in the devtodev application settings

  • To track your income from subscriptions, you need to call the following method at the moment of the subscription purchase even if the user signed up for a trial subscription: func subscriptionPayment(transaction: SKPaymentTransaction, product: SKProduct).

    For example:

    Further user actions - renewal, unsubscription, etc. are tracked by using the data received from AppStore in the server-server format. You will need the corresponding setting for it.

    Also, if you want to track changes in the status of the subscriptions purchased before devtodev SDK 2.0 integration, you need to transfer your history of previously purchased subscriptions to devtodev.

    The SDK monitors the need for historical data to avoid sending out excessive queries to App Store. Use the DTDAnalytics.isRestoreTransactionHistoryRequiredmethod to check whether or not there is a need in sending out the information about the previously purchased subscriptions to devtodev. The method returns BOOL value.

    An example of a purchase history query with verification of the need for it:

    Use the DTDAnalytics.subscriptionHistory method to transfer the list of previously purchased subscriptions received from App Store.

    If your project accounts users by user ID (not by device ID) and the device is used by more than one user, you need to filter the transaction history so that it will contain only those transactions that belong to the active user. Otherwise, subscriptions of all device users will be attributed to the user who was the first to launch the app after the integration of subscription tracking.

    To recover the purchase history, the user should be logged in with his Apple ID. Be mindful of this before starting the recovering process.

    Please note that in order to track subscriptions, you need to do the following:

    1. Call the subscriptionPayment method (described below)

    To track your income from subscriptions, you need to call the following method at the moment of the subscription purchase even if the user signed up for a trial subscription: func subscriptionPayment(transaction: Transaction, product: Product).

    For example:

    Fork with Transaction.updates:

    Further user actions - renewal, unsubscription, etc. are tracked by using the data received from AppStore in the server-server format. You will need the corresponding setting for it.

    Also, if you want to track changes in the status of the subscriptions purchased before devtodev SDK 2.0 integration, you need to transfer your history of previously purchased subscriptions to devtodev.

    The SDK monitors the need for historical data to avoid sending out excessive queries to App Store. Use the DTDAnalytics.isRestoreTransactionHistoryRequiredmethod to check whether or not there is a need in sending out the information about the previously purchased subscriptions to devtodev. The method returns BOOL value.

    An example of a purchase history query with verification of the need for it:

    Use the DTDAnalytics.subscriptionHistory method to transfer the list of previously purchased subscriptions received from App Store.

    If your project accounts users by user ID (not by device ID) and the device is used by more than one user, you need to filter the transaction history so that it will contain only those transactions that belong to the active user. Otherwise, subscriptions of all device users will be attributed to the user who was the first to launch the app after the integration of subscription tracking.

    To recover the purchase history, the user should be logged in with his Apple ID. Be mindful of this before starting the recovering process.

    Please note that in order to track subscriptions, you need to do the following:

    1. Call the subscriptionPayment method (described below)

    To track your income from subscriptions, you need to call the following method at the moment of the subscription purchase even if the user signed up for a trial subscription: (void)subscriptionPaymentWithTransaction:(SKPaymentTransaction * _Nonnull)transaction product:(SKProduct * _Nonnull)product;

    For example:

    Further user actions - renewal, unsubscription, etc. are tracked by using the data received from AppStore in the server-server format. You will need the corresponding setting for it.

    Also, if you want to track changes in the status of the subscriptions purchased before devtodev SDK 2.0 integration, you need to transfer your history of previously purchased subscriptions to devtodev.

    The SDK monitors the need for historical data to avoid sending out excessive queries to App Store. Use the (void)isRestoreTransactionHistoryRequiredWithCompletionHandler:( void (^ _Nonnull)(BOOL))completionHandler; method to check whether or not there is a need in sending out the information about the previously purchased subscriptions to devtodev. The method returns BOOL value.

    An example of a purchase history query with verification of the need for it:

    Use the (void)subscriptionHistoryWithTransactions:(NSArray<SKPaymentTransaction *> * _Nonnull)transactions; method to transfer the list of previously purchased subscriptions received from App Store.

    If your project accounts users by user ID (not by device ID) and the device is used by more than one user, you need to filter the transaction history so that it will contain only those transactions that belong to the active user. Otherwise, subscriptions of all device users will be attributed to the user who was the first to launch the app after the integration of subscription tracking.

    To recover the purchase history, the user should be logged in with his Apple ID. Be mindful of this before starting the recovering process.

    Please note that in order to track subscriptions, you need to do the following:

    1. Call the subscriptionPayment method (described below)

    Before sending a request for subscription from your app, during the creation of BillingFlowParams, to the setObfuscatedAccountId function of the BillingFlowParams.newBuilder() object insert obfuscatedAccountId obtained from DTDAnalytics.getObfuscatedAccountId.

    Attention! The obfuscated identifier is returned asynchronously, outside of the calling thread!

    Example:

    To track your subscriptions, add this event immediately after the platform confirms that the subscription was approved by the user.

    How to find the transaction ID in GooglePlay transaction?

    Find the INAPP_PURCHASE_DATA object In the JSON fields that are returned in the response data for a purchase order. A unique transaction identifier is the value of orderId property in INAPP_PURCHASE_DATA object. If the order is a test purchase made via the In-app Billing Sandbox, orderId property will be empty.

    Further user actions - renewal, unsubscription, etc. are tracked by using the data received from Google Play in the server-server format. You will need the corresponding setting for it.

    The subscriptionHistory method is used for matching users with subscribers who purchased their subscriptions before the SDK 2.0 integration. Otherwise, it will be impossible to establish the affiliation when it gets renewed or cancelled.

    To get a list of active subscriptions call billingClient.queryPurchasesAsync. After successfully receiving a response from Google Play Services, pass it to DTDAnalytics.subscriptionHistory(purchaseList: List<String>).

    purchaseList: List<String> purchaseList - a string containing list of json objects is passed to the DTDAnalytics.subscriptionHistory method. For the event to run, the json object must contain the following keys:

    • orderID - a unique transaction identifier

    • productID - a unique product identifier

    The SDK monitors the need for historical data to avoid sending out excessive queries. Use the DTDAnalytics.isRestoreTransactionHistoryRequired method to check whether or not there is a need in sending out the information about the previously purchased subscriptions to devtodev. The method returns a Boolean value.

    Attention! DTDAnalytics.isRestoreTransactionHistoryRequired is returned asynchronously, outside of the calling thread!

    Example:

    If your project accounts users by user ID (not by device ID) and the device is used by more than one user, you need to filter the transaction history so that it will contain only those transactions that belong to the active user. Otherwise, subscriptions of all device users will be attributed to the user who was the first to launch the app after the integration of subscription tracking.

    Please note that in order to track subscriptions, you need to do the following:

    1. Call the subscriptionPayment method (described below)

    Before sending a request for subscription from your app, during the creation of BillingFlowParams, to the setObfuscatedAccountId function of the BillingFlowParams.newBuilder() object insert obfuscatedAccountId obtained from DTDAnalytics.INSTANCE.getObfuscatedAccountId.

    Attention! The obfuscated identifier is returned asynchronously, outside of the calling thread!

    Example:

    To track your subscriptions, add this event immediately after the platform confirms that the subscription was approved by the user.

    How to find the transaction ID in GooglePlay transaction?

    Find the INAPP_PURCHASE_DATA object In the JSON fields that are returned in the response data for a purchase order. A unique transaction identifier is the value of orderId property in INAPP_PURCHASE_DATA object. If the order is a test purchase made via the In-app Billing Sandbox, orderId property will be empty.

    Further user actions - renewal, unsubscription, etc. are tracked by using the data received from Google Play in the server-server format. You will need the corresponding setting for it.

    The subscriptionHistory method is used for matching users with subscribers who purchased their subscriptions before the SDK 2.0 integration. Otherwise, it will be impossible to establish the affiliation when it gets renewed or cancelled.

    To get a list of active subscriptions call billingClient.queryPurchasesAsync. After successfully receiving a response from Google Play Services, pass it to DTDAnalytics.subscriptionHistory(purchaseList: List<String>).

    purchaseList: List<String> purchaseList - a string containing list of json objects is passed to the DTDAnalytics.INSTANCE.subscriptionHistory method. For the event to run, the json object must contain the following keys:

    • orderID - a unique transaction identifier

    • productID - a unique product identifier

    The SDK monitors the need for historical data to avoid sending out excessive queries. Use the DTDAnalytics.INSTANCE.isRestoreTransactionHistoryRequired method to check whether or not there is a need in sending out the information about the previously purchased subscriptions to devtodev. The method returns a Boolean value.

    Attention! DTDAnalytics.INSTANCE.isRestoreTransactionHistoryRequired is returned asynchronously, outside of the calling thread!

    Example:

    If your project accounts users by user ID (not by device ID) and the device is used by more than one user, you need to filter the transaction history so that it will contain only those transactions that belong to the active user. Otherwise, subscriptions of all device users will be attributed to the user who was the first to launch the app after the integration of subscription tracking.

    In order to work with Unity subscriptions, you need to integrate the subscription module to your project. You can do it by manually importing the unitypackage.

    Integration by importing the unitypackage

    1. Download the latest version of devtodev package from the repository:

    2. Import DTDAnalytics.unitypackage to your project

    3. Import DTDSubscriptions.unitypackage to your project.

    For the DTDSubscriptions module to function you need the DTDAnalytics and Unity IAP modules.

    You also need to create an AppleTangle file (only for iOS). Open the Unity editor menu and choose Window → Unity IAP → Receipt Validation Obfuscator (pic. 1).

    In case you don’t use the IAP receipt validation, clear the input field under “2. Paste the key here:” and click Obfuscate Google Play Licence Key (pic. 2).

    • Initialize the DTDAnalytics module (see our ).

    • Add DTDSubscriptions.Initialize(IStoreController controller) to the method.

    • Restore purchase history in order for the module to function correctly.

    • If you use Google Play, after successful IAP initialization call the DTDSubscriptions.History() method.

    To avoid excessive restoring of subscription history, use the DTDSubscriptions.IsRestoreTransactionHistoryRequired(Action<bool> resultCallback) method. If the resultCallback returns true, call the DTDSubscriptions.History() method.

    Example:

    • If you use Apple App Store, first set the DTDSubscriptions.IsRestoring property to true (this will filter out unwanted transactions). After , call the DTDSubscriptions.History() method. After that, set the DTDSubscriptions.IsRestoring property to false.

    Example:

    • Add a DTDSubscriptions.Payment(Product product) call to the ProcessPurchase method.

    Below you can see an example of the entire script:

    The method takes on the step value with an integer type.

    Value
    Meaning

    0

    The user skipped the tutorial

    -1

    The value defines the beginning of the tutorial

    1..int

    Counting number of completed tutorial stage

    -2

    The value defines the completion of the tutorial

    The method takes on the step value with an integer type.

    Value
    Meaning

    0

    The user skipped the tutorial

    -1

    The value defines the beginning of the tutorial

    1..int

    Counting number of completed tutorial stage

    -2

    The value defines the completion of the tutorial

    The method takes on the step value with an integer type.

    Value
    Meaning

    0

    The user skipped the tutorial

    -1

    The value defines the beginning of the tutorial

    1..int

    Counting number of completed tutorial stage

    -2

    The value defines the completion of the tutorial

    The method takes on the step value with an integer type.

    Value
    Meaning

    0

    The user skipped the tutorial

    -1

    The value defines the beginning of the tutorial

    1..int

    Counting number of completed tutorial stage

    -2

    The value defines the completion of the tutorial

    The method takes on the step value with an int base type.

    Value
    Meaning

    0

    The user skipped the tutorial

    -1

    The value defines the beginning of the tutorial

    1..int

    Counting number of completed tutorial stage

    -2

    The value defines the completion of the tutorial

    The method takes on the step value with an integer type.

    Value
    Meaning

    0

    The user skipped the tutorial

    -1

    The value defines the beginning of the tutorial

    1..int

    Counting number of completed tutorial stage

    -2

    The value defines the completion of the tutorial

    Blueprint

    Parameter
    Type
    Restrictions
    Description

    step

    int32

    From 1 to int32.MaxValue - 1

    Tutorial step

    The method takes on the step value with an int32 base type.

    Value
    Meaning

    The method takes on the step value with an integer type.

    Value
    Meaning

    0

    The user skipped the tutorial

    -1

    The value defines the beginning of the tutorial

    1..int

    Counting number of completed tutorial stage

    -2

    The value defines the completion of the tutorial

    Restrictions
    Description

    level

    int

    From 1 to Int32.max - 1

    Level reached

    balances

    [String:Int]

    String - from 1 to 24 symbols

    Int - from Int64.min to int64.max

    Resources’ names and number at the time of level up

    The event should be dispatched right after the level-up. The number of the level reached is passed to the level parameter.

    To monitor the average account balance of in-game currency by the end of each level, dispatch in-game currencies (resources) names and their amounts to the method signature:

    Attention! The number of tracked in-game currencies or resources (their unique names) should not exceed 30 at all times.

    Parameter
    Type
    Restrictions
    Description

    The event should be dispatched right after the level-up. The number of the level reached is passed to the level parameter.

    To monitor the average account balance of in-game currency by the end of each level, dispatch in-game currencies (resources) names and their amounts to the method signature:

    Attention! The number of tracked in-game currencies or resources (their unique names) should not exceed 30 at all times.

    Parameter
    Type
    Restrictions
    Description

    The event should be dispatched right after the level-up. The number of the level reached is passed to the level parameter.

    To monitor the average account balance of in-game currency by the end of each level, dispatch in-game currencies (resources) names and their amounts to the method signature:

    Attention! The number of tracked in-game currencies or resources (their unique names) should not exceed 30 at all times.

    Parameter
    Type
    Restrictions
    Description

    The event should be dispatched right after the level-up. The number of the level reached is passed to the level parameter.

    To monitor the average account balance of in-game currency by the end of each level, dispatch in-game currencies (resources) names and their amounts to the method signature:

    Attention! The number of tracked in-game currencies or resources (their unique names) should not exceed 30 at all times.

    Parameter
    Type
    Restrictions
    Description

    The event should be dispatched right after the level-up. The number of the level reached is passed to the level parameter.

    To monitor the average account balance of in-game currency by the end of each level, dispatch in-game currencies (resources) names and their amounts to the method signature:

    Attention! The number of tracked in-game currencies or resources (their unique names) should not exceed 30 at all times.

    Parameter
    Type
    Restrictions
    Description

    The event should be dispatched right after the level-up. The number of the level reached is passed to the level parameter.

    You can send and track the following data along with the level values: an average amount of the in-game currency by the end of the level, user spendings on the level, and amounts of purchased or earned in-game currency/resources. Unfortunately, Web SDK doesn’t allow to automatically calculate spending and receiving of the in-game currency/resources while users are passing the level (data accumulation on the Web might be inaccurate as users might utilize multiple browsers and devices, as well as erase local browser data).

    Attention! The number of tracked in-game currencies or resources (their unique names) should not exceed 30 at all times.

    Parameter
    Type
    Restrictions
    Description

    The event should be dispatched right after the level-up. The number of the level reached is passed to the level parameter.

    Blueprint

    Parameter
    Type
    Restrictions
    Description

    level

    int32

    To monitor the average account balance of in-game currency by the end of each level, dispatch in-game currencies (resources) names and their amounts to the method signature:

    Attention! The number of tracked in-game currencies or resources (their unique names) should not exceed 30 at all times.

    Parameter
    Type
    Restrictions
    Description

    The event should be dispatched right after the level-up. The number of the level reached is passed to the level parameter.

    To monitor the average account balance of in-game currency by the end of each level, dispatch in-game currencies (resources) names and their amounts to the method signature:

    Attention! The number of tracked in-game currencies or resources (their unique names) should not exceed 30 at all times.

    Parameter
    Type
    Restrictions
    Description
    Parameter
    Type
    Restrictions
    Description

    balance

    TMap<FString, int64>

    FString - from 1 to 24 symbols

    int64 - from int64.MinValue to int64.MaxValue

    Resources’ names and number

    source

    string

    from 1 to 23 symbols

    The sources of currency/resources. It can be used for breaking down income by its sources. For example, a city builder game may have some: “Rent” for the profit received from rental property, or “Bank” if the player has purchased some currency.

    accrualType

    DTDAccrualType (enum)

    The currency/resource source type. The player can either gain resources during the game (earned) or purchase them for money (bought)

    acrualType can receive one of the following values:

    Parameter
    Type
    Restrictions
    Description

    currencyName

    NSString

    from 1 to 24 symbols

    In-game currency/resource name

    currencyAmount

    NSInteger

    from 1 to Int32.max

    Amount of currency in circulation

    acrualType can receive one of the following values:

    Parameter
    Type
    Restrictions
    Description

    currencyName

    string

    from 1 to 24 symbols

    In-game currency/resource name

    currencyAmount

    int

    from 1 to Int.max

    Amount of currency in circulation

    accrualType can receive one of the following values:

    Parameter
    Type
    Restrictions
    Description

    currencyName

    string

    from 1 to 24 symbols

    In-game currency/resource name

    currencyAmount

    int

    from 1 to Int.max

    Amount of currency in circulation

    accrualType can receive one of the following values:

    Parameter
    Type
    Restrictions
    Description

    currencyName

    string

    from 1 to 24 symbols

    In-game currency/resource name

    currencyAmount

    int

    from 1 to Int.MaxValue

    Amount of currency in circulation

    AccrualType can receive one of the following values:

    Parameter
    Type
    Restrictions
    Description

    currencyName

    string

    from 1 to 24 symbols

    In-game currency/resource name

    currencyAmount

    int

    from 1 to Int32.MaxValue

    Amount of currency in circulation

    AccrualType can receive one of the following values:

    Parameter
    Type
    Restrictions
    Description

    currencyName

    string

    from 1 to 24 symbols

    In-game currency/resource name

    currencyAmount

    int

    from 1 to Int.max

    Amount of currency in circulation

    Example:

    Blueprint
    Parameter
    Type
    Restrictions
    Description

    currencyName

    FString

    from 1 to 24 symbols

    In-game currency/resource name

    currencyAmount

    int32

    from 1 to Int32.MaxValue

    Parameter
    Type
    Restrictions
    Description

    currencyName

    String

    from 1 to 24 symbols

    In-game currency/resource name

    currencyAmount

    int

    from 1 to Int32.max

    Amount of currency in circulation

    acrualType can receive one of the following values:

    The name of a resource group. For example, for “Wood” it can be “Construction materials”.

    purchaseAmount

    int

    from 1 to Int32.max

    The number of units of goods purchased.

    purchaseCurrency

    string

    from 1 to 24 symbols

    The name of a currency used for the purchase.

    purchasePrice

    int

    from 1 to Int32.max

    The price of the purchased item in the specified in-game currency.

    In case the item is sold for more than one currency/resource, you need to build a dictionary with all the names and amounts of the currencies/resources.

    Parameter
    Type
    Restrictions
    Description

    purchaseId

    NSString

    from 1 to 32 symbols

    A unique purchase name or ID. Make sure that the names are always in the same language otherwise they will be listed as different items.

    purchaseType

    NSString

    In case the item is sold for more than one currency/resource, you need to build a dictionary with all the names and amounts of the currencies/resources.

    Parameter
    Type
    Restrictions
    Description

    purchaseId

    string

    from 1 to 32 symbols

    A unique purchase name or ID. Make sure that the names are always in the same language otherwise they will be listed as different items.

    purchaseType

    string

    In case the item is sold for more than one currency/resource, you need to build a dictionary with all the names and amounts of the currencies/resources.

    Parameter
    Type
    Restrictions
    Description

    purchaseId

    string

    from 1 to 32 symbols

    A unique purchase name or ID. Make sure that the names are always in the same language otherwise they will be listed as different items.

    purchaseType

    string

    In case the item is sold for more than one currency/resource, you need to build a dictionary with all the names and amounts of the currencies/resources.

    Parameter
    Type
    Restrictions
    Description

    purchaseId

    string

    from 1 to 32 symbols

    A unique purchase name or ID. Make sure that the names are always in the same language otherwise they will be listed as different items.

    purchaseType

    string

    In case the item is sold for more than one currency/resource, you need to build a dictionary with all the names and amounts of the currencies/resources.

    Parameter
    Type
    Restrictions
    Description

    purchaseId

    string

    from 1 to 32 symbols

    A unique purchase name or ID. Make sure that the names are always in the same language otherwise they will be listed as different items.

    purchaseType

    string

    In case the item is sold for more than one currency/resource, you need to build a dictionary with all the names and amounts of the currencies/resources.

    Parameter
    Type
    Restrictions
    Description

    purchaseId

    string

    from 1 to 32 symbols

    A unique purchase name or ID. Make sure that the names are always in the same language otherwise they will be listed as different items.

    purchaseType

    string

    Blueprint
    Parameter
    Type
    Restrictions
    Description

    purchaseId

    FString

    from 1 to 32 symbols

    A unique purchase name or ID. Make sure that the names are always in the same language otherwise they will be listed as different items.

    purchaseType

    In case the item is sold for more than one currency/resource, you need to build a dictionary with all the names (FString) and amounts of the currencies/resources (int32) .

    Parameter
    Type
    Restrictions
    Restrictions

    In case the item is sold for more than one currency/resource, you need to build a dictionary with all the names and amounts of the currencies/resources.

    Parameter
    Type
    Restrictions
    Description

    purchaseId

    String

    from 1 to 32 symbols

    A unique purchase name or ID. Make sure that the names are always in the same language otherwise they will be listed as different items.

    purchaseType

    String

    Parameter
    Type
    Restrictions
    Description

    eventName

    string

    from 1 to 40 symbols

    The name of the event. It is usually the number or the name of the area.

    DTDStartProgressionEventParameters:

    Parameter
    Type
    Restrictions
    Description

    source

    string

    from 1 to 40 symbols

    The name of the previous event used for connecting events together. E.g. a previous area visited by the player.

    difficulty

    int

    from 0 to Int32.max

    An optional difficulty value which is set using the value: setDifficulty(difficulty: Int)

    Once the player completes the location successfully, the following method is called:

    Parameter
    Type
    Restrictions
    Description

    eventName

    string

    from 1 to 40 symbols

    The name of the event. It is usually the number or the name of the area. It’s important to use the name that was specified at the area’s opening.

    DTDFinishProgressionEventParameters:

    Parameter
    Type
    Restrictions
    Description

    successfulCompletion

    bool

    true/false

    The completion event result. ‘True’ if successful, ‘false’ if unsuccessful/lost.

    duration

    int

    from 0 to Int64.max

    Time in seconds taken to complete the area. If not specified, it is automatically calculated as the difference between startProgressionEvent and finishProgressionEvent method calls.

    spent

    There are two methods in working with progression event:

    • startProgressionEvent

    • finishProgressionEvent

    When a player spawns at a location, the following method is called:

    Parameter
    Type
    Restrictions
    Description

    DTDStartProgressionEventParameters:

    Parameter
    Type
    Restrictions
    Description

    Once the player completes the location successfully, the following method is called:

    Parameter
    Type
    Restrictions
    Description

    DTDFinishProgressionEventParameters:

    Parameter
    Type
    Restrictions
    Description

    There are two methods in working with progression event:

    • startProgressionEvent

    • finishProgressionEvent

    When a player spawns at a location, the following method is called:

    Parameter
    Type
    Restrictions
    Description

    DTDStartProgressionEventParameters:

    Parameter
    Type
    Restrictions
    Description

    Once the player completes the location successfully, the following method is called:

    Parameter
    Type
    Restrictions
    Description

    DTDFinishProgressionEventParameters:

    Parameter
    Type
    Restrictions
    Description

    There are two methods in working with progression event:

    • startProgressionEvent

    • finishProgressionEvent

    When a player spawns at a location, the following method is called:

    Parameter
    Type
    Restrictions
    Description

    DTDStartProgressionEventParameters:

    Parameter
    Type
    Restrictions
    Description

    Once the player completes the location successfully, the following method is called:

    Parameter
    Type
    Restrictions
    Description

    DTDFinishProgressionEventParameters:

    Parameter
    Type
    Restrictions
    Description

    There are two methods in working with progression event:

    • StartProgressionEvent

    • FinishProgressionEvent

    When a player spawns at a location, the following method is called:

    Parameter
    Type
    Restrictions
    Description

    DTDStartProgressionEventParameters:

    Parameter
    Type
    Restrictions
    Description

    Once the player completes the location successfully, the following method is called:

    Parameter
    Type
    Restrictions
    Description

    DTDFinishProgressionEventParameters:

    Parameter
    Type
    Restrictions
    Description

    There are two methods in working with progression event:

    • StartProgressionEvent

    • FinishProgressionEvent

    When a player spawns at a location, the following method is called:

    Parameter
    Type
    Restrictions
    Description

    DTDStartProgressionEventParameters:

    Parameter
    Type
    Restrictions
    Description

    Once the player completes the location successfully, the following method is called:

    Parameter
    Type
    Restrictions
    Description

    DTDFinishProgressionEventParameters:

    Parameter
    Type
    Restrictions
    Description

    There are two methods in working with progression event:

    • startProgressionEvent

    • finishProgressionEvent

    When a player spawns at a location, the following method is called:

    Parameter
    Type
    Restrictions
    Description

    startProgressionEvent event parameters:

    Parameter
    Type
    Restrictions
    Description

    Example:

    Once the player completes the location (instead of successfully or not), the following method is called:

    Parameter
    Type
    Restrictions
    Description

    startProgressionEvent event parameters:

    Parameter
    Type
    Restrictions
    Description

    Example:

    There are two methods in working with progression event:

    • startProgressionEvent

    • finishProgressionEvent

    When a player spawns at a location, the following method is called (one of them):

    Blueprint

    Parameter
    Type
    Restrictions
    Description

    Start progression event with parameters:

    Parameter
    Type
    Restrictions
    Description

    FDTDStartProgressionEventParams:

    Parameter
    Type
    Restrictions
    Description

    Example:

    Once the player completes the location (instead of successfully or not), the following method is called (one of them):

    Parameter
    Type
    Restrictions
    Description

    Finish progression event with parameters:

    Parameter
    Type
    Restrictions
    Description

    FDTDFinishProgressionEventParameters:TMap<FString, int64>

    Parameter
    Type
    Restrictions
    Description

    Example:

    There are two methods in working with progression event:

    • StartProgressionEvent

    • FinishProgressionEvent

    When a player spawns at a location, the following method is called:

    Parameter
    Type
    Restrictions
    Description

    GDDTDStartProgressionEventParameters:

    Parameter
    Type
    Restrictions
    Description

    Once the player completes the location successfully, the following method is called:

    Parameter
    Type
    Restrictions
    Description

    GDDTDFinishProgressionEventParameters:

    Parameter
    Type
    Restrictions
    Description

    orderId

    string

    from 1 to 65 symbols

    A unique transaction ID. Use transactionIdentifier property value in SKPaymentTransaction object in a complete transaction receipt.

    currencyCode

    string

    precisely 3 symbols

    Transaction currency (ISO 4217 standard) e.g. USD, EUR etc.

    eventName

    string

    from 1 to 72 symbols

    Custom event name.

    parameters

    0

    The user skipped the tutorial

    -1

    The value defines the beginning of the tutorial

    1..int

    Counting number of completed tutorial stage

    -2

    currencyName

    string

    from 1 to 24 symbols

    In-game currency/resource name

    currencyAmount

    int

    from 1 to Int32.max

    Amount of currency in circulation

    purchaseId

    string

    from 1 to 32 symbols

    A unique purchase name or ID. Make sure that the names are always in the same language otherwise they will be listed as different items.

    purchaseType

    string

    automatic data collection
    personal data
    Limits
    application settings
    General settings
    here
    application settings
    General settings
    application settings
    General settings
    application settings
    General settings
    application settings
    General settings
    Blueprint
    DTDAnalytics.realCurrencyPayment(orderId: "Order ID", 
                                     price: 12.5, 
                                     productId: "Product ID", 
                                     currencyCode: "USD")

    DTDCustomEventParameters

    The value defines the completion of the tutorial

    from 1 to 96 symbols

     DTDAnalytics.INSTANCE.realCurrencyPayment(
            "Order ID",
            12.5,
            "Product ID",
            "USD"
    );
    DTDAnalytics.RealCurrencyPayment(
        orderId: "Order ID",
        price: 12.5,
        productId: "Product ID",
        currencyCode: "USD");
    DTDAnalytics.RealCurrencyPayment(
        orderId: "Order ID",
        price: 12.5,
        productId: "Product ID",
        currencyCode: "USD");
    window.devtodev.realCurrencyPayment(
                                    orderId,
                                    price,
                                    productId,
                                    currencyCode)
    DTDAnalytics.RealCurrencyPayment("orderId", 9.99, "productId", "USD")
    [DTDAnalytics customEvent:@"Event name"];
    DTDCustomEventParameters *parameters = [[DTDCustomEventParameters alloc] init];
    [parameters addString:@"key for string value" value:@"string value"];
    [parameters addInt:@"key for int value" value:10];
    [parameters addBool:@"key for bool value" value:true];
    [parameters addDouble:@"key for double value" value:12.5];
    
    [DTDAnalytics customEvent:@"Event name" withParameters:parameters];
    DTDAnalytics.customEvent(eventName = "Event name")
    let parameters = DTDCustomEventParameters()
    parameters.add(key = "key for string value", value = "string value")
    parameters.add(key = "key for int value", value = 10)
    parameters.add(key = "key for bool value", value = true)
    parameters.add(key = "key for double value", value = 12.5)
    
    DTDAnalytics.customEvent(
        eventName = "Event name", 
        customEventParameters = parameters
    )
    DTDAnalytics.INSTANCE.customEvent("Event name");
    DTDCustomEventParameters parameters = new DTDCustomEventParameters();
    parameters.add("key for string value", "string value");
    parameters.add("key for int value", 10);
    parameters.add("key for bool value", true);
    parameters.add("key for double value", 12.5);
    DTDAnalytics.INSTANCE.customEvent("Event name", parameters);
    DTDAnalytics.CustomEvent(eventName: "Event name");
    var parameters = new DTDCustomEventParameters();
    parameters.Add(key: "key for string value", value: "string value");
    parameters.Add(key: "key for int value", value: 10);
    parameters.Add(key: "key for bool value", value: true);
    parameters.Add(key: "key for double value", value: 12.5);
    DTDAnalytics.CustomEvent(eventName: "Event name", parameters: parameters);
    DTDAnalytics.CustomEvent(eventName: "Event name");
    var parameters = new DTDCustomEventParameters();
    parameters.Add(key: "key for string value", value: "string value");
    parameters.Add(key: "key for int value", value: 10);
    parameters.Add(key: "key for bool value", value: true);
    parameters.Add(key: "key for double value", value: 12.5);
    DTDAnalytics.CustomEvent(eventName: "Event name", parameters: parameters);
    window.devtodev.customEvent(eventName, parameters)
    window.devtodev.customEvent("Event name", {
            "key for string value" : "string value",
            "key for int value": 10,
            "key for bool value": true,
            "key for double value": 12.5
    })
    UDTDAnalyticsBPLibrary::CustomEvent("EventName");
    DTDAnalytics.CustomEvent("CustomEvent")
    var parameters = GDDTDCustomEventParams.new()
    parameters.AddStringValue("str_key", "str_value")
    parameters.AddBoolValue("bool_key", true)
    parameters.AddIntegerValue("int_key", 100)
    parameters.AddFloatValue("float_key", 0.0015)
    DTDAnalytics.CustomEventWithParams("CustomEventWithParams", parameters)
    [DTDAnalytics tutorialStep:1];
    DTDAnalytics.tutorial(step = 1)
    DTDAnalytics.INSTANCE.tutorial(1);
    DTDAnalytics.Tutorial(1);
    DTDAnalytics.Tutorial(1);
    window.devtodev.tutorial(1)
    UDTDAnalyticsBPLibrary::Tutorial(1);
    DTDAnalytics.Tutorial(1)
    [DTDAnalytics levelUp:2];
    NSDictionary *balance = @{@"Currency name 1": @100, @"Currency name 2": @10};
    [DTDAnalytics levelUp:2 withBalances:balance];
    DTDAnalytics.levelUp(level = 2)
    val resources = mapOf("Currency name 1" to 100L, "Currency name 2" to 10L)
    DTDAnalytics.levelUp(
        level = 2, 
        resource = resources
    )
    DTDAnalytics.INSTANCE.levelUp(2);
    Map<String, Long> resources = new HashMap<>();
    resources.put("Currency name 1", 100L);
    resources.put("Currency name 2", 10L);
    DTDAnalytics.INSTANCE.levelUp(2, resources);
    DTDAnalytics.LevelUp(2);
    var balance = new Dictionary<string, long>();
    balance.Add("Currency name 1", 100);
    balance.Add("Currency name 2", 200);
    DTDAnalytics.LevelUp(2, balance);
    DTDAnalytics.LevelUp(level: 2)
    var balance = new Dictionary<string, long>();
    balance.Add("Currency name 1", 100);
    balance.Add("Currency name 2", 200);
    DTDAnalytics.LevelUp(level: 2, resources: balance);
    devtodev.levelUp(2)
    var balance = {
                    "Currency name 1" : 100,
                    "Currency name 2" : 200
    }
    var spent = {
                    "Currency name 2" : 1
    }
    var earned = {
                                    
                    "Currency name 1" : 5
    }
    var bought = {
                    "Currency name 1" : 50,
                    "Currency name 2" : 30
    }
    window.devtodev.levelUp(2, balance, spent,  earned, bought)
    DTDAnalytics.LevelUp(2)
    var resources = GDDTDInt64Resources.new()
    resources.AddValue("level_resource_1", 6000)
    resources.AddValue("level_resource_2", 95001000)
    DTDAnalytics.LevelUpWithBalance(2, resources)
    var resources = GDDTDInt64Resources.new()
    resources.AddValue("current_balance_res_1", 222)
    resources.AddValue("current_balance_res_2", 1500)
    DTDAnalytics.CurrentBalance(resources)
    [DTDAnalytics currencyName:@"Currency name 1"
                  currencyAmount:100
                  source:@"Source name"
                  accrualType:DTDAccrualTypeEarned];
    DTDAnalytics.currencyAccrual(
        currencyName = "Currency name 1",
        currencyAmount = 100,
        source = "Source name",
        DTDAccrualType = DTDAccrualType.Earned
    )
    DTDAnalytics.INSTANCE.currencyAccrual(
            "Currency name 1",
            100, 
            "Source name",
            DTDAccrualType.Earned
    );
    DTDAnalytics.CurrencyAccrual(
        currencyName: "Currency name 1",
        currencyAmount: 100,
        source: "Source name",
        accrualType: DTDAccrualType.Earned); 
    
    DTDAnalytics.CurrencyAccrual(currencyName: "Currency name 1", 
                                 currencyAmount: 100, 
                                 source: "Source name", 
                                 accrualType: DTDAccrualType.Earned)
    
    window.devtodev.currencyAccrual(
                                    currencyName,
                                    currencyAmount,
                                    source,
                                    accrualType)
    DTDAnalytics.CurrencyAccrual("currencyName_1", 500, "store", GDDTDAccrualType.Bought)
    DTDAnalytics.CurrencyAccrual("currencyName_2", 10, "market", GDDTDAccrualType.Earned)
    [DTDAnalytics virtualCurrencyPaymentWithPurchaseId:@"Purchase ID"
                                          purchaseType:@"Purchase type"
                                        purchaseAmount:100
                                         purchasePrice:10
                                      purchaseCurrency:@"Purchase currency"];
    NSDictionary *resources = @{@"Purchase currency name 1": @100,
                                @"Purchase currency name 2": @10};
    [DTDAnalytics virtualCurrencyPaymentWithPurchaseId:@"Purchase ID"
                                          purchaseType:@"Purchase Type"
                                        purchaseAmount:100
                                             resources:resources];
    DTDAnalytics.virtualCurrencyPayment(
        purchaseId = "Purchase ID",
        purchaseType = "Purchase type",
        purchaseAmount = 100,
        purchasePrice = 10,
        purchaseCurrency = "Purchase currency"
    )
    val resources = mapOf(
        "Purchase currency name 1" to 100,
        "purchase Currency name 2" to 10
    )
    ot
    DTDAnalytics.virtualCurrencyPayment(
        purchaseId = "Purchase ID",
        purchaseType = "Purchase Type",
        purchaseAmount = 100,
        map = resources
    )
    DTDAnalytics.INSTANCE.virtualCurrencyPayment(
            "Purchase ID",
            "Purchase type",
            100,
            10,
            "Purchase currency" 
    );
    Map<String, Integer> resources = new HashMap<>();
    resources.put("Purchase currency name 1", 100);
    resources.put("Purchase currency name 2", 10);
    DTDAnalytics.INSTANCE.virtualCurrencyPayment(
            "Purchase ID",
            "Purchase Type",
            100,
            resources
    );
    DTDAnalytics.VirtualCurrencyPayment(
        purchaseId: "Purchase ID",
        purchaseType: "Purchase type",
        purchaseAmount: 100,
        purchasePrice: 10,
        purchaseCurrency: "Purchase currency");
    var resources = new Dictionary<string, int>
    {
        ["Purchase currency name 1"] = 100,
        ["purchase Currency name 2"] = 10
    };
    DTDAnalytics.VirtualCurrencyPayment(
        purchaseId: "Purchase ID",
        purchaseType: "Purchase Type",
        purchaseAmount: 100,
        resources: resources);
    DTDAnalytics.VirtualCurrencyPayment(purchaseId: "Purchase ID",
                                        purchaseType: "Purchase type",
                                        purchaseAmount: 100,
                                        purchasePrice: 10,
                                        purchaseCurrency: "Purchase currency")
    var resources = new Dictionary<string, int>
    {
        ["Purchase currency name 1"] = 100,
        ["purchase Currency name 2"] = 10
    };
    DTDAnalytics.VirtualCurrencyPayment(
        purchaseId: "Purchase ID",
        purchaseType: "Purchase Type",
        purchaseAmount: 100,
        resources: resources);
    window.devtodev.virtualCurrencyPayment(
                                    purchaseId,
                                    purchaseType,
                                    purchaseAmount, 
                                    purchasePrice,
                                    purchaseCurrency)
    var resources = {
                    "Purchase currency name 1": 100,
                    "purchase Currency name 2": 10
    };
    window.devtodev.virtualCurrencyPayment(
                    "Purchase ID",
                    "Purchase type",
                    100, 
                    resources)
    DTDAnalytics.VirtualCurrencyPayment("purchaseID", "purchaseType", 1000, 15, "resource_1")
    var resources = GDDTDInt32Resources.new()
    resources.AddValue("resource_1", 700)
    resources.AddValue("resource_2", 100)
    DTDAnalytics.VirtualCurrencyPaymentWithResources("purchaseID", "purchaseType", 500, resources)
    DTDStartProgressionEventParameters * parameters = [[DTDStartProgressionEventParameters alloc] init];
    parameters.source = @"Source";
    [parameters setDifficultyWithDifficulty:10];
    
    [DTDAnalytics startProgressionEvent:@"Progression event name" withParameters:parameters];
    let parameters = DTDStartProgressionEventParameters()
    parameters.source = "Source"
    parameters.setDifficulty(difficulty = 10)
    
    DTDAnalytics.startProgressionEvent(
        eventName = "Progression event name", 
        parameters = parameters
    )
    DTDStartProgressionEventParameters parameters = new DTDStartProgressionEventParameters();
    parameters.setSource("Source");
    parameters.setDifficulty(10);
    DTDAnalytics.INSTANCE.startProgressionEvent("Progression event name", parameters);
    var parameters = new DTDStartProgressionEventParameters();
    parameters.Source = "Source";
    parameters.Difficulty = 10;
    DTDAnalytics.StartProgressionEvent(
        eventName: "Progression event name",
        parameters: parameters);
    var parameters = new DTDStartProgressionEventParameters();
    parameters.Source = "Source";
    parameters.Difficulty = 10;
    DTDAnalytics.StartProgressionEvent(
        eventName: "Progression event name",
        parameters: parameters);
    window.devtodev.startProgressionEvent(eventName, parameters)
    var params = GDDTDStartProgressionEventParams.new()
    params.SetDifficulty(10)
    params.SetSource("source_1")
    
    DTDAnalytics.StartProgressionEventWithParams("ProgressionEventWithParams", params)
    DTDAnalytics.customEvent(eventName: "Event name")
    let parameters = DTDCustomEventParameters()
    parameters.add(key: "key for string value", value: "string value")
    parameters.add(key: "key for int value", value: 10)
    parameters.add(key: "key for bool value", value: true)
    parameters.add(key: "key for double value", value: 12.5)
    
    DTDAnalytics.customEvent(eventName: "Event name", parameters: parameters)
    DTDAnalytics.tutorial(step: 1)
    DTDAnalytics.levelUp(level: 2)
    let balance: [String: Int] = ["Currency name 1": 100, "Currency name 2": 10]
    DTDAnalytics.levelUp(level: 2, balances: balance)
    let balance: [String: Int] = ["Currency name 1": 100, "Currency name 2": 10]
    DTDAnalytics.currentBalance(balance: balance)
     NSDictionary *balance = @{@"Currency name 1": @100, 
                               @"Currency name 2": @10};
     [DTDAnalytics currentBalanceWithBalance:balance];
    val balance = mapOf("Currency name 1" to 100L, 
                        "Currency name 2" to 10L)
    DTDAnalytics.currentBalance(
        balance = balance
    )
    Map<String, Long> balance = new HashMap<>();
    balance.put("Currency name 1", 100L);
    balance.put("Currency name 2", 10L);
    DTDAnalytics.INSTANCE.currentBalance(balance);
    var balance = new Dictionary<string, long>();
    balance.Add("Currency name 1", 100);
    balance.Add("Currency name 2", 200);
    DTDAnalytics.CurrentBalance(balance);
    var balance = new Dictionary<string, long>();
    balance.Add("Currency name 1", 100);
    balance.Add("Currency name 2", 200);
    DTDAnalytics.CurrentBalance(balance);
    var balance = {
                    "Currency name 1" : 100,
                    "Currency name 2" : 10
    }
    window.devtodev.currentBalance(balance);
    DTDAnalytics.currencyAccrual(currencyName: "Currency name 1", 
                                 currencyAmount: 100, 
                                 source: "Source name", 
                                 accrualType: .earned)
    DTDAnalytics.virtualCurrencyPayment(purchaseId: "Purchase ID",
                                        purchaseType: "Purchase type",
                                        purchaseAmount: 100,
                                        purchasePrice: 10,
                                        purchaseCurrency: "Purchase currency")
    let resources: [String: Int] = ["Purchase currency name 1": 100, 
                                    "Purchase currency name 2": 10]
    DTDAnalytics.virtualCurrencyPayment(purchaseId: "Purchase ID",
                                        purchaseType: "Purchase Type",
                                        purchaseAmount: 100,
                                        resources: resources)
    let parameters = DTDStartProgressionEventParameters()
    parameters.source = "Source"
    parameters.setDifficulty(difficulty: 10)
    
    DTDAnalytics.startProgressionEvent(eventName: "Progression event name", 
                                       parameters: parameters)
    extension Purchases: SKPaymentTransactionObserver {
      func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
        for transaction in transactions { 
          switch transaction.transactionState {
          case .purchased:
              // Your code ...
              if let product = products?[transaction.payment.productIdentifier] {
                 DTDAnalytics.subscriptionPayment(transaction: transaction, product: product)
              }
          case .restored:
              // Your code ...
          case .failed:
              // Your code ...
          default:
              // Your code ...
          }
        }
      }
    }
    DTDAnalytics.isRestoreTransactionHistoryRequired { isNeedRestore in
      if isNeedRestore {
        DispatchQueue.main.async {
          SKPaymentQueue.default().restoreCompletedTransactions()
        }
      }
    }
    extension Purchases: SKPaymentTransactionObserver {
      func paymentQueueRestoreCompletedTransactionsFinished(_ queue: SKPaymentQueue) {
        // Your code ...
        let restoredTransactions = queue.transactions.filter { $0.transactionState == .restored }
        DTDAnalytics.subscriptionHistory(transactions: restoredTransactions)
      }
    }
    TMap<FString, int64> balance;
    balance.Add("CurrencyName", 123);
    UDTDAnalyticsBPLibrary::CurrentBalance(balance);
    public enum DTDAccrualType: Int {
        case earned = 0
        case bought = 1
    }
    let parameters = DTDFinishProgressionEventParameters()
    parameters.successfulCompletion = true
    parameters.duration = 100
    parameters.spent = ["currency name 1": 1000,
                        "currency name 2": 50]
    parameters.earned = ["currency name 2": 100]
    
    DTDAnalytics.finishProgressionEvent(eventName: "Progression event name", 
                                        parameters: parameters)

    from Double.min to Double.max

    The item price in the transaction currency.

    productId

    string

    from 1 to 255 symbols

    Item name. We recommend using a bundle or names in the same language.

    from Double.min to Double.max

    The item price in the transaction currency.

    productId

    string

    from 1 to 255 symbols

    Item name. We recommend using a bundle or names in the same language.

    bought

    [string: long]

    String - from 1 to 24 symbols Long - from 0 to Number.MAX_SAFE_INTEGER

    Game currency amount bought during the level. Optional.

    from 1 to int32.MaxValue

    The number of units of goods purchased.

    resources

    TMap<FString, int32>

    FString - from 1 to 24 symbols

    int32 - from 1 to int32.MaxValue

    Map with resources.

    key - from 1 to 24 symbols

    value - from 1 to Int64.max

    Resources consumed during an area completion.

    earned

    NSDictionary<NSString *,NSNumber *>

    key - from 1 to 24 symbols

    value - from 1 to Int64.max

    Resources earned during an area completion.

    key - from 1 to 24 symbols

    value - from 1 to Int64.max

    Resources consumed during an area completion.

    earned

    Map<String, Long>

    key - from 1 to 24 symbols

    value - from 1 to Int64.max

    Resources earned during an area completion.

    key - from 1 to 24 symbols

    value - from 1 to Int64.max

    Resources consumed during an area completion.

    earned

    Map<String, Long>

    key - from 1 to 24 symbols

    value - from 1 to Int64.max

    Resources earned during an area completion.

    key - from 1 to 24 symbols

    value - From 1 to int.MaxValue

    Resources consumed during an area completion.

    earned

    [String: Int]

    key - from 1 to 24 symbols

    value - From 1 to int.MaxValue

    Resources earned during an area completion.

    key - from 1 to 24 symbols

    value - From 1 to Int64.MaxValue

    Resources consumed during an area completion.

    earned

    [String: Int]

    key - from 1 to 24 symbols

    value - From 1 to Int64.MaxValue

    Resources earned during an area completion.

    key - from 1 to 24 symbols

    value - from 1 to Number.MAX_SAFE_INTEGER

    Resources consumed during an area completion.

    earned

    [String: Int]

    key - from 1 to 24 symbols

    value - from 1 to Number.MAX_SAFE_INTEGER

    Resources earned during an area completion.

    key - from 1 to 24 symbols

    value - from 0 to int64.MaxValue

    Resources consumed during an area completion.

    earned

    TMap<FString, int64>

    key - from 1 to 24 symbols

    value - from 0 to int64.MaxValue

    Resources earned during an area completion.

    key - from 1 to 24 symbols

    value - from 1 to Int64.max

    Resources consumed during an area completion.

    earned

    GDDTDInt64Resources

    key - from 1 to 24 symbols

    value - from 1 to Int64.max

    Resources earned during an area completion.

    price

    double

    from Double.min to Double.max

    The item price in the transaction currency.

    productId

    NSString

    from 1 to 255 symbols

    Item name. We recommend using a bundle or names in the same language.

    price

    double

    from Double.min to Double.max

    The item price in the transaction currency.

    productId

    string

    from 1 to 255 symbols

    Item name. We recommend using a bundle or names in the same language.

    price

    double

    from Double.min to Double.max

    The item price in the transaction currency.

    productId

    string

    from 1 to 255 symbols

    Item name. We recommend using a bundle or names in the same language.

    price

    double

    from double.MinValue to double.MaxValue

    The item price in the transaction currency.

    productId

    string

    from 1 to 255 symbols

    Item name. We recommend using a bundle or names in the same language.

    price

    double

    from Double.MinValue to Double.MaxValue

    The item price in the transaction currency.

    productId

    string

    from 1 to 255 symbols

    Item name. We recommend using a bundle or names in the same language.

    price

    double

    from Number.MIN_VALUE to Number.MAX_VALUE

    The item price in the transaction currency.

    productId

    string

    from 1 to 255 symbols

    Item name. We recommend using a bundle or names in the same language.

    Transaction currency (ISO 4217 standard) e.g. USD, EUR etc.

    price

    float

    from float.MinValue to float.MaxValue

    The item price in the transaction currency.

    productId

    FString

    from 1 to 255 symbols

    Item name. We recommend using a bundle or names in the same language.

    price

    Float

    from Double.min to Double.max

    The item price in the transaction currency.

    productId

    String

    from 1 to 255 symbols

    Item name. We recommend using a bundle or names in the same language.

    DTDCustomEventParameters

    key - from 1 to 32 symbols

    value - see below

    Custom event parameters.

    int

    from Int64.min to Int64.max

    string

    from 1 to 255 symbols

    bool

    true/false

    double

    from Double.min to Double.max

    DTDCustomEventParameters

    key - from 1 to 32 symbols

    value - see below

    Custom event parameters.

    Long

    from Long.min to Long.max

    String

    from 1 to 255 symbols

    Boolean

    true/false

    Double

    from Double.min to Double.max

    DTDCustomEventParameters

    key - from 1 to 32 symbols

    value - see below

    Custom event parameters.

    Long

    from Long.min to Long.max

    String

    from 1 to 255 symbols

    Boolean

    true/false

    Double

    from Double.min to Double.max

    DTDCustomEventParameters

    key - from 1 to 32 symbols

    value - see below

    Custom event parameters.

    long

    from long.MinValue to long.MaxValue

    string

    from 1 to 255 symbols

    bool

    true/false

    double

    from double.MinValue to double.MaxValue

    DTDCustomEventParameters

    key - from 1 to 32 symbols

    value - see below

    Custom event parameters.

    long

    from Int64.MinValue to Int64.MaxValue

    string

    from 1 to 255 symbols

    bool

    true/false

    double

    from Double.MinValue to Double.MaxValue

    Custom event parameters.

    long

    from Number.MIN_SAFE_INTEGER to Number.MAX_SAFE_INTEGER

    string

    from 1 to 255 symbols

    bool

    true/false

    double

    from Number.MIN_VALUE to Number.MAX_VALUE

    eventName

    FString

    from 1 to 72 symbols

    Custom event name.

    parameters

    FDTDCustomEventParams

    • StringParameters (TMap<FString, FString>)

    • IntParameters (TMap<FString, int64>)

    • FloatParameters (TMap<FString, float>)

    • BoolParameters (TMap<FString, bool>)

    key - from 1 to 32 symbols

    value - see below

    Custom event parameters.

    Warning: avoid duplicate keys in all dictionaries. Because dictionaries are combined into a generic dictionary [string: any] in native code.

    int64

    from int64.MinValue to int64.MaxValue

    FString

    from 1 to 255 symbols

    bool

    true/false

    float

    from float.MinValue to float.MaxValue

    GDDTDCustomEventParams

    key - from 1 to 32 symbols

    value - see below

    Custom event parameters.

    int

    from Int64.min to Int64.max

    String

    from 1 to 255 symbols

    bool

    true/false

    Float

    from Float.min to Float.max

    Parameter

    Type

    Restrictions

    Description

    orderId

    string

    from 1 to 65 symbols

    A unique transaction identifier.

    currencyCode

    string

    precisely 3 symbols

    Transaction currency (ISO 4217 standard) e.g. USD, EUR etc.

    price

    Parameter

    Type

    Restrictions

    Description

    orderId

    string

    from 1 to 65 symbols

    A unique transaction identifier.

    currencyCode

    string

    precisely 3 symbols

    Transaction currency (ISO 4217 standard) e.g. USD, EUR etc.

    price

    0

    The user skipped the tutorial

    -1

    The value defines the beginning of the tutorial

    1..int32

    Counting number of completed tutorial stage

    -2

    The value defines the completion of the tutorial

    level

    NSInteger

    From 1 to Int32.max - 1

    Level reached

    balances

    NSDictionary<NSString *,NSNumber *>

    String - from 1 to 24 symbols

    Int - from Int64.min to int64.max

    Resources’ names and number at the time of level up

    level

    int

    From 1 to Int.max - 1

    Level reached

    balances

    Map<String,Long>

    String - from 1 to 24 symbols

    Long - from Long.min to Long.max

    Resources’ names and number at the time of level up

    level

    int

    From 1 to Int.max - 1

    Level reached

    balances

    Map<String,Long>

    String - from 1 to 24 symbols

    Long - from Long.min to Long.max

    Resources’ names and number at the time of level up

    level

    int

    From 1 to int.MaxValue - 1

    Level reached

    balances

    [string: long]

    String - from 1 to 24 symbols

    Long - from long.MinValue to long.MaxValue

    Resources’ names and number at the time of level up

    level

    int

    From 1 to int32.MaxValue - 1

    Level reached

    balances

    [string: long]

    String - from 1 to 24 symbols

    Long - from long64.MinValue to long64.MaxValue

    Resources’ names and number at the time of level up

    level

    int

    From 1 to int.MaxValue - 1

    Level reached

    balances

    [string: long]

    String - from 1 to 24 symbols

    Long - from long.MinValue to long.MaxValue

    Resources’ names and number at the time of level up

    spent

    [string: long]

    String - from 1 to 24 symbols Long - from 0 to Number.MAX_SAFE_INTEGER

    Game currency amount spent during the level. Optional.

    earned

    [string: long]

    String - from 1 to 24 symbols Long - from 0 to Number.MAX_SAFE_INTEGER

    From 1 to int32.MaxValue - 1

    Level reached

    level

    int32

    From 1 to int32.MaxValue - 1

    Level reached

    balance

    TMap<FString, int64>

    FString - from 1 to 24 symbols

    int64 - from int64.MinValue to int64.MaxValue

    Resources’ names and number at the time of level up

    level

    int

    From 1 to Int32.max - 1

    Level reached

    balances

    GDDTDInt64Resources

    String - from 1 to 24 symbols

    Int - from Int64.min to int64.max

    Resources’ names and number at the time of level up

    source

    NSString

    from 1 to 23 symbols

    The sources of currency/resources. It can be used for breaking down income by its sources. For example, a city builder game may have some: “Rent” for the profit received from rental property, or “Bank” if the player has purchased some currency.

    accrualType

    DTDAccrualType (enum)

    The currency/resource source type. The player can either gain resources during the game (earned) or purchase them for money (bought)

    source

    string

    from 1 to 23 symbols

    The sources of currency/resources. It can be used for breaking down income by its sources. For example, a city builder game may have some: “Rent” for the profit received from rental property, or “Bank” if the player has purchased some currency.

    accrualType

    DTDAccrualType (enum)

    The currency/resource source type. The player can either gain resources during the game (earned) or purchase them for money (bought)

    source

    string

    from 1 to 23 symbols

    The sources of currency/resources. It can be used for breaking down income by its sources. For example, a city builder game may have some: “Rent” for the profit received from rental property, or “Bank” if the player has purchased some currency.

    accrualType

    DTDAccrualType (enum)

    The currency/resource source type. The player can either gain resources during the game (earned) or purchase them for money (bought)

    source

    string

    from 1 to 23 symbols

    The sources of currency/resources. It can be used for breaking down income by its sources. For example, a city builder game may have some: “Rent” for the profit received from rental property, or “Bank” if the player has purchased some currency.

    accrualType

    DTDAccrualType (enum)

    The currency/resource source type. The player can either gain resources during the game (earned) or purchase them for money (bought)

    source

    string

    from 1 to 23 symbols

    The sources of currency/resources. It can be used for breaking down income by its sources. For example, a city builder game may have some: “Rent” for the profit received from rental property, or “Bank” if the player has purchased some currency.

    accrualType

    DTDAccrualType (enum)

    The currency/resource source type. The player can either gain resources during the game (earned) or purchase them for money (bought)

    source

    string

    from 1 to 23 symbols

    The sources of currency/resources. It can be used for breaking down income by its sources. For example, a city builder game may have some: “Rent” for the profit received from rental property, or “Bank” if the player has purchased some currency.

    accrualType

    int

    The currency/resource source type. The player can either gain resources during the game (0) or purchase them for money (1)

    Amount of currency in circulation

    source

    FString

    from 1 to 23 symbols

    The sources of currency/resources. It can be used for breaking down income by its sources. For example, a city builder game may have some: “Rent” for the profit received from rental property, or “Bank” if the player has purchased some currency.

    accrualType

    EDTDAccrualType

    The currency/resource source type. The player can either gain resources during the game (earned) or purchase them for money (bought)

    source

    String

    from 1 to 23 symbols

    The sources of currency/resources. It can be used for breaking down income by its sources. For example, a city builder game may have some: “Rent” for the profit received from rental property, or “Bank” if the player has purchased some currency.

    accrualType

    GDDTDAccrualType (enum)

    The currency/resource source type. The player can either gain resources during the game (earned) or purchase them for money (bought)

    from 1 to 96 symbols

    The name of a resource group. For example, for “Wood” it can be “Construction materials”.

    purchaseAmount

    NSInteger

    from 1 to Int32.max

    The number of units of goods purchased.

    purchaseCurrency

    NSString

    from 1 to 24 symbols

    The name of a currency used for the purchase.

    purchasePrice

    NSInteger

    from 1 to Int32.max

    The price of the purchased item in the specified in-game currency.

    from 1 to 96 symbols

    The name of a resource group. For example, for “Wood” it can be “Construction materials”.

    purchaseAmount

    int

    from 1 to Int.max

    The number of units of goods purchased.

    purchaseCurrency

    string

    from 1 to 24 symbols

    The name of a currency used for the purchase.

    purchasePrice

    int

    from 1 to Int.max

    The price of the purchased item in the specified in-game currency.

    from 1 to 96 symbols

    The name of a resource group. For example, for “Wood” it can be “Construction materials”.

    purchaseAmount

    int

    from 1 to Int.max

    The number of units of goods purchased.

    purchaseCurrency

    string

    from 1 to 24 symbols

    The name of a currency used for the purchase.

    purchasePrice

    int

    from 1 to Int.max

    The price of the purchased item in the specified in-game currency.

    from 1 to 96 symbols

    The name of a resource group. For example, for “Wood” it can be “Construction materials”.

    purchaseAmount

    int

    from 1 to int.MaxValue

    The number of units of goods purchased.

    purchaseCurrency

    string

    from 1 to 24 symbols

    The name of a currency used for the purchase.

    purchasePrice

    int

    from 1 to int.MaxValue

    The price of the purchased item in the specified in-game currency.

    from 1 to 96 symbols

    The name of a resource group. For example, for “Wood” it can be “Construction materials”.

    purchaseAmount

    int

    from 1 to Int32.MaxValue

    The number of units of goods purchased.

    purchaseCurrency

    string

    from 1 to 24 symbols

    The name of a currency used for the purchase.

    purchasePrice

    int

    from 1 to Int32.MaxValue

    The price of the purchased item in the specified in-game currency.

    from 1 to 96 symbols

    The name of a resource group. For example, for “Wood” it can be “Construction materials”.

    purchaseAmount

    int

    from 1 to Number.MAX_SAFE_INTEGER

    The number of units of goods purchased.

    purchaseCurrency

    string

    from 1 to 24 symbols

    The name of a currency used for the purchase.

    purchasePrice

    int

    from 1 to Number.MAX_SAFE_INTEGER

    The price of the purchased item in the specified in-game currency.

    FString

    from 1 to 96 symbols

    The name of a resource group. For example, for “Wood” it can be “Construction materials”.

    purchaseAmount

    int32

    from 1 to Int32.MaxValue

    The number of units of goods purchased.

    purchaseCurrency

    FString

    from 1 to 24 symbols

    The name of a currency used for the purchase.

    purchasePrice

    int32

    from 1 to Int32.MaxValue

    The price of the purchased item in the specified in-game currency.

    purchaseId

    FString

    from 1 to 32 symbols

    A unique purchase name or ID. Make sure that the names are always in the same language otherwise they will be listed as different items.

    purchaseType

    FString

    from 1 to 96 symbols

    The name of a resource group. For example, for “Wood” it can be “Construction materials”.

    purchaseAmount

    from 1 to 96 symbols

    The name of a resource group. For example, for “Wood” it can be “Construction materials”.

    purchaseAmount

    int

    from 1 to Int32.max

    The number of units of goods purchased.

    purchaseCurrency

    String

    from 1 to 24 symbols

    The name of a currency used for the purchase.

    purchasePrice

    int

    from 1 to Int32.max

    The price of the purchased item in the specified in-game currency.

    [String: Int]

    key - from 1 to 24 symbols

    value - from 1 to Int64.max

    Resources consumed during an area completion.

    earned

    [String: Int]

    key - from 1 to 24 symbols

    value - from 1 to Int64.max

    Resources earned during an area completion.

    eventName

    NSString

    from 1 to 40 symbols

    The name of the event. It is usually the number or the name of the area.

    source

    NSString

    from 1 to 40 symbols

    The name of the previous event used for connecting events together. E.g. a previous area visited by the player.

    difficulty

    NSInteger

    from 0 to Int32.max

    An optional difficulty value which is set using the value: setDifficulty(difficulty: Int)

    eventName

    NSString

    from 1 to 40 symbols

    The name of the event. It is usually the number or the name of the area. It’s important to use the name that was specified at the area’s opening.

    successfulCompletion

    BOOL

    true/false

    The completion event result. ‘True’ if successful, ‘false’ if unsuccessful/lost.

    duration

    NSInteger

    from 0 to Int64.max

    Time in seconds taken to complete the area. If not specified, it is automatically calculated as the difference between startProgressionEvent and finishProgressionEvent method calls.

    spent

    eventName

    string

    from 1 to 40 symbols

    The name of the event. It is usually the number or the name of the area.

    source

    string

    from 1 to 40 symbols

    The name of the previous event used for connecting events together. E.g. a previous area visited by the player.

    difficulty

    int

    from 0 to Int32.max

    An optional difficulty value which is set using the value: setDifficulty(difficulty: Int)

    eventName

    string

    from 1 to 40 symbols

    The name of the event. It is usually the number or the name of the area. It’s important to use the name that was specified at the area’s opening.

    successfulCompletion

    bool

    true/false

    The completion event result. ‘True’ if successful, ‘false’ if unsuccessful/lost.

    duration

    int

    from 0 to Int64.max

    Time in seconds taken to complete the area. If not specified, it is automatically calculated as the difference between startProgressionEvent and finishProgressionEvent method calls.

    spent

    eventName

    string

    from 1 to 40 symbols

    The name of the event. It is usually the number or the name of the area.

    source

    string

    from 1 to 40 symbols

    The name of the previous event used for connecting events together. E.g. a previous area visited by the player.

    difficulty

    int

    from 0 to Int32.max

    An optional difficulty value which is set using the value: setDifficulty(difficulty: Int)

    eventName

    string

    from 1 to 40 symbols

    The name of the event. It is usually the number or the name of the area. It’s important to use the name that was specified at the area’s opening.

    successfulCompletion

    bool

    true/false

    The completion event result. ‘True’ if successful, ‘false’ if unsuccessful/lost.

    duration

    int

    from 0 to Int64.max

    Time in seconds taken to complete the area. If not specified, it is automatically calculated as the difference between startProgressionEvent and finishProgressionEvent method calls.

    spent

    eventName

    string

    from 1 to 40 symbols

    The name of the event. It is usually the number or the name of the area.

    source

    string

    from 1 to 40 symbols

    The name of the previous event used for connecting events together. E.g. a previous area visited by the player.

    difficulty

    int

    from 0 to int.MaxValue

    An optional difficulty value.

    eventName

    string

    from 1 to 40 symbols

    The name of the event. It is usually the number or the name of the area. It’s important to use the name that was specified at the area’s opening.

    successfulCompletion

    bool

    true/false

    The completion event result. ‘True’ if successful, ‘false’ if unsuccessful/lost.

    duration

    int

    from 0 to int.MaxValue

    Time in seconds taken to complete the area. If not specified, it is automatically calculated as the difference between StartProgressionEvent and FinishProgressionEvent method calls.

    spent

    eventName

    string

    from 1 to 40 symbols

    The name of the event. It is usually the number or the name of the area.

    source

    string

    from 1 to 40 symbols

    The name of the previous event used for connecting events together. E.g. a previous area visited by the player.

    difficulty

    int

    from 0 to Int32.MaxValue

    An optional difficulty value.

    eventName

    string

    from 1 to 40 symbols

    The name of the event. It is usually the number or the name of the area. It’s important to use the name that was specified at the area’s opening.

    successfulCompletion

    bool

    true/false

    The completion event result. ‘True’ if successful, ‘false’ if unsuccessful/lost.

    duration

    int

    from 0 to Int64.MaxValue

    Time in seconds taken to complete the area. If not specified, it is automatically calculated as the difference between StartProgressionEvent and FinishProgressionEvent method calls.

    spent

    eventName

    string

    from 1 to 40 symbols

    The name of the event. It is usually the number or the name of the area.

    parameters

    object

    see below

    Location event parameters.

    source

    string

    from 1 to 40 symbols

    The name of the previous event used for connecting events together. E.g. a previous area visited by the player.

    difficulty

    int

    from 0 to Number.MAX_SAFE_INTEGER

    An optional difficulty value.

    eventName

    string

    from 1 to 40 symbols

    The name of the event. It is usually the number or the name of the area. It’s important to use the name that was specified at the area’s opening.

    parameters

    object

    see below

    Location event parameters.

    successfulCompletion

    bool

    true/false

    The completion event result. ‘True’ if successful, ‘false’ if unsuccessful/lost.

    duration

    int

    from 0 to Number.MAX_SAFE_INTEGER

    Time in seconds taken to complete the area. If not specified, it is automatically calculated as the difference between startProgressionEvent and finishProgressionEvent method calls.

    spent

    eventName

    FString

    from 1 to 72 symbols

    Progression event name.

    eventName

    FString

    from 1 to 40 symbols

    The name of the event. It is usually the number or the name of the area.

    params

    FDTDStartProgressionEventParams

    see below

    Start progression event parameters.

    source

    FString

    from 1 to 40 symbols

    The name of the previous event used for connecting events together. E.g. a previous area visited by the player.

    difficulty

    int32

    from 0 to int32.MaxValue

    An optional difficulty value.

    eventName

    FString

    from 1 to 72 symbols

    Progression event name.

    eventName

    FString

    from 1 to 40 symbols

    The name of the event. It is usually the number or the name of the area. It’s important to use the name that was specified at the area’s opening.

    parameters

    FDTDFinishProgressionEventParams

    see below

    Finish progression event parameters.

    successfulCompletion

    bool

    true/false

    The completion event result. ‘True’ if successful, ‘false’ if unsuccessful/lost.

    duration

    int32

    from 0 to int32.MaxValue

    Time in seconds taken to complete the area. If not specified, it is automatically calculated as the difference between startProgressionEvent and finishProgressionEvent method calls.

    spent

    eventName

    String

    from 1 to 40 symbols

    The name of the event. It is usually the number or the name of the area.

    source

    String

    from 1 to 40 symbols

    The name of the previous event used for connecting events together. E.g. a previous area visited by the player.

    difficulty

    int

    from 0 to Int32.max

    An optional difficulty value which is set using the value: SetDifficulty()

    eventName

    string

    from 1 to 40 symbols

    The name of the event. It is usually the number or the name of the area. It’s important to use the name that was specified at the area’s opening.

    successfulCompletion

    bool

    true/false

    The completion event result. ‘True’ if successful, ‘false’ if unsuccessful/lost.

    duration

    int

    from 0 to Int64.max

    Time in seconds taken to complete the area. If not specified, it is automatically calculated as the difference between startProgressionEvent and finishProgressionEvent method calls.

    spent

    Configure data transfer from the App Store Connect
    Configure integration in the devtodev application settings
    Configure data transfer from the App Store Connect
    Configure integration in the devtodev application settings
    Configure data transfer from the Google Cloud Platform
    Configure integration in the devtodev application settings
    Configure data transfer from the Google Cloud Platform
    Configure integration in the devtodev application settings
    https://github.com/devtodev-analytics/Unity-sdk-3.0/releases/latest
    instruction manual
    OnInitialized
    restoring IAP transactions
    ISO 4217 standard
    ISO 4217 standard
    ISO 4217 standard
    ISO 4217 standard
    ISO 4217 standard
    ISO 4217 standard
    ISO 4217 standard
    Blueprint
    Pic. 1
    Pic. 2
    Blueprint
    Blueprint
    Blueprint
    Blueprint
    Blueprint

    double

    double

    Game currency earned during the level. Optional.

    int32

    NSDictionary<NSString *,NSNumber *>

    Map<String, Long>

    Map<String, Long>

    [String: Int]

    [String: Int]

    [String: Int]

    TMap<FString, int64>

    GDDTDInt64Resources

    1UDTDAnalyticsBPLibrary::RealCurrencyPayment("OrderId", 12.5, "ProductId", "USD");
    FDTDCustomEventParams params;
    params.BoolParameters.Add("BoolKey", true);
    params.FloatParameters.Add("FloatKey", 3.3);
    params.IntParameters.Add("IntKey");
    params.StringParameters.Add("StringKey", "StringValue");
    UDTDAnalyticsBPLibrary::CustomEventWithParams("EventName", params);
    func purchase(_ product: Product) async throws {
      let result = try await product.purchase()
    
      switch result {
      case let .success(.verified(transaction)):
        // Successful purchase
        await transaction.finish()
        DTDAnalytics.subscriptionPayment(transaction: transaction, product: product)
    
      case let .success(.unverified(_, error)):
        // Successful purchase but transaction/receipt can't be verified
        // Could be a jailbroken phone
        print(error)
    
      case .pending:
        // Transaction waiting on SCA (Strong Customer Authentication) or
        // approval from Ask to Buy
        break
      case .userCancelled:
        // Do nothing
        break
    
      @unknown default:
        break
      }
    }
    private func listenForTransactions() -> Task<Void, Error> {
      return Task.detached {
          // Iterate through any transactions that don't come from a direct call to `purchase()`.
          for await verificationResult in Transaction.updates {
          guard let case .verified(let transaction) = verificationResult else { return }
          if let revocationDate = transaction.revocationDate {
            // Remove access to the product identified by transaction.productID.
            // Transaction.revocationReason provides details about
            // the revoked transaction.
          } else if let expirationDate = transaction.expirationDate,
                        expirationDate < Date() {
            // Do nothing, this subscription is expired.
            return
          } else if transaction.isUpgraded {
            // Do nothing, there is an active transaction
           // for a higher level of service.
           return
          } else {
           // Provide access to the product identified by
           // transaction.productID.
            if let product = self.products.first(where: { $0.id == transaction.productID }) {
              DTDAnalytics.subscriptionPayment(transaction: transaction, product: product)
            }
          }
        }
      }
    }
    DTDAnalytics.isRestoreTransactionHistoryRequired { [weak self] flag in
      if flag {
        Task {
          await self?.restoreTransactions()
        }
      }
    }
    extension Purchases: SKPaymentTransactionObserver {
    func restoreTransactions() async {
      var transactions: [Transaction] = []
      for await transaction in Transaction.all {
        if case let .verified(verifiedTransaction) = transaction {
          transactions.append(verifiedTransaction)
        }
      }
    
      DTDAnalytics.subscriptionHistory(transactions: transactions)
    }
    - (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions{
        for(SKPaymentTransaction *transaction in transactions) {
            switch(transaction.transactionState){
                case SKPaymentTransactionStatePurchasing: {
                    // Your code ...
                    break;
                }
    
                case SKPaymentTransactionStatePurchased: {
                    // Your code ...
                    SKProduct *product = [_products objectForKey:transaction.payment.productIdentifier];
                    if (product != nil) {
                        [DTDAnalytics subscriptionPaymentWithTransaction:transaction product:product];
                    }
                    break;
                }
    
                case SKPaymentTransactionStateRestored: {
                    // Your code ...
                    break;
                }
    
                case SKPaymentTransactionStateFailed: {
                    // Your code ...
                    break;
                }
                    
                case SKPaymentTransactionStateDeferred: {
                    // Your code ...
                    break;
                }
            }
        }
    }
    [DTDAnalytics isRestoreTransactionHistoryRequiredWithCompletionHandler:^(BOOL isNeedRestore){
      if (isNeedRestore == true) {
        dispatch_async(dispatch_get_main_queue(), ^{
          [SKPaymentQueue.defaultQueue restoreCompletedTransactions];
        });
      }
    }];
    -(void)paymentQueueRestoreCompletedTransactionsFinished:(SKPaymentQueue *)queue {
      NSMutableArray *restoredTransactions = [NSMutableArray new];
        for (SKPaymentTransaction *transaction in queue.transactions) {
          if (transaction.transactionState == SKPaymentTransactionStateRestored) {
            [restoredTransactions addObject:transaction];
          }
        }
      [DTDAnalytics subscriptionHistoryWithTransactions:restoredTransactions];
    }
    DTDAnalytics.getObfuscatedAccountId { obfuscatedAccountId ->
            val flowParams: BillingFlowParams = BillingFlowParams.newBuilder()
                    .setObfuscatedAccountId(obfuscatedAccountId)
                    .build()
                
            val result = billingClient.launchBillingFlow(activity, flowParams)
    }
    DTDAnalytics.subscriptionPayment(orderId: String,
                                       price: Double,
                                   productId: String,
                                currencyCode: String);
    DTDAnalytics.isRestoreTransactionHistoryRequired { isNeedRestore ->
      if(isNeedRestore) {
        billingClient.queryPurchasesAsync(BillingClient.SkuType.SUBS) { billingResult, purchaseList ->
          if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
            val purchases = mutableListOf<String>()
            purchaseList.forEach { purchase -> purchases.add(purchase.originalJson) }
            DTDAnalytics.subscriptionHistory(purchases)
          }
        }
      }
    }
    DTDAnalytics.INSTANCE.getObfuscatedAccountId(obfuscatedAccountId -> {
                  BillingFlowParams flowParams = BillingFlowParams.newBuilder()
                          .setObfuscatedAccountId(obfuscatedAccountId)
                          .build();
                        
                  billingClient.launchBillingFlow(activity, flowParams);
                  return null;
          }
    );
    DTDAnalytics.INSTANCE.subscriptionPayment(orderId: String, price: Double, productId: String, currencyCode: String);
    DTDAnalytics.INSTANCE.isRestoreTransactionHistoryRequired(isNeedRestore -> {
        if (isNeedRestore) {
            billingClient.queryPurchasesAsync(BillingClient.ProductType.SUBS, (billingResult, purchaseList) -> {
                if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
                    ArrayList<String> purchases = new ArrayList<>();
                    purchaseList.forEach(purchase ->
                            purchases.add(purchase.getOriginalJson())
                    );
                    DTDAnalytics.INSTANCE.subscriptionHistory(purchases);
                }
            });
        }
        return null;
    });
    /// <summary>
    /// Your IStoreListener implementation of OnInitialized.
    /// </summary>
    public void OnInitialized(IStoreController controller, IExtensionProvider extensions)
    {
       DTDSubscriptions.Initialize(controller);
    }
    public void OnInitialized(IStoreController controller, IExtensionProvider extensions)
    {
    #if UNITY_ANDROID     
         DTDSubscriptions.Initialize(controller);
         DTDSubscriptions.IsRestoreTransactionHistoryRequired((b) =>
         {
            if(b) DTDSubscriptions.History();
         });
    #endif
    }
    public void OnInitialized(IStoreController controller, IExtensionProvider extensions)
    {
        DTDSubscriptions.Initialize(controller);
    if UNITY_STANDALONE_OSX || UNITY_IOS
        DTDSubscriptions.IsRestoring = true;
        extensions.GetExtension<IAppleExtensions>().RestoreTransactions(result =>
        {
            if (result)
            {
                DTDSubscriptions.IsRestoreTransactionHistoryRequired((b) =>
                {
                    if (b) DTDSubscriptions.History();
                });
            }
            DTDSubscriptions.IsRestoring = false;
        });
    #endif
    }
    public PurchaseProcessingResult ProcessPurchase (PurchaseEventArgs e)
    {
       var product = e.purchasedProduct;
       DTDSubscriptions.Payment(product);
       return PurchaseProcessingResult.Complete;
    }
    using DevToDev.Subscriptions;
    using UnityEngine;
    using UnityEngine.Purchasing;
    
    public class MyIAPManager : IStoreListener
    {
        public IStoreController StoreController { get; private set; }
    
        public void InitializeIAPManager()
        {
            var builder = ConfigurationBuilder.Instance(StandardPurchasingModule.Instance());
            builder.AddProduct("example", ProductType.Subscription);
            UnityPurchasing.Initialize(this, builder);
        }
    
        /// <summary>
        /// Called when Unity IAP is ready to make purchases.
        /// </summary>
        public void OnInitialized(IStoreController controller, IExtensionProvider extensions)
        {
            StoreController = controller;
            DTDSubscriptions.Initialize(controller);
    #if UNITY_ANDROID
            DTDSubscriptions.IsRestoreTransactionHistoryRequired((b) =>
            {
                if (b) DTDSubscriptions.History();
            });
    #elif UNITY_STANDALONE_OSX || UNITY_IOS
            DTDSubscriptions.IsRestoring = true;
            extensions.GetExtension<IAppleExtensions>().RestoreTransactions(result =>
            {
                if (result)
                {
                    DTDSubscriptions.IsRestoreTransactionHistoryRequired((b) =>
                    {
                        if (b) DTDSubscriptions.History();
                    });
                }
                DTDSubscriptions.IsRestoring = false;
            });
    #endif
        }
    
        /// <summary>
        /// Called when Unity IAP encounters an unrecoverable initialization error.
        ///
        /// Note that this will not be called if Internet is unavailable; Unity IAP
        /// will attempt initialization until it becomes available.
        /// </summary>
        public void OnInitializeFailed(InitializationFailureReason error)
        {
            Debug.Log($"IAP initialization error {error.ToString()}");
        }
    
        /// <summary>
        /// Called when a purchase completes.
        ///
        /// May be called at any time after OnInitialized().
        /// </summary>
        public PurchaseProcessingResult ProcessPurchase(PurchaseEventArgs e)
        {
            var product = e.purchasedProduct;
            DTDSubscriptions.Payment(product);
            return PurchaseProcessingResult.Complete;
        }
    
        /// <summary>
        /// Called when a purchase fails.
        /// </summary>
        public void OnPurchaseFailed(Product i, PurchaseFailureReason p)
        {
            Debug.Log(p.ToString());
        }
    }
    UDTDAnalyticsBPLibrary::LevelUp(2);
    TMap<FString, int64> balance;
    balance.Add("CurrencyName", 123);
    UDTDAnalyticsBPLibrary::LevelUpWithBalance(2, balance);
    typedef enum DTDAccrualType: NSUInteger {
    DTDAccrualTypeEarned = 0,
    DTDAccrualTypeBought = 1,
    } DTDAccrualType;
    enum class DTDAccrualType(val value: Long) {
        Earned(0L),
        Bought(1L);
    }
    public final enum class DTDAccrualType {
        Earned;
        Bought;
    }a
    enum DTDAccrualType : long
    {
        Earned = 0L,
        Bought = 1L
    }
    public enum DTDAccrualType
    {
      Earned = 0,
      Bought = 1
    }
    window.devtodev.currencyAccrual(
                                    "Currency name 1",
                                    100,
                                    "Source name",
                                    0)
    UDTDAnalyticsBPLibrary::CurrencyAccrual("CurrencyName", 12, "Source", EDTDAccrualType::Bought);
    enum  AccrualType:
        Earned = 0
        Bought = 1
    UDTDAnalyticsBPLibrary::VirtualCurrencyPayment("PurchaseId", "PurchaseType", 2, 3, "CurrencyName");
    var resources = new Dictionary<string, int>
    {
        ["Purchase currency name 1"] = 100,
        ["purchase Currency name 2"] = 10
    };
    DTDAnalytics.VirtualCurrencyPayment(
        purchaseId: "Purchase ID",
        purchaseType: "Purchase Type",
        purchaseAmount: 100,
        resources: resources);
    DTDFinishProgressionEventParameters *parameters = [[DTDFinishProgressionEventParameters alloc] init];
    parameters.successfulCompletion = true;
    parameters.duration = 100;
    parameters.spent = @{@"currency name 1": @1000,
                         @"currency name 2": @50};
    parameters.earned = @{@"currency name 2": @100};
    
    [DTDAnalytics finishProgressionEvent:@"Progression event name" withParameters:parameters];
    let parameters = DTDFinishProgressionEventParameters()
    parameters.successfulCompletion = true
    parameters.duration = 100
    parameters.spent = ["currency name 1": 1000,
                        "currency name 2": 50]
    parameters.earned = ["currency name 2": 100]
    
    DTDAnalytics.finishProgressionEvent(
        eventName = "Progression event name",
        parameters = parameters
    )
    Map<String, Long> spendMap = new HashMap<>();
    spendMap.put("currency name 1", 1000L);
    spendMap.put("currency name 2", 100L);
    
    Map<String, Long> earnedMap = new HashMap<>();
    spendMap.put("currency name 2", 100L);
    
    DTDFinishProgressionEventParameters param = new DTDFinishProgressionEventParameters();
    param.setSuccessfulCompletion(true);
    param.setDuration(100);
    param.setSpent(spendMap);
    param.setEarned(earnedMap);
    
    DTDAnalytics.INSTANCE.finishProgressionEvent("Progression event name", param);
    var parameters = new DTDFinishProgressionEventParameters();
    parameters.SuccessfulCompletion = true;
    parameters.Duration = 100;
    parameters.Spent = new Dictionary<string, long>
    {
        ["currency name 1"] = 1000,
        ["currency name 2"] = 50
    };
    parameters.Earned = new Dictionary<string, long>
    {
        ["currency name 2"] = 100
    };
    DTDAnalytics.FinishProgressionEvent(
        eventName: "Progression event name",
        parameters: parameters);
    var parameters = new DTDFinishProgressionEventParameters();
    parameters.SuccessfulCompletion = true;
    parameters.Duration = 100;
    parameters.Spent = new Dictionary<string, long>
    {
        ["currency name 1"] = 1000,
        ["currency name 2"] = 50
    };
    parameters.Earned = new Dictionary<string, long>
    {
        ["currency name 2"] = 100
    };
    DTDAnalytics.FinishProgressionEvent(
        eventName: "Progression event name",
        parameters: parameters);
    window.devtodev.startProgressionEvent("Location 11", {
                    difficulty: 10,
                    source: "Location 10"
    })
    window.devtodev.finishProgressionEvent("Progression event name", {
                    successfulCompletion,
                    duration,
                    spent,
                    earned
    })
    window.devtodev.finishProgressionEvent("Location 11", {
                    true,
                    100,
                    spent: {
                                    "currency name 1": 1000,
                                    "currency name 2": 50 
                    },
                    earned: {
                                    "currency name 2": 100
                    }
    })
    UDTDAnalyticsBPLibrary::StartProgressionEvent("EventName");
    StartProgressionEventParams params;
    params.Difficulty = 3;
    params.Source = "Source";
    UDTDAnalyticsBPLibrary::StartProgressionEventWithParams("EventName", params);
    UDTDAnalyticsBPLibrary::FinishProgressionEvent("EventName");window.devtodev.finishProgressionEvent("Progression event name", {
    FDTDFinishProgressionEventParams params;
    params.Duration = 200;
    params.SuccessfulCompletion = true;
    params.Earned.Add("CurrencyName1", 1);
    params.Spent.Add("CurrencyName2", 2);
    UDTDAnalyticsBPLibrary::FinishProgressionEventWithParams("EventName", params);
    var earnedResources = GDDTDInt64Resources.new()
    earnedResources.AddValue("resource_1", 200)
    earnedResources.AddValue("resource_2", 1500)
    	
    var spentResources = GDDTDInt64Resources.new()
    spentResources.AddValue("resource_1", 100)
    spentResources.AddValue("resource_2", 600)
    	
    var params = GDDTDFinishProgressionEventParams.new()
    params.SetSuccessfulCompletion(true)
    params.SetEarnedResources(earnedResources)
    params.SetSpentResources(spentResources)
    params.SetDuration(2000)
    DTDAnalytics.FinishProgressionEventWithParams("ProgressionEventWithParams", params)