Skip to content

Commit 8f70220

Browse files
authored
Merge pull request #14 from Optable/cache-targeting
targetingFromCache() and targetingClearCache() APIs
2 parents c999319 + e00bd86 commit 8f70220

File tree

10 files changed

+254
-32
lines changed

10 files changed

+254
-32
lines changed

DemoApp/DemoAppJava/app/src/main/java/co/optable/demoappjava/ui/GAMBanner/GAMBannerFragment.java

Lines changed: 51 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import com.google.android.gms.ads.doubleclick.PublisherAdView;
1515

1616
import java.util.HashMap;
17+
import java.util.List;
1718

1819
import co.optable.android_sdk.OptableSDK;
1920
import co.optable.demoappjava.MainActivity;
@@ -30,6 +31,7 @@ public View onCreateView(@NonNull LayoutInflater inflater,
3031
mPublisherAdView = root.findViewById(R.id.publisherAdView);
3132
targetingDataView = root.findViewById(R.id.targetingDataView);
3233

34+
// loadAdButton loads targeting data and then the GAM banner:
3335
Button btn = root.findViewById(R.id.loadAdButton);
3436
btn.setOnClickListener(view -> {
3537
targetingDataView.setText("");
@@ -51,27 +53,60 @@ public View onCreateView(@NonNull LayoutInflater inflater,
5153

5254
targetingDataView.setText(msg.toString());
5355
mPublisherAdView.loadAd(adRequest.build());
56+
witness();
5457
});
58+
});
5559

56-
HashMap<String, String> eventProperties = new HashMap<String, String>();
57-
eventProperties.put("exampleKey", "exampleValue");
58-
59-
MainActivity.OPTABLE
60-
.witness("GAMBannerFragment.loadAdButtonClicked", eventProperties)
61-
.observe(getViewLifecycleOwner(), result -> {
62-
final StringBuilder msg = new StringBuilder();
63-
msg.append(targetingDataView.getText().toString());
64-
65-
if (result.getStatus() == OptableSDK.Status.SUCCESS) {
66-
msg.append("\n\nSuccess calling witness API to log loadAdButtonClicked event.\n\n");
67-
} else {
68-
msg.append("\n\nOptableSDK Error: " + result.getMessage() + "\n\n");
69-
}
60+
// loadAdButton2 loads targeting data from cache, and then the GAM banner:
61+
btn = root.findViewById(R.id.loadAdButton2);
62+
btn.setOnClickListener(view -> {
63+
targetingDataView.setText("");
64+
PublisherAdRequest.Builder adRequest = new PublisherAdRequest.Builder();
65+
final StringBuilder msg = new StringBuilder();
66+
HashMap<String, List<String>> data = MainActivity.OPTABLE.targetingFromCache();
67+
68+
if (data != null) {
69+
msg.append("Loading GAM ad with cached targeting data:\n\n");
70+
data.forEach((key, values) -> {
71+
adRequest.addCustomTargeting(key, values);
72+
msg.append(key.toString() + " = " + values.toString());
73+
});
74+
} else {
75+
msg.append("Targeting data cache empty.");
76+
}
77+
78+
targetingDataView.setText(msg.toString());
79+
mPublisherAdView.loadAd(adRequest.build());
80+
witness();
81+
});
7082

71-
targetingDataView.setText(msg.toString());
72-
});
83+
// loadAdButton3 clears targeting data cache:
84+
btn = root.findViewById(R.id.loadAdButton3);
85+
btn.setOnClickListener(view -> {
86+
targetingDataView.setText("Clearing targeting data cache.\n\n");
87+
MainActivity.OPTABLE.targetingClearCache();
7388
});
7489

