Kotlin SDK for integrating with an Optable Data Connectivity Node (DCN) from an Android application.
This SDK is published using JitPack, so to add it to your application build, follow the JitPack How To.
If you're using gradle, add the following in your root build.gradle
at the end of repositories
:
allprojects {
repositories {
...
maven {
url 'https://jitpack.io'
}
}
}
Then add the dependency to the SDK in your app build.gradle
:
dependencies {
implementation 'com.github.Optable:optable-android-sdk:VERSION_TAG'
}
Remember to replace VERSION_TAG
with the latest or desired SDK release.
To configure an instance of the SDK integrating with an Optable DCN running at hostname dcn.customer.com
, from a configured application origin identified by slug my-app
, you can instantiate the SDK from an Activity or Application Context
, such as for example the following application MainActivity
:
import co.optable.android_sdk.OptableSDK
...
class MainActivity : AppCompatActivity() {
companion object {
var OPTABLE: OptableSDK? = null
}
...
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
...
MainActivity.OPTABLE = OptableSDK(this, "dcn.customer.com", "my-app")
...
}
}
import co.optable.android_sdk.OptableSDK;
...
public class MainActivity extends AppCompatActivity {
public static OptableSDK OPTABLE;
...
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
...
MainActivity.OPTABLE = new OptableSDK(this.getApplicationContext(),
"dcn.customer.com", "my-app");
...
}
}
You can then call various SDK APIs on the instance as shown in the examples below. It's also possible to configure multiple instances of OptableSDK
in order to connect to other (e.g., partner) DCNs and/or reference other configured application slug IDs.
Note that all SDK communication with Optable DCNs is done over TLS. The only exception to this is if you instantiate the OptableSDK
class with a fourth optional boolean parameter, insecure
, set to true
. For example, in Kotlin:
MainActivity.OPTABLE = OptableSDK(this, "dcn.customer.com", "my-app", true)
Note that production DCNs only listen to TLS traffic. The insecure: true
option is meant to be used by Optable developers running the DCN locally for testing.
By default, the SDK detects the application user agent by sniffing settings.userAgentString
from a WebView
. The resulting user agent string is sent to your DCN for analytics purposes. To disable this behavior, you can provide an optional fifth string parameter useragent
, which allows you to set whatever user agent string you would like to send instead. For example, in Kotlin:
MainActivity.OPTABLE = OptableSDK(this, "dcn.customer.com", "my-app", false, "custom-ua")
Finally, an optional sixth boolean parameter skipAdvertisingIdDetection
can be used to skip any ID info detection from AdvertisingIdClient
which by default runs in a background co-routine. Disabling ad ID detection means that the SDK will not be able to automatically obtain the Google Advertising ID. For example, to disable ad ID detection, in Kotlin:
MainActivity.OPTABLE = OptableSDK(this, "dcn.customer.com", "my-app", false, null, true)
To associate a user device with an authenticated identifier such as an Email address, or with other known IDs such as the Google Advertising ID, or even your own vendor or app level PPID
, you can call the identify
API as follows:
import co.optable.android_sdk.OptableSDK
import my.org.app.MainActivity
import android.util.Log
...
val emailString = "some.email@address.com"
val sendGoogleAdIDBoolean = true
val ppid = "my-id-123"
MainActivity.OPTABLE!!
.identify(emailString, sendGoogleAdIDBoolean, ppid)
.observe(viewLifecycleOwner, Observer
{ result ->
if (result.status == OptableSDK.Status.SUCCESS) {
Log.i("Identify API Success")
} else {
// result.status is OptableSDK.Status.ERROR
// result.message is the error message
Log.e("Identify API Error: ${result.message}")
}
})
import co.optable.android_sdk.OptableSDK;
import co.optable.demoappjava.MainActivity;
import android.util.Log;
...
String emailString = "some.email@address.com";
Boolean sendGoogleAdIDBoolean = true;
String ppid = "my-id-123";
MainActivity.OPTABLE
.identify(emailString, sendGoogleAdIDBoolean, ppid)
.observe(getViewLifecycleOwner(), result -> {
if (result.getStatus() == OptableSDK.Status.SUCCESS) {
Log.i(null, "Identify API Success");
} else {
// result.getStatus() is OptableSDK.Status.ERROR
// result.getMessage() is the error message
Log.e(null, "Identify API Error: " + result.getMessage());
}
});
The SDK identify()
method will asynchronously connect to the configured DCN and send IDs for resolution. The second (sendGoogleAdIDBoolean
) and third (ppid
) arguments to identify()
are optional. You can register an observer to understand successful completion or errors.
⚠️ Client-Side Email Hashing: The SDK will compute the SHA-256 hash of the Email address on the client-side and send the hashed value to the DCN. The Email address is not sent by the device in plain text.
Since the sendGoogleAdIDBoolean
value provided to identify()
is true
, the SDK will fetch and send the Google Advertising ID in the call to identify
too, unless the user has turned on "Limit ad tracking" in their Google device advertising settings.
The frequency of invocation of identify
is up to you, however for optimal identity resolution we recommended to call the identify()
method on your SDK instance every time you authenticate a user, as well as periodically, such as for example once every 15 to 60 minutes while the application is being actively used and an internet connection is available.
To associate key value traits with the user's device, for eventual audience assembly, you can call the profile API as follows:
import co.optable.android_sdk.OptableSDK
import my.org.app.MainActivity
import android.util.Log
...
MainActivity.OPTABLE!!
.profile(hashMapOf("gender" to "F", "age" to 38, "hasAccount" to true))
.observe(viewLifecycleOwner, Observer { result ->
if (result.status == OptableSDK.Status.SUCCESS) {
Log.i("Profile API Success... ")
} else {
// result.status is OptableSDK.Status.ERROR
// result.message is the error message
Log.e("Profile API Error: ${result.message}")
}
})
import co.optable.android_sdk.OptableSDK;
import co.optable.demoappjava.MainActivity;
import android.util.Log;
import java.util.HashMap;
...
HashMap<String,Object> traits = new HashMap<String,Object>();
traits.put("gender", "F");
traits.put("age", 38);
traits.put("hasAccount", true);
MainActivity.OPTABLE
.profile(traits)
.observe(getViewLifecycleOwner(), result -> {
if (result.getStatus() == OptableSDK.Status.SUCCESS) {
Log.i(null, "Profile API Success... ");
} else {
// result.getStatus() is OptableSDK.Status.ERROR
// result.getMessage() is the error message
Log.e(null, "Profile API Error: " + result.getMessage());
}
});
The specified traits are associated with the user's device and can be used for matching during audience assembly. The traits are of type OptableProfileTraits
which is an alias for HashMap<String,Any>
(or HashMap<String,Object>
in Java), and should consist only of key-value pairs where the key is a string and the value is either a string, number, or boolean.
To get the targeting key values associated by the configured DCN with the device in real-time, you can call the targeting
API as follows:
import co.optable.android_sdk.OptableSDK
import my.org.app.MainActivity
import android.util.Log
...
MainActivity.OPTABLE!!
.targeting()
.observe(viewLifecycleOwner, Observer { result ->
if (result.status == OptableSDK.Status.SUCCESS) {
Log.i("Targeting API Success... ")
// result.data!! can be iterated to get targeting key values:
result.data!!.forEach { (key, values) ->
Log.i("Targeting KV: ${key} = ${values}")
}
} else {
// result.status is OptableSDK.Status.ERROR
// result.message is the error message
Log.e("Targeting API Error: ${result.message}")
}
})
import co.optable.android_sdk.OptableSDK;
import co.optable.demoappjava.MainActivity;
import android.util.Log;
...
MainActivity.OPTABLE
.targeting()
.observe(getViewLifecycleOwner(), result -> {
if (result.getStatus() == OptableSDK.Status.SUCCESS) {
Log.i(null, "Targeting API Success... ");
result.getData().forEach((key, values) -> {
Log.i(null, "Targeting KV: " + key.toString() + " = " + values.toString());
});
} else {
// result.getStatus() is OptableSDK.Status.ERROR
// result.getMessage() is the error message
Log.e(null, "Targeting API Error: " + result.getMessage());
}
});
On success, the resulting key values are typically sent as part of a subsequent ad call. Therefore we recommend that you either call targeting()
before each ad call, or in parallel periodically, caching the resulting key values which you then provide in ad calls.
The targeting
API will automatically cache resulting key value data in client storage on success. You can subsequently retrieve the cached key value data as follows:
var cachedTargetingData = MainActivity.OPTABLE!!.targetingFromCache()
if (cachedTargetingData != null) {
cachedTargetingData!!.forEach { (key, values) ->
// key is a String and values is a List<String> ...
}
}
HashMap<String, List<String>> cachedTargetingData = MainActivity.OPTABLE.targetingFromCache();
if (cachedTargetingData != null) {
cachedTargetingData.forEach((key, values) -> {
// key is a String and values is a List<String> ...
});
}
You can also clear the locally cached targeting data:
MainActivity.OPTABLE!!.targetingClearCache()
MainActivity.OPTABLE.targetingClearCache();
Note that both targetingFromCache()
and targetingClearCache()
are synchronous.
To send real-time event data from the user's device to the DCN for eventual audience assembly, you can call the witness API as follows:
import co.optable.android_sdk.OptableSDK
import my.org.app.MainActivity
import android.util.Log
...
MainActivity.OPTABLE!!
.witness("example.event.type", hashMapOf("exampleKey" to "exampleValue"))
.observe(viewLifecycleOwner, Observer { result ->
if (result.status == OptableSDK.Status.SUCCESS) {
Log.i("Witness API Success... ")
} else {
// result.status is OptableSDK.Status.ERROR
// result.message is the error message
Log.e("Witness API Error: ${result.message}")
}
})
import co.optable.android_sdk.OptableSDK;
import co.optable.demoappjava.MainActivity;
import android.util.Log;
import java.util.HashMap;
...
HashMap<String,Object> eventProperties = new HashMap<String,Object>();
eventProperties.put("exampleKey", "exampleValue");
MainActivity.OPTABLE
.witness("example.event.type", eventProperties)
.observe(getViewLifecycleOwner(), result -> {
if (result.getStatus() == OptableSDK.Status.SUCCESS) {
Log.i(null, "Witness API Success... ");
} else {
// result.getStatus() is OptableSDK.Status.ERROR
// result.getMessage() is the error message
Log.e(null, "Witness API Error: " + result.getMessage());
}
});
The specified event type and properties are associated with the logged event and which can be used for matching during audience assembly. The optional witness event properties are of type OptableWitnessProperties
which is an alias for HashMap<String,Any>
(or HashMap<String,Object>
in Java), and should consist only of key-value pairs where the key is a string and the value is either a string, number, or boolean.
We can further extend the above targeting
example to show an integration with a Google Ad Manager 360 ad server account:
import co.optable.android_sdk.OptableSDK
import my.org.app.MainActivity
import android.util.Log
import com.google.android.gms.ads.doubleclick.PublisherAdRequest
import com.google.android.gms.ads.doubleclick.PublisherAdView
...
MainActivity.OPTABLE!!
.targeting()
.observe(viewLifecycleOwner, Observer { result ->
// Build GAM360 ad request:
var adRequest = PublisherAdRequest.Builder()
if (result.status == OptableSDK.Status.SUCCESS) {
Log.i("Targeting API Success... ")
// result.data!! can be iterated to get targeting key values:
result.data!!.forEach { (key, values) ->
// Add key values to GAM360 ad request:
adRequest.addCustomTargeting(key, values)
Log.i("Targeting KV: ${key} = ${values}")
}
} else {
// result.status is OptableSDK.Status.ERROR
// result.message is the error message
Log.e("Targeting API Error: ${result.message}")
}
// Assuming pubAdView refers to a pre-configured instance of
// com.google.android.gms.ads.doubleclick.PublisherAdView:
pubAdView.loadAd(adRequest.build())
})
import co.optable.android_sdk.OptableSDK;
import co.optable.demoappjava.MainActivity;
import android.util.Log;
import com.google.android.gms.ads.doubleclick.PublisherAdRequest;
import com.google.android.gms.ads.doubleclick.PublisherAdView;
...
MainActivity.OPTABLE.targeting().observe(getViewLifecycleOwner(), result -> {
// Build GAM360 ad request:
PublisherAdRequest.Builder adRequest = new PublisherAdRequest.Builder();
if (result.getStatus() == OptableSDK.Status.SUCCESS) {
Log.i(null, "Targeting API Success... ");
// result.getData() can be iterated to get targeting key values:
result.getData().forEach((key, values) -> {
// Add key values to GAM360 ad request:
adRequest.addCustomTargeting(key, values);
Log.i(null, "Targeting KV: " + key.toString() + " = " + values.toString());
});
} else {
// result.getStatus() is OptableSDK.Status.ERROR
// result.getMessage() is the error message
Log.e(null, "Targeting API Error: " + result.getMessage());
}
// Assuming pubAdView refers to a pre-configured instance of
// com.google.android.gms.ads.doubleclick.PublisherAdView:
pubAdView.loadAd(adRequest.build());
});
Working examples are available in the Kotlin and Java SDK demo applications.
If you send Email newsletters that contain links to your application (e.g., deep links), then you may want to automatically identify visitors that have clicked on any such links via their Email address.
To enable automatic identification of visitors originating from your Email newsletter, you first need to include an oeid parameter in the query string of all links to your website in your Email newsletter template. The value of the oeid parameter should be set to the SHA256 hash of the lowercased Email address of the recipient. For example, if you are using Braze to send your newsletters, you can easily encode the SHA256 hash value of the recipient's Email address by setting the oeid parameter in the query string of any links to your application as follows:
oeid={{${email_address} | downcase | sha2}}
The above example uses various personalization tags as documented in Braze's user guide to dynamically insert the required data into an oeid parameter, all of which should make up a part of the destination URL in your template.
In order for your application to open on devices where it is installed when a link to your domain is clicked, you need to configure and prepare your application to handle deep links first.
When Android launches your app after a user clicks on a link, it will start your app activity with your configured intent filters. You can then obtain the Uri
of the link by calling getData()
, and pass it to the SDK's tryIdentifyFromURI()
API which will automatically look for oeid
in the query parameters of the Uri
and call identify
with its value if found.
For example, you can call getData()
on the incoming Intent
from your onCreate()
activity lifecycle callback as follows:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.main)
...
val data: Uri? = intent?.data
if (data != null) {
MainActivity.OPTABLE!!.tryIdentifyFromURI(data)
}
...
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
...
Intent intent = getIntent();
Uri data = intent.getData();
if (data != null) {
MainActivity.OPTABLE.tryIdentifyFromURI(data);
}
...
}
The Kotlin and Java demo applications show a working example of identify
, targeting
, and witness
APIs, as well as an integration with the Google Ad Manager 360 ad server, enabling the targeting of ads served by GAM360 to audiences activated in the Optable DCN.
By default, the demo applications will connect to the Optable demo DCN at sandbox.optable.co
and reference application slug android-sdk-demo
. The demo apps depend on the GAM Mobile Ads SDK for Android and load ads from a GAM360 account operated by Optable.
To build the Kotlin demo app, from Android Studio, navigate to File > Open
and open the DemoApp/DemoAppKotlin
directory. To build the Java demo app, open the DemoApp/DemoAppJava
directory. In both cases, you should be able to build and run the resulting project directly, since it will automatically download the co.optable.android_sdk
library from the JitPack Maven repository.