- Overview
- Adding to a project
- Initialising
- Amazon
- Starting and stopping
- Recording events
- Simple event
- Complex event
- Transactions
- Event triggers
- Engage
- Image Messaging
- Cross promotion
- Forgetting a user
- Push notifications
- Settings
- ProGuard
- Changelog
- Migrations
- License
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.
The deltaDNA SDK can be used in Android projects using minimum SDK version 15 and newer (Android 4.0.3+).
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
}
}
The SDK needs to be initialised with the following parameters in an Application
subclass:
Application
instanceenvironmentKey
, 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.
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));
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.
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"
}
}
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"));
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 stringtransactionReceiptSignature
- 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.
All recordEvent
methods return an EventAction
instance on which EventActionHandler
s 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);
})
);
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();
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.
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
}
});
}
}
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
}
});
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.
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();
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.
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.
Can be found here.
- 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 anEventAction
object, which can be used for Event-Triggered Campaigns. This means that chaining calls on theDDNA
SDK instance after callingrecordEvent
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.
The sources are available under the Apache 2.0 license.
For more information, please visit deltadna.com. For questions or assistance, please email us at support@deltadna.com.