Skip to content

Commit d0f9572

Browse files
authored
Merge pull request #10 from Optable/witness-api
witness API implementation
2 parents eec224d + 3998d60 commit d0f9572

5 files changed

Lines changed: 105 additions & 7 deletions

File tree

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

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import android.widget.TextView
1313
import androidx.fragment.app.Fragment
1414
import androidx.lifecycle.Observer
1515
import co.optable.android_sdk.OptableSDK
16+
import co.optable.android_sdk.OptableWitnessProperties
1617
import co.optable.androidsdkdemo.MainActivity
1718
import co.optable.androidsdkdemo.R
1819
import com.google.android.gms.ads.doubleclick.PublisherAdRequest
@@ -50,6 +51,21 @@ class GAMBannerFragment : Fragment() {
5051
targetingDataView.setText(msg)
5152
mPublisherAdView.loadAd(adRequest.build())
5253
})
54+
55+
MainActivity.OPTABLE!!
56+
.witness(
57+
"GAMBannerFragment.loadAdButtonClicked",
58+
hashMapOf("exampleKey" to "exampleValue")
59+
)
60+
.observe(viewLifecycleOwner, Observer { result ->
61+
var msg = targetingDataView.text.toString()
62+
if (result.status == OptableSDK.Status.SUCCESS) {
63+
msg += "\n\nSuccess calling witness API to log loadAdButtonClicked event.\n"
64+
} else {
65+
msg += "\n\nOptableSDK Error: ${result.message}\n"
66+
}
67+
targetingDataView.setText(msg)
68+
})
5369
}
5470

5571
return root

README.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,33 @@ MainActivity.OPTABLE!!
134134

135135
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.
136136

137+
### Witness API
138+
139+
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:
140+
141+
```kotlin
142+
import co.optable.android_sdk.OptableSDK
143+
import co.optable.android_sdk.OptableWitnessProperties
144+
import my.org.app.MainActivity
145+
import android.util.Log
146+
...
147+
MainActivity.OPTABLE!!
148+
.witness("example.event.type", hashMapOf("example" to "value"))
149+
.observe(viewLifecycleOwner, Observer { result ->
150+
if (result.status == OptableSDK.Status.SUCCESS) {
151+
           Log.i("Witness API Success... ")
152+
} else {
153+
// result.status is OptableSDK.Status.ERROR
154+
// result.message is the error message
155+
Log.e("Witness API Error: ${result.message}")
156+
}
157+
})
158+
```
159+
160+
The specified event type and properties are associated with the logged event and which can be used for matching during audience assembly.
161+
162+
Note that event properties are of type `OptableWitnessProperties` which is an alias for `HashMap<String,String>`, and should consist only of string keyvalue pairs.
163+
137164
### Integrating GAM360
138165

139166
We can further extend the above `targeting` example to show an integration with a [Google Ad Manager 360](https://admanager.google.com/home/) ad server account:

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

Lines changed: 47 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,17 @@ import java.security.MessageDigest
2525
typealias OptableIdentifyInput = List<String>
2626

2727
/*
28-
* Identify API usually just returns {}... Void would be better but that results in retrofit2
29-
* error when parsing response, even when the API responded successfully, since {} is technically
30-
* a HashMap:
28+
* Witness API expects event properties:
29+
*/
30+
typealias OptableWitnessProperties = HashMap<String, String>
31+
32+
/*
33+
* Identify and Witness APIs usually just return {}... Void would be better but that results in
34+
* retrofit2 error when parsing response, even when the API responded successfully, since {} is
35+
* technically a HashMap:
3136
*/
3237
typealias OptableIdentifyResponse = HashMap<Any,Any>
38+
typealias OptableWitnessResponse = HashMap<Any,Any>
3339

3440
/*
3541
* Targeting API responds with a key-values dictionary on success:
@@ -192,6 +198,44 @@ class OptableSDK(context: Context, host: String, app: String, insecure: Boolean
192198
return liveData
193199
}
194200

201+
/*
202+
* witness(event, properties) calls the Optable Sandbox "witness" API in order to log a
203+
* specified 'event' (e.g., "app.screenView", "ui.buttonPressed"), with the specified keyvalue
204+
* OptableWitnessProperties 'properties', which can be subsequently used for audience assembly.
205+
*
206+
* It is asynchronous, so the caller may call observe() on the returned LiveData and expect
207+
* an instance of Response<OptableWitnessResponse> in the result. Success can be checked by
208+
* comparing result.status to OptableSDK.Status.SUCCESS. Note that result.data!! will point
209+
* to an empty HashMap on success, and can therefore be ignored.
210+
*/
211+
fun witness(event: String, properties: OptableWitnessProperties):
212+
LiveData<Response<OptableWitnessResponse>> {
213+
val liveData = MutableLiveData<Response<OptableWitnessResponse>>()
214+
val client = this.client
215+
216+
GlobalScope.launch {
217+
val response = client.Witness(event, properties)
218+
when (response) {
219+
is EdgeResponse.Success -> {
220+
liveData.postValue(Response.success(response.body))
221+
}
222+
is EdgeResponse.ApiError -> {
223+
liveData.postValue(Response.error(response.body))
224+
}
225+
is EdgeResponse.NetworkError -> {
226+
liveData.postValue(Response.error(
227+
Response.Error("NetworkError", "None")))
228+
}
229+
is EdgeResponse.UnknownError -> {
230+
liveData.postValue(Response.error(
231+
Response.Error("UnknownError", "None")))
232+
}
233+
}
234+
}
235+
236+
return liveData
237+
}
238+
195239
companion object {
196240
/*
197241
* eid(email) is a helper that returns type-prefixed SHA256(downcase(email))

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,15 @@ class Client(private val config: Config, private val context: Context) {
8888
return edgeService!!.Targeting(this.config.app)
8989
}
9090

91+
suspend fun Witness(event: String, properties: OptableWitnessProperties):
92+
EdgeResponse<OptableWitnessResponse, OptableSDK.Response.Error>
93+
{
94+
val evtBody = HashMap<String,Any>()
95+
evtBody.put("event", event)
96+
evtBody.put("properties", properties)
97+
return edgeService!!.Witness(this.config.app, evtBody)
98+
}
99+
91100
fun hasGAID(): Boolean {
92101
return ((gaid != null) && (gaidLAT == false) && !TextUtils.isEmpty(gaid!!))
93102
}

android_sdk/src/main/java/co/optable/android_sdk/edge/EdgeService.kt

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,7 @@
44
*/
55
package co.optable.android_sdk.edge
66

7-
import co.optable.android_sdk.OptableIdentifyInput
8-
import co.optable.android_sdk.OptableIdentifyResponse
9-
import co.optable.android_sdk.OptableSDK
10-
import co.optable.android_sdk.OptableTargetingResponse
7+
import co.optable.android_sdk.*
118
import retrofit2.http.Body
129
import retrofit2.http.GET
1310
import retrofit2.http.POST
@@ -23,4 +20,9 @@ interface EdgeService {
2320
suspend fun Targeting(@Path("app") app: String):
2421
EdgeResponse<OptableTargetingResponse, OptableSDK.Response.Error>
2522

23+
@POST("/{app}/witness")
24+
suspend fun Witness(@Path("app") app: String,
25+
@Body witnessBody: HashMap<String,Any>):
26+
EdgeResponse<OptableWitnessResponse, OptableSDK.Response.Error>
27+
2628
}

0 commit comments

Comments
 (0)