Skip to content

deltaDNA/android-sdk

Repository files navigation

deltaDNA logo

deltaDNA Android SDK

Apache2 licensed

Contents

Overview

The deltaDNA SDK allows your Android games to record in-game events and upload player actions. It contains event caching, numerous helper methods, and some automated behaviours to help simplify your integration.

Adding to a project

The deltaDNA SDK can be used in Android projects using minimum SDK version 15 and newer (Android 4.0.3+).

Gradle

In your top-level build script:

allprojects {
    repositories {
        maven {
            url 'https://maven.pkg.github.com/deltaDNA/android-sdk'
            credentials {
                username = "github_username"
                password = "github_personal_access_token"
            }
        }
    }
}

In your app's build script:

dependencies {
    implementation 'com.deltadna.android:deltadna-sdk:5.0.2'
}

The Java source and target compatibility needs to be set to 1.8 in you app's build script:

android {
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
}

Initialising

The SDK needs to be initialised with the following parameters in an Application subclass:

  • Application instance
  • environmentKey, a unique 32 character string assigned to your application. You will be assigned separate application keys for development and production builds of your game. You will need to change the environment key that you initialise the SDK with as you move from development and testing to production.
  • collectUrl, this is the address of the server that will be collecting your events.
  • engageUrl, this is the address of the server that will provide real-time A/B Testing and Targeting.

Note: If the SDK detects that the environment has changed between runs (for example during testing) then the event store will be cleared, to avoid sending events to the wrong environment.

It is a requirement in versions 5.0.0 and above to check if a user is in a location where PIPL consent is required, and to provide that consent if so. This must be done before the SDK will send any events or make any engage requests.

public class MyApplication extends Application {

    @Override
    public void onCreate() {
        super.onCreate();

        DDNA.initialise(new DDNA.Configuration(
                this,
                "environmentKey",
                "collectUrl",
                "engageUrl"));

        DDNA.instance().isPiplConsentRequired(new ConsentTracker.Callback() {
            @Override
            public void onSuccess(boolean requiresConsent) {
                if (requiresConsent) {
                    // Put in a consent flow here to check that the user has given their consent, then update the booleans below to match.
                    DDNA.instance().setPiplConsent(true, true);
                }
            }

            @Override
            public void onFailure(Throwable exception) {
                Log.e("EXAMPLE", "Failed to check for PIPL consent", exception);
                // Try again later.
            }
        });
    }
}

You will need to register your Application subclass in the manifest file

<application
    android:name=".MyApplication"
    ...>
</application>

After the initialise() call the SDK will be available throughout the entire lifecycle of your application by calling DDNA.instance().

You may also set optional attributes on the Configuration, such as the client version, or user id, amongst other options.

Amazon

When building an APK to be distributed on the Amazon Appstore then the platform needs to be changed to the ClientInfo.Platform.AMAZON enum during initialisation

DDNA.initialise(new DDNA.Configuration(
        this,
        "environmentKey",
        "collectUrl",
        "engageUrl")
        .platform(ClientInfo.PLATFORM_AMAZON));

Starting and stopping

Inside of your Activity class you will need to start the SDK with DDNA.instance().startSdk() from the onCreate(Bundle) method, and likewise stop the SDK with DDNA.instance().stopSdk() from the onDestroy() method.

public class MyActivity extends AppCompatActivity {

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        DDNA.instance().startSdk();
    }

    @Override
    public void onDestroy() {
        DDNA.instance().stopSdk();

        super.onDestroy();
    }
}

This is the minimum amount of code needed to initialise the SDK and start sending events. It will automatically send the newPlayer event the first time the SDK is run, and the gameStarted and clientDevice events each time the game runs.

Recording events

Simple event

By using one of the standard event schemas we can record an event such as

DDNA.instance().recordEvent(new Event("options")
        .putParam("option", "Music")
        .putParam("action", "Disabled"));

Which would be uploaded with the following JSON