7590
return root;
7691
}
92+
93+
private void witness() {
94+
HashMap<String, String> eventProperties = new HashMap<String, String>();
95+
eventProperties.put("exampleKey", "exampleValue");
96+
97+
MainActivity.OPTABLE
98+
.witness("GAMBannerFragment.loadAdButtonClicked", eventProperties)
99+
.observe(getViewLifecycleOwner(), result -> {
100+
final StringBuilder msg = new StringBuilder();
101+
msg.append(targetingDataView.getText().toString());
102+
103+
if (result.getStatus() == OptableSDK.Status.SUCCESS) {
104+
msg.append("\n\nSuccess calling witness API to log loadAdButtonClicked event.\n\n");
105+
} else {
106+
msg.append("\n\nOptableSDK Error: " + result.getMessage() + "\n\n");
107+
}
108+
109+
targetingDataView.setText(msg.toString());
110+
});
111+
}
77112
}

DemoApp/DemoAppJava/app/src/main/res/layout/fragment_gambanner.xml

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,37 @@
2222

2323
<Button
2424
android:id="@+id/loadAdButton"
25-
android:layout_width="wrap_content"
26-
android:layout_height="wrap_content"
25+
android:layout_width="120dp"
26+
android:layout_height="50dp"
27+
android:layout_marginTop="8dp"
2728
android:text="Load Banner"
29+
android:textSize="8sp"
30+
app:layout_constraintEnd_toEndOf="parent"
31+
app:layout_constraintHorizontal_bias="0.054"
32+
app:layout_constraintStart_toStartOf="parent"
33+
app:layout_constraintTop_toTopOf="parent" />
34+
35+
<Button
36+
android:id="@+id/loadAdButton2"
37+
android:layout_width="120dp"
38+
android:layout_height="50dp"
39+
android:layout_marginTop="8dp"
40+
android:text="Cached Banner"
41+
android:textSize="8sp"
42+
app:layout_constraintEnd_toEndOf="parent"
43+
app:layout_constraintHorizontal_bias="0.498"
44+
app:layout_constraintStart_toStartOf="parent"
45+
app:layout_constraintTop_toTopOf="parent" />
46+
47+
<Button
48+
android:id="@+id/loadAdButton3"
49+
android:layout_width="120dp"
50+
android:layout_height="50dp"
51+
android:layout_marginTop="8dp"
52+
android:text="Clear Cache"
53+
android:textSize="8sp"
2854
app:layout_constraintEnd_toEndOf="parent"
55+
app:layout_constraintHorizontal_bias="0.945"
2956
app:layout_constraintStart_toStartOf="parent"
3057
app:layout_constraintTop_toTopOf="parent" />
3158

DemoApp/DemoAppKotlin/app/src/main/java/co/optable/androidsdkdemo/ui/GAMBanner/GAMBannerFragment.kt

Lines changed: 43 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,11 @@ class GAMBannerFragment : Fragment() {
3333
targetingDataView = root.findViewById(R.id.targetingDataView)
3434
targetingDataView.setText("")
3535

36+
// loadAdButton loads targeting data and then the GAM banner:
3637
var btn = root.findViewById(R.id.loadAdButton) as Button
3738
btn.setOnClickListener {
3839
MainActivity.OPTABLE!!.targeting().observe(viewLifecycleOwner, Observer { result ->
39-
var msg = targetingDataView.text.toString()
40+
var msg = ""
4041
var adRequest = PublisherAdRequest.Builder()
4142

4243
if (result.status == OptableSDK.Status.SUCCESS) {
@@ -51,14 +52,49 @@ class GAMBannerFragment : Fragment() {
5152

5253
targetingDataView.setText(msg)
5354
mPublisherAdView.loadAd(adRequest.build())
55+
witness()
5456
})
57+
}
5558

56-
MainActivity.OPTABLE!!
57-
.witness(
58-
"GAMBannerFragment.loadAdButtonClicked",
59-
hashMapOf("exampleKey" to "exampleValue")
60-
)
61-
.observe(viewLifecycleOwner, Observer { result ->
59+
// loadAdButton2 loads targeting data from cache, and then the GAM banner:
60+
btn = root.findViewById(R.id.loadAdButton2) as Button
61+
btn.setOnClickListener {
62+
var msg = ""
63+
var adRequest = PublisherAdRequest.Builder()
64+
var data = MainActivity.OPTABLE!!.targetingFromCache()
65+
66+
if (data != null) {
67+
msg += "Loading GAM ad with cached targeting data:\n\n"
68+
data!!.forEach { (key, values) ->
69+
adRequest.addCustomTargeting(key, values)
70+
msg += "${key} = ${values}\n"
71+
}
72+
} else {
73+
msg += "Targeting data cache empty."
74+
}
75+
76+
targetingDataView.setText(msg)
77+
mPublisherAdView.loadAd(adRequest.build())
78+
witness()
79+
}
80+
81+
// loadAdButton3 clears targeting data cache:
82+
btn = root.findViewById(R.id.loadAdButton3) as Button
83+
btn.setOnClickListener {
84+
targetingDataView.setText("Clearing targeting data cache.\n\n")
85+
MainActivity.OPTABLE!!.targetingClearCache()
86+
}
87+
88+
return root
89+
}
90+
91+
private fun witness() {
92+
MainActivity.OPTABLE!!
93+
.witness(
94+
"GAMBannerFragment.loadAdButtonClicked",
95+
hashMapOf("exampleKey" to "exampleValue")
96+
)
97+
.observe(viewLifecycleOwner, Observer { result ->
6298
var msg = targetingDataView.text.toString()
6399
if (result.status == OptableSDK.Status.SUCCESS) {
64100
msg += "\n\nSuccess calling witness API to log loadAdButtonClicked event.\n\n"
@@ -67,9 +103,6 @@ class GAMBannerFragment : Fragment() {
67103
}
68104
targetingDataView.setText(msg)
69105
})
70-
}
71-
72-
return root
73106
}
74107

75108
}

DemoApp/DemoAppKotlin/app/src/main/res/layout/fragment_gambanner.xml

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,37 @@
2222

2323
<Button
2424
android:id="@+id/loadAdButton"
25-
android:layout_width="wrap_content"
26-
android:layout_height="wrap_content"
25+
android:layout_width="120dp"
26+
android:layout_height="50dp"
27+
android:layout_marginTop="8dp"
2728
android:text="Load Banner"
29+
android:textSize="8sp"
30+
app:layout_constraintEnd_toEndOf="parent"
31+
app:layout_constraintHorizontal_bias="0.054"
32+
app:layout_constraintStart_toStartOf="parent"
33+
app:layout_constraintTop_toTopOf="parent" />
34+
35+
<Button
36+
android:id="@+id/loadAdButton2"
37+
android:layout_width="120dp"
38+
android:layout_height="50dp"
39+
android:layout_marginTop="8dp"
40+
android:text="Cached Banner"
41+
android:textSize="8sp"
42+
app:layout_constraintEnd_toEndOf="parent"
43+
app:layout_constraintHorizontal_bias="0.498"
44+
app:layout_constraintStart_toStartOf="parent"
45+
app:layout_constraintTop_toTopOf="parent" />
46+
47+
<Button
48+
android:id="@+id/loadAdButton3"
49+
android:layout_width="120dp"
50+
android:layout_height="50dp"
51+
android:layout_marginTop="8dp"
52+
android:text="Clear Cache"
53+
android:textSize="8sp"
2854
app:layout_constraintEnd_toEndOf="parent"
55+
app:layout_constraintHorizontal_bias="0.945"
2956
app:layout_constraintStart_toStartOf="parent"
3057
app:layout_constraintTop_toTopOf="parent" />
3158

README.md

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,48 @@ MainActivity.OPTABLE
225225

226226
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.
227227

228+
#### Caching Targeting Data
229+
230+
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:
231+
232+
##### Kotlin
233+
234+
```kotlin
235+
var cachedTargetingData = MainActivity.OPTABLE!!.targetingFromCache()
236+
if (cachedTargetingData != null) {
237+
cachedTargetingData!!.forEach { (key, values) ->
238+
// key is a String and values is a List<String> ...
239+
}
240+
}
241+
```
242+
243+
##### Java
244+
245+
```java
246+
HashMap<String, List<String>> cachedTargetingData = MainActivity.OPTABLE.targetingFromCache();
247+
if (cachedTargetingData != null) {
248+
cachedTargetingData.forEach((key, values) -> {
249+
// key is a String and values is a List<String> ...
250+
});
251+
}
252+
```
253+
254+
You can also clear the locally cached targeting data:
255+
256+
##### Kotlin
257+
258+
```kotlin
259+
MainActivity.OPTABLE!!.targetingClearCache()
260+
```
261+
262+
##### Java
263+
264+
```java
265+
MainActivity.OPTABLE.targetingClearCache();
266+
```
267+
268+
Note that both `targetingFromCache()` and `targetingClearCache()` are synchronous.
269+
228270
### Witness API
229271

230272
To send real-time event data from the user's device to the sandbox for eventual audience assembly, you can call the witness API as follows:

android_sdk/build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ dependencies {
5353
implementation 'com.squareup.retrofit2:retrofit:2.7.0'
5454
implementation 'com.squareup.retrofit2:converter-gson:2.7.0'
5555
implementation 'com.squareup.okhttp3:okhttp:4.3.1'
56-
implementation 'com.google.code.gson:gson:2.8.5'
56+
implementation 'com.google.code.gson:gson:2.8.6'
5757
implementation "androidx.preference:preference-ktx:1.1.1"
5858
implementation 'com.google.android.gms:play-services-ads-lite:11.8.0'
5959
implementation 'androidx.core:core-ktx:1.3.1'

android_sdk/src/main/java/co/optable/android_sdk/Config.kt

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
*/
55
package co.optable.android_sdk
66

7+
import android.util.Base64
8+
79
class Config(val host: String, val app: String, val insecure: Boolean = false) {
810

911
fun edgeBaseURL(): String {
@@ -15,7 +17,16 @@ class Config(val host: String, val app: String, val insecure: Boolean = false) {
1517
}
1618

1719
fun passportKey(): String {
18-
return "OPTABLE_" + this.host + "/" + this.app
20+
return key("PASS")
21+
}
22+
23+
fun targetingKey(): String {
24+
return key("TGT")
25+
}
26+
27+
private fun key(kind: String): String {
28+
val sfx = this.host + "/" + this.app
29+
return "OPTABLE_" + kind + "_" + Base64.encodeToString(sfx.toByteArray(), 0)
1930
}
2031

2132
}

android_sdk/src/main/java/co/optable/android_sdk/OptableSDK.kt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,7 @@ class OptableSDK @JvmOverloads constructor(context: Context, host: String, app:
198198
val response = client.Targeting()
199199
when (response) {
200200
is EdgeResponse.Success -> {
201+
client.TargetingSetCache(response.body)
201202
liveData.postValue(Response.success(response.body))
202203
}
203204
is EdgeResponse.ApiError -> {
@@ -217,6 +218,14 @@ class OptableSDK @JvmOverloads constructor(context: Context, host: String, app:
217218
return liveData
218219
}
219220

221+
fun targetingFromCache(): OptableTargetingResponse? {
222+
return this.client.TargetingFromCache()
223+
}
224+
225+
fun targetingClearCache() {
226+
this.client.TargetingClearCache()
227+
}
228+
220229
/*
221230
* witness(event, properties) calls the Optable Sandbox "witness" API in order to log a
222231
* specified 'event' (e.g., "app.screenView", "ui.buttonPressed"), with the specified keyvalue

android_sdk/src/main/java/co/optable/android_sdk/core/Client.kt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,18 @@ class Client(private val config: Config, private val context: Context) {
9797
return edgeService!!.Witness(this.config.app, evtBody)
9898
}
9999

100+
fun TargetingSetCache(keyvalues: OptableTargetingResponse) {
101+
storage.setTargeting(keyvalues)
102+
}
103+
104+
fun TargetingFromCache(): OptableTargetingResponse? {
105+
return storage.getTargeting()
106+
}
107+
108+
fun TargetingClearCache() {
109+
storage.clearTargeting()
110+
}
111+
100112
fun hasGAID(): Boolean {
101113
return ((gaid != null) && (gaidLAT == false) && !TextUtils.isEmpty(gaid!!))
102114
}

0 commit comments

Comments
 (0)