{
    "eventName": "options",
    "userID": "a2e92bdd-f59d-498f-9385-2ae6ada432e3",
    "sessionID": "0bc56224-8939-4639-b5ba-197f84dad4f4",
    "eventTimestamp":"2014-07-04 11:09:42.491",
    "eventParams": {
        "platform": "ANDROID",
        "sdkVersion": "Android SDK v4.0",
        "option": "Music",
        "action": "Disabled"
    }
}

Complex event

If you create more complicated events which get reused throughout of your game then instead of building up the event each time you can subclass from Event and add your parameters into the constructor

public class MissionStartedEvent extends Event {

    public MissionStartedEvent(
            String name,
            String id,
            boolean isTutorial,
            String difficulty) {

        super("missionStarted")

        putParam("missionName", name)
        putParam("missionID", id)
        putParam("isTutorial", isTutorial)
        putParam("missionDifficulty", difficulty);
    }
}

And you will be able to record events by creating a new instance and passing it to recordEvent(Event)

DDNA.instance().recordEvent(new MissionStartedEvent(
        "Mission01",
        "M001",
        false,
        "EASY"));

Transactions

A transaction is a complex event which introduces nesting, arrays, and some special objects that you will encounter when the player buys, trades, wins, exchanges currency and items with the game or other players. To help with this we provide Transaction, which is an Event with additional properties

recordEvent(new Transaction(
        "IAP - Large Treasure Chest",
        "PURCHASE",
        new Product()
                .addItem("Golden Battle Axe", "Weapon", 1)
                .addItem("Mighty Flaming Sword of the First Age", "Legendary Weapon", 1)
                .addItem("Jewel Encrusted Shield", "Armour", 1)
                .addVirtualCurrency("Gold", "PREMIUM", 100),
        new Product().setRealCurrency("USD", 499))
        .setId("47891208312996456524019-178.149.115.237:51787")
        .setProductId("4019"));

As detailed in the ISO 4217 standard, not all real currencies have 2 minor units and thus require conversion into a common form. The Product.ConvertCurrency() method can be used to ensure the correct currency value is sent.

For example, to track a purchase made with 550 JPÂĄ:

new Product().setRealCurrency("JPY", Product.convertCurrency(DDNA.instance(), "JPY", 550); // realCurrencyAmount: 500

And to track a $4.99 purchase:

new Product().setRealCurrency("USD", Product.convertCurrency(DDNA.instance(), "USD", 4.99f); // realCurrencyAmount: 499

These will be converted automatically into a convertedProductAmount parameter that is used as a common currency for reporting.

Receipt validation can also be performed against purchases made via the Google Play Store. To validate in-app purchases made through the Google Play Store the following parameters should be added to the transaction event:

  • transactionServer - the server for which the receipt should be validated against, in this case 'GOOGLE'
  • transactionReceipt - the purchase data as a string
  • transactionReceiptSignature - the in-app data signature

When a transaction event is received with the above parameters, the receipt will be checked against the store and the resulting event will be tagged with a revenueValidated parameter to allow for the filtering out of invalid revenue.

This event may be more complex, but the structure is logical, flexible, and provides a mechanism for players spending or receiving any combination of currencies and items.

Event triggers

All recordEvent methods return an EventAction instance on which EventActionHandlers can be registered through the add method, for handling triggers which match the conditions setup on the Platform for Event-Triggered Campaigns. You can also add an EventActionEvaluateCompleteHandler to get a callback when once the event is evaluated by using the addEvaluateCompleteHandler method. Once all the handlers have been registered run() needs to be called in order for the event triggers to be evaluated and for a matching handler to be run. This happens on the client without any network use and as such it is instantaneous.

recordEvent(new Event("missionStarted").putParam("missionLevel", 1))
        .add(new EventActionHandler.GameParametersHandler(gameParameters -> {
            // do something with the game parameters
        }))
        .add(new EventActionHandler.ImageMessageHandler(imageMessage -> {
            // the image message is already prepared so it will show instantly
            imageMessage.show(MyActivity.this, MY_REQUEST_CODE);
        }))
        .addEvaluateCompleteHandler(new EventActionEvaluateCompleteHandler() {
                            @Override
                            public void onComplete(Event event) {
                               // do something with the event
                            }
        })
        .run();

In Addition to the above mechanism, default handlers can be specified. These will be used every time run() is called on an EventAction, after any handlers which have been registered via the add method. These should be Specified before the SDK is started so they can be used to handle internal events such as newPlayer and gameStarted but they must be registered after the SDK is initialized. You can specify these handlers like so:

        DDNA.instance().getSettings().setDefaultGameParametersHandler(new EventActionHandler.GameParametersHandler(gameParameters -> {
                    // do something with the game parameters
                })
        );
        DDNA.instance().getSettings().setDefaultImageMessageHandler(new EventActionHandler.ImageMessageHandler(imageMessage -> {
                    // the image message is already prepared so it will show instantly
                    imageMessage.show(ExampleActivity.this, REQUEST_CODE_IMAGE_MSG);
                })
        );

Event Trigger Evaluation Handler

An EventAction instance is returned when recording events that you expect to match the conditions setup in the platform for an Event Triggered Campaign, EventActionHandlers can be registered with the add method through this. There is the option of adding an EventActionEvaluateCompleteHandler to get a callback once the event is evaluated by using the addEvaluateCompleteHandler method. Once all the handlers have been registered .run() needs to be called in order for these event triggers to be evaluated, this happens on the client without any network use and as such, is instantaneous.

        .add(new EventActionHandler.GameParametersHandler(gameParameters -> {
            // do something with the game parameters
        }))
        .add(new EventActionHandler.ImageMessageHandler(imageMessage -> {
            // the image message is already prepared so it will show instantly
            imageMessage.show(MyActivity.this, MY_REQUEST_CODE);
        }))
        .addEvaluateCompleteHandler(new EventActionEvaluateCompleteHandler() {
                            @Override
                            public void onComplete(Event event) {
                               // do something with the event
                            }
        })
        .run();

Engage

An Engage request can be performed by calling requestEngagement(Engagement, EngageListener), providing your Engagement and a an EngageListener for listening to the completion or error.

requestEngagement(
        new Engagement("outOfCredits")
                .putParam("userLevel", 4)
                .putParam("userXP", 1000)
                .putParam("missionName", "Diso Volante"),
        new OutOfCreditsListener());

The Engagement object which was sent will be returned in the listener's onCompleted(Engagement) callback method, at which point it has been populated with data from the platform ready to be retrieved by calling getJson() on the Engagement.

class OutOfCreditsListener implements EngageListener<Engagement> {
    
    public void onCompleted(Engagement engagement) {
        // do something with the result
        if (engagement.isSuccessful()) {
            // for example with parameters
            JSONObject parameters = engagement.getJson()
        }
    }
    
    public void onError(Throwable t) {
        // act on error
    }
}

If there was an error processing your Engage request at the server then the details will be available in the Engagement by calling getError(). Any non-server errors, such as due to an Internet connection not being available, will be propagated into the onError(Throwable) callback method. In this case onCompleted(Engagement) will never be called.

Image Messaging

An Image Messaging request is performed in a similar way to an Engage request with an ImageMessage instance being built up from the returned Engagement in the onCompleted(Engagement) callback method. Since the decision point may not have been set-up to show an Image Message the returned value needs to be null checked.

DDNA.instance().getEngageFactory().requestImageMessage(
        "missionDifficulty",
        new EngageFactory.Callback<ImageMessage>() {
            @Override
            public void onCompleted(@Nullable ImageMessage action) {
                if (action != null) {
                    action.prepare(MyPrepareListener());
                }
            }
        });

An alternative way of requesting an Image Message is by using the DDNA instance directly instead of the EngageFactory.

DDNA.instance().requestEngagement(
        new Engagement("missionDifficulty"),
        new EngageListener<Engagement>() {
            @Override
            public void onComplete(Engagement engagement) {
                ImageMessage imageMessage = ImageMessage.create(engagement);
                if (imageMessage != null) {
                    imageMessage.prepare(MyPrepareListener());
                }
            }
            
            @Override
            public void onError(Throwable t) {
                // act on error
            }
        });

When the onPrepared(ImageMessage) of your ImageMessage.PrepareListener listener gets invoked you may show the Image Message by calling show(Activity, int) on the ImageMessage instance, or not do anything if the application is no longer in a state for showing the Image Message.

class MyPrepareListener implements ImageMessage.PrepareListener {
    
    @Override
    public void onPrepared(ImageMessage src) {
        src.show(MyActivity.this, MY_REQUEST_CODE);
    }
    
    @Override
    public void onError() {
        // act on error
    }
}

To handle the result of the action performed on the Image Message you will need to override the onActivityResult(int, int, Intent) method of your Activity

@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
    if (requestCode == MY_REQUEST_CODE) {
        ImageMessageActivity.handleResult(
                resultCode,
                data,
                new ImageMessageResultListener() {
                    @Override
                    public void onAction(String value, JSONObject params) {
                        // act on action with value/params
                    }
                    
                    @Override
                    public void onLink(String value, JSONObject params) {
                        // act on link action with value/params
                    }
                    
                    @Override
                    public void onStore(String value, JSONObject params) {
                        // act on store action with value/params
                    }
                    
                    @Override
                    public void onCancelled() {
                        // act on cancellation
                    }
                });
    }
}

Cross promotion

To register a user for cross promotion between multiple games the user needs to sign into a service which can provide unique user identification. Once the user has been signed in the ID can be set in the SDK:

DDNA.instance().setCrossGameUserId(crossGameUserId);

On the next session the SDK will download a new configuration with cross promotion campaigns relevant to the user.

When a cross promotion campaign with a store action has been acted on by the user, the SDK will return the store link for the currently set platform:

ImageMessageActivity.handleResult(
        resultCode,
        data,
        new ImageMessageResultListener() {
            @Override
            public void onStore(String value, JSONObject params) {
                // perform an action on 'value', such as opening the store URL
            }
        });

Forgetting a user (GDPR)

If a user no longer wishes to be tracked and would like to be forgotten the forgetMe() API can be used. This will stop the SDK from sending/receiving any further information to/from the Platform, as well as initiating a data deletion request on behalf of the user. The SDK will continue to work as it normally would, without any additional work required. If a user only wants to stop sending new data, consenting to keep already collected data in our system, the stopTrackingMe() method can be used instead. This will function the same as forgetMe(), except the data deletion request will not be sent, thus any data associated with that user will remain on the platform. It is possible to initiate a forgetMe() request after the stopTrackingMe() request if requested by the user.

If the game supports changing of users then calling startSdk(userId) with a new user ID or clearPersistentData() will restore the previous SDK functionality.

Push notifications

The SDK can store the Android Registration Id for the device and send it to deltaDNA so that you may send targeted push notification messages to players.

If your application already handles retrieving of the id then you can set it on the SDK by calling

DDNA.instance().setRegistrationId("your_id");

You may however also make use of the deltadna-sdk-notifications addon which requires less work on your side for refreshing the registration id/token.

If you would like to unregister the client from receiving push notifications then you should call

DDNA.instance().clearRegistrationId();

Settings

If you need further customisation on how the SDK works, such as disabling the automatic event uploads, or changing the number of retries for failed requests then you may do so through the Settings class, which can be retrieved through

DDNA.instance().getSettings();

Settings can also be set during the initialisation step on the Configuration, which is the recommended approach.

ProGuard

There is no need to add additional directives in your ProGuard configuration if you are setting minifyEnabled true for your application as the library provides its own configuration file which gets included by the Android build tools during the build process.

Changelog

Can be found here.

Migrations

  • Version 4.0
  • Version 4.1
  • Version 4.3
  • Version 4.9
    • The SDK has been updated to use Java 8 features, as such projects will need to be updated to use 1.8 for the Java source and target compatibility as per the official documentation.
    • recordEvent methods have been changed to to return an EventAction object, which can be used for Event-Triggered Campaigns. This means that chaining calls on the DDNA SDK instance after calling recordEvent is no longer supported.
  • Version 4.12
    • Critical updates have been made to the Firebase integration in library-notifications. Please follow this guide if you are using push notifications in your project and are updating to this version of the SDK or later.

License

The sources are available under the Apache 2.0 license.

Contact Us

For more information, please visit deltadna.com. For questions or assistance, please email us at support@deltadna.com.