Skip to content

Commit 56cf081

Browse files
authored
Configuration and tracking updates (#28)
* Update README.md * Ch2950/documentation (#19) * added sections number of results config parameters (#20) * rename api_key (#22) * added config object (#21) * ch3426 add uid parameter (#23) * ch4501 added purchase track event (#25) * ch3226 match tracking calls singatures to iOS calls (#26) * ch3225 foreground method added (#27)
1 parent bb66b1d commit 56cf081

File tree

16 files changed

+218
-151
lines changed

16 files changed

+218
-151
lines changed

README.md

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ An Android Client for [Constructor.io](http://constructor.io/). [Constructor.io
99
Please follow the directions at [Jitpack.io](https://jitpack.io/#Constructor-io/constructorio-client-android/v1.1.0) to add the client to your project.
1010

1111
## 2. Retrieve an API key
12+
1213
You can find this in your [Constructor.io dashboard](https://constructor.io/dashboard). Contact sales if you'd like to sign up, or support if you believe your company already has an account.
1314

1415
## 3. Implement the Autocomplete UI
@@ -136,18 +137,42 @@ The Android Client sends behavioral events to [Constructor.io](http://constructo
136137
Three types of these events exist:
137138

138139
1. **General Events** are sent as needed when an instance of the Client is created or initialized
139-
1. **Autocomplete Events** measure user interaction with autocomplete results and the `CIOAutocompleteViewController` sends them automatically.
140+
1. **Autocomplete Events** measure user interaction with autocomplete results and extending from `BaseSuggestionFragment` sends them automatically.
140141
1. **Search Events** measure user interaction with search results and the consuming app has to explicitly instrument them itself
141142

143+
### Autocomplete Events
144+
145+
If you decide to extend from the `BaseSuggestionFragment`, these events are sent automatically.
146+
142147
```kotlin
143148
import io.constructor.core.ConstructorIo
144149

145-
// Track search results loaded (term, resultCount)
146-
ConstructorIo.trackSearchResultLoaded("a search term", 123)
150+
// Track when the user focuses into the search bar (searchTerm)
151+
ConstructorIo.trackInputFocus("")
147152

148-
// Track search result click (term, itemId, position)
149-
ConstructorIo.trackSearchResultClickThrough("a search term", "an item id", "1")
153+
// Track when the user selects an autocomplete suggestion (searchTerm, originalQuery, sectionName)
154+
ConstructorIo.trackAutocompleteSelect("toothpicks", "tooth", "Search Suggestions")
150155

151-
// Track conversion (item id, term, revenue)
152-
constructorIO.trackConversion("an item id", "a search term", "45.00")
156+
// Track when the user submits a search (searchTerm, originalQuery)
157+
ConstructorIo.trackSearchSubmit("toothpicks", "tooth")
153158
```
159+
160+
### Search Events
161+
162+
These events should be sent manually by the consuming app.
163+
164+
```kotlin
165+
import io.constructor.core.ConstructorIo
166+
167+
// Track when search results are loaded into view (searchTerm, resultCount)
168+
ConstructorIo.trackSearchResultsLoaded("tooth", 789)
169+
170+
// Track when a search result is clicked (itemName, customerId, searchTerm)
171+
ConstructorIo.trackSearchResultClick("Fashionable Toothpicks", "1234567-AB", "tooth")
172+
173+
// Track when a search result converts (itemName, customerId, revenue, searchTerm)
174+
ConstructorIo.trackConversion("Fashionable Toothpicks", "1234567-AB", 12.99, "tooth")
175+
176+
// Track when products are purchased (customerIds)
177+
ConstructorIo.trackPurchase(customerIDs: ["123-AB", "456-CD"])
178+
```

library/src/main/java/io/constructor/core/Constants.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,11 @@ class Constants {
2222
const val EVENT = "tr"
2323
const val API_KEY = "key"
2424
const val NUM_RESULTS = "num_results_"
25+
const val CUSTOMER_ID = "customer_ids"
2526
const val GROUP_ID = "group[group_id]"
2627
const val GROUP_DISPLAY_NAME = "group[display_name]"
2728
const val USER_ID = "ui"
29+
const val TERM_UNKNOWN = "TERM_UNKNOWN"
2830
}
2931

3032
object QueryValues {

library/src/main/java/io/constructor/core/ConstructorIo.kt

Lines changed: 42 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ import io.constructor.data.ConstructorData
66
import io.constructor.data.DataManager
77
import io.constructor.data.local.PreferencesHelper
88
import io.constructor.data.memory.ConfigMemoryHolder
9+
import io.constructor.data.model.Group
910
import io.constructor.data.model.Suggestion
10-
import io.constructor.data.model.SuggestionViewModel
1111
import io.constructor.injection.component.AppComponent
1212
import io.constructor.injection.component.DaggerAppComponent
1313
import io.constructor.injection.module.AppModule
@@ -93,6 +93,10 @@ object ConstructorIo {
9393
}
9494
}
9595

96+
fun appMovedToForeground() {
97+
preferenceHelper.getSessionId(sessionIncrementEventHandler)
98+
}
99+
96100
fun getAutocompleteResults(query: String): Observable<ConstructorData<List<Suggestion>?>> {
97101
val params = mutableListOf<Pair<String, String>>()
98102
configMemoryHolder.autocompleteResultCount?.entries?.forEach {
@@ -101,71 +105,72 @@ object ConstructorIo {
101105
return dataManager.getAutocompleteResults(query, params.toTypedArray())
102106
}
103107

104-
fun trackSelect(query: String, suggestion: SuggestionViewModel, errorCallback: ConstructorError = null) {
108+
fun trackAutocompleteSelect(searchTerm: String, originalQuery: String, sectionName: String, group: Group? = null, errorCallback: ConstructorError = null) {
105109
val sessionId = preferenceHelper.getSessionId(sessionIncrementEventHandler)
106110
val encodedParams: ArrayList<Pair<String, String>> = arrayListOf()
107-
suggestion.group?.groupId?.let { encodedParams.add(Constants.QueryConstants.GROUP_ID.urlEncode() to it) }
108-
suggestion.group?.displayName?.let { encodedParams.add(Constants.QueryConstants.GROUP_DISPLAY_NAME.urlEncode() to it.urlEncode()) }
109-
disposable.add(dataManager.trackSelect(suggestion.term,
111+
group?.groupId?.let { encodedParams.add(Constants.QueryConstants.GROUP_ID.urlEncode() to it) }
112+
group?.displayName?.let { encodedParams.add(Constants.QueryConstants.GROUP_DISPLAY_NAME.urlEncode() to it.urlEncode()) }
113+
disposable.add(dataManager.trackAutocompleteSelect(searchTerm,
110114
arrayOf(Constants.QueryConstants.SESSION to sessionId.toString(),
111-
Constants.QueryConstants.AUTOCOMPLETE_SECTION to suggestion.section!!,
112-
Constants.QueryConstants.ORIGINAL_QUERY to query,
115+
Constants.QueryConstants.AUTOCOMPLETE_SECTION to sectionName,
116+
Constants.QueryConstants.ORIGINAL_QUERY to originalQuery,
113117
Constants.QueryConstants.EVENT to Constants.QueryValues.EVENT_CLICK),
114118
encodedParams.toTypedArray())
115119
.subscribe({
116-
context.broadcastIntent(Constants.EVENT_QUERY_SENT, Constants.EXTRA_TERM to query)
120+
context.broadcastIntent(Constants.EVENT_QUERY_SENT, Constants.EXTRA_TERM to searchTerm)
117121
}, { t ->
118122
t.printStackTrace()
119123
errorCallback?.invoke(t)
120124
e("trigger select error: ${t.message}") //To change body of created functions use File | Settings | File Templates.
121125
}))
122126
}
123127

124-
fun trackSearch(query: String, suggestion: SuggestionViewModel, errorCallback: ConstructorError = null) {
128+
fun trackSearchSubmit(searchTerm: String, originalQuery: String, group: Group?, errorCallback: ConstructorError = null) {
125129
val sessionId = preferenceHelper.getSessionId(sessionIncrementEventHandler)
126130
val encodedParams: ArrayList<Pair<String, String>> = arrayListOf()
127-
suggestion.group?.groupId?.let { encodedParams.add(Constants.QueryConstants.GROUP_ID.urlEncode() to it) }
128-
suggestion.group?.displayName?.let { encodedParams.add(Constants.QueryConstants.GROUP_DISPLAY_NAME.urlEncode() to it.urlEncode()) }
129-
disposable.add(dataManager.trackSearch(suggestion.term,
131+
group?.groupId?.let { encodedParams.add(Constants.QueryConstants.GROUP_ID.urlEncode() to it) }
132+
group?.displayName?.let { encodedParams.add(Constants.QueryConstants.GROUP_DISPLAY_NAME.urlEncode() to it.urlEncode()) }
133+
disposable.add(dataManager.trackSearchSubmit(searchTerm,
130134
arrayOf(Constants.QueryConstants.SESSION to sessionId.toString(),
131-
Constants.QueryConstants.ORIGINAL_QUERY to query,
135+
Constants.QueryConstants.ORIGINAL_QUERY to originalQuery,
132136
Constants.QueryConstants.EVENT to Constants.QueryValues.EVENT_SEARCH), encodedParams.toTypedArray())
133137
.subscribe({
134-
context.broadcastIntent(Constants.EVENT_QUERY_SENT, Constants.EXTRA_TERM to query)
138+
context.broadcastIntent(Constants.EVENT_QUERY_SENT, Constants.EXTRA_TERM to searchTerm)
135139
}, {
136140
it.printStackTrace()
137141
errorCallback?.invoke(it)
138142
e("trigger search error: ${it.message}")
139143
}))
140144
}
141145

142-
fun trackConversion(itemId: String, term: String = "TERM_UNKNOWN", revenue: String? = null, errorCallback: ConstructorError = null) {
146+
fun trackConversion(itemName: String, customerId: String, revenue: Double?, searchTerm: String = Constants.QueryConstants.TERM_UNKNOWN, sectionName: String? = null, errorCallback: ConstructorError = null) {
143147
val sessionId = preferenceHelper.getSessionId(sessionIncrementEventHandler)
144-
disposable.add(dataManager.trackConversion(term, itemId, revenue,
148+
disposable.add(dataManager.trackConversion(searchTerm, itemName, customerId, "%.2f".format(revenue),
145149
arrayOf(Constants.QueryConstants.SESSION to sessionId.toString(),
146-
Constants.QueryConstants.AUTOCOMPLETE_SECTION to preferenceHelper.defaultItemSection)).subscribeOn(Schedulers.io())
150+
Constants.QueryConstants.AUTOCOMPLETE_SECTION to (sectionName ?: preferenceHelper.defaultItemSection))).subscribeOn(Schedulers.io())
147151
.subscribe({}, { t ->
148152
t.printStackTrace()
149153
errorCallback?.invoke(t)
150154
e("Conversion event error: ${t.message}")
151155
}))
152156
}
153157

154-
fun trackSearchResultClickThrough(term: String, itemId: String, position: String? = null, errorCallback: ConstructorError = null) {
158+
fun trackSearchResultClick(itemName: String, customerId: String, searchTerm: String = Constants.QueryConstants.TERM_UNKNOWN, sectionName: String? = null, errorCallback: ConstructorError = null) {
155159
val sessionId = preferenceHelper.getSessionId(sessionIncrementEventHandler)
156-
disposable.add(dataManager.trackSearchResultClickThrough(term, itemId, position,
160+
val sName = sectionName ?: preferenceHelper.defaultItemSection
161+
disposable.add(dataManager.trackSearchResultClick(itemName, customerId, searchTerm,
157162
arrayOf(Constants.QueryConstants.SESSION to sessionId.toString(),
158-
Constants.QueryConstants.AUTOCOMPLETE_SECTION to preferenceHelper.defaultItemSection)).subscribeOn(Schedulers.io())
163+
Constants.QueryConstants.AUTOCOMPLETE_SECTION to sName)).subscribeOn(Schedulers.io())
159164
.subscribe({}, { t ->
160165
t.printStackTrace()
161166
errorCallback?.invoke(t)
162-
e("Conversion click through event error: ${t.message}")
167+
e("Search result click event error: ${t.message}")
163168
}))
164169
}
165170

166-
fun trackSearchResultLoaded(term: String, resultCount: Int, errorCallback: ConstructorError = null) {
171+
fun trackSearchResultsLoaded(term: String, resultCount: Int, errorCallback: ConstructorError = null) {
167172
val sessionId = preferenceHelper.getSessionId(sessionIncrementEventHandler)
168-
disposable.add(dataManager.trackSearchResultLoaded(term, resultCount,
173+
disposable.add(dataManager.trackSearchResultsLoaded(term, resultCount,
169174
arrayOf(Constants.QueryConstants.SESSION to sessionId.toString(),
170175
Constants.QueryConstants.ACTION to Constants.QueryValues.EVENT_SEARCH_RESULTS)).subscribeOn(Schedulers.io())
171176
.subscribe({}, { t ->
@@ -187,4 +192,18 @@ object ConstructorIo {
187192
}))
188193
}
189194

195+
fun trackPurchase(clientIds: Array<String>, sectionName: String? = null, errorCallback: ConstructorError = null) {
196+
val sessionId = preferenceHelper.getSessionId(sessionIncrementEventHandler)
197+
val sectionNameParam = sectionName ?: preferenceHelper.defaultItemSection
198+
val params = mutableListOf(Constants.QueryConstants.SESSION to sessionId.toString(),
199+
Constants.QueryConstants.AUTOCOMPLETE_SECTION to sectionNameParam)
200+
clientIds.forEach { params.add(Constants.QueryConstants.CUSTOMER_ID to it) }
201+
disposable.add(dataManager.trackPurchase(params.toTypedArray()).subscribeOn(Schedulers.io())
202+
.subscribe({}, { t ->
203+
t.printStackTrace()
204+
errorCallback?.invoke(t)
205+
e("Input focus event error: ${t.message}")
206+
}))
207+
}
208+
190209
}

library/src/main/java/io/constructor/data/DataManager.kt

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -25,32 +25,36 @@ constructor(private val constructorApi: ConstructorApi) {
2525
}
2626
}.toObservable()
2727

28-
fun trackSelect(term: String, params: Array<Pair<String, String>> = arrayOf(), encodedParams: Array<Pair<String, String>> = arrayOf()): Completable {
29-
return constructorApi.trackSelect(term, params.toMap(), encodedParams.toMap())
28+
fun trackAutocompleteSelect(term: String, params: Array<Pair<String, String>> = arrayOf(), encodedParams: Array<Pair<String, String>> = arrayOf()): Completable {
29+
return constructorApi.trackAutocompleteSelect(term, params.toMap(), encodedParams.toMap())
3030
}
3131

32-
fun trackSearch(term: String, params: Array<Pair<String, String>> = arrayOf(), encodedParams: Array<Pair<String, String>> = arrayOf()): Completable {
33-
return constructorApi.trackSearch(term, params.toMap(), encodedParams.toMap())
32+
fun trackSearchSubmit(term: String, params: Array<Pair<String, String>> = arrayOf(), encodedParams: Array<Pair<String, String>> = arrayOf()): Completable {
33+
return constructorApi.trackSearchSubmit(term, params.toMap(), encodedParams.toMap())
3434
}
3535

3636
fun trackSessionStart(params: Array<Pair<String, String>>): Completable {
3737
return constructorApi.trackSessionStart(params.toMap())
3838
}
3939

40-
fun trackConversion(term: String, itemId: String, revenue: String? = null, params: Array<Pair<String, String>> = arrayOf()): Completable {
41-
return constructorApi.trackConversion(term, itemId, revenue, params.toMap())
40+
fun trackConversion(term: String, itemName: String, customerId: String, revenue: String? = null, params: Array<Pair<String, String>> = arrayOf()): Completable {
41+
return constructorApi.trackConversion(term, itemName, customerId, revenue, params.toMap())
4242
}
4343

44-
fun trackSearchResultClickThrough(term: String, itemId: String, position: String? = null, params: Array<Pair<String, String>> = arrayOf()): Completable {
45-
return constructorApi.trackSearchResultClickThrough(term, itemId, position, params.toMap())
44+
fun trackSearchResultClick(itemName: String, customerId: String, term: String, params: Array<Pair<String, String>> = arrayOf()): Completable {
45+
return constructorApi.trackSearchResultTerm(term, itemName, customerId, params.toMap())
4646
}
4747

48-
fun trackSearchResultLoaded(term: String, reultCount: Int, params: Array<Pair<String, String>>): Completable {
49-
return constructorApi.trackSearchResultLoaded(term, reultCount, params.toMap())
48+
fun trackSearchResultsLoaded(term: String, resultCount: Int, params: Array<Pair<String, String>>): Completable {
49+
return constructorApi.trackSearchResultsLoaded(term, resultCount, params.toMap())
5050
}
5151

5252
fun trackInputFocus(term: String?, params: Array<Pair<String, String>>): Completable {
5353
return constructorApi.trackInputFocus(term, params.toMap())
5454
}
5555

56+
fun trackPurchase(params: Array<Pair<String, String>>): Completable {
57+
return constructorApi.trackPurchase(params.toMap())
58+
}
59+
5660
}

library/src/main/java/io/constructor/data/local/PreferencesHelper.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,13 @@ constructor(@ApplicationContext context: Context, prefFileName: String = PREF_FI
3030
get() = preferences.getLong(SESSION_LAST_ACCESS, System.currentTimeMillis())
3131
set(value) = preferences.edit().putLong(SESSION_LAST_ACCESS, value).apply()
3232

33-
fun getSessionId(sessionIncrementAction: ((String) -> Unit)? = null): Int {
33+
fun getSessionId(sessionIncrementAction: ((String) -> Unit)? = null, forceIncrement: Boolean = false): Int {
3434
if (!preferences.contains(SESSION_ID)) {
3535
return resetSession(sessionIncrementAction)
3636
}
3737
val sessionTime = lastSessionAccess
3838
val timeDiff = System.currentTimeMillis() - sessionTime
39-
if (timeDiff > SESSION_TIME_THRESHOLD) {
39+
if (timeDiff > SESSION_TIME_THRESHOLD || forceIncrement) {
4040
var sessionId = preferences.getInt(SESSION_ID, 1)
4141
preferences.edit().putInt(SESSION_ID, ++sessionId).apply()
4242
sessionIncrementAction?.invoke(sessionId.toString())

library/src/main/java/io/constructor/data/remote/ApiPaths.kt

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,12 @@ package io.constructor.data.remote
22

33
object ApiPaths {
44
const val URL_GET_SUGGESTIONS = "autocomplete/{value}"
5-
const val URL_SELECT_EVENT = "autocomplete/{term}/select"
6-
const val URL_SEARCH_EVENT = "autocomplete/{term}/search"
5+
const val URL_AUTOCOMPLETE_SELECT_EVENT = "autocomplete/{term}/select"
6+
const val URL_SEARCH_SUBMIT_EVENT = "autocomplete/{term}/search"
77
const val URL_SESSION_START_EVENT = "behavior"
8-
const val URL_CONVERT_EVENT = "autocomplete/{term}/conversion"
9-
const val URL_CLICK_THROUGH_EVENT = "autocomplete/{term}/click_through"
8+
const val URL_CONVERSION_EVENT = "autocomplete/{term}/conversion"
9+
const val URL_SEARCH_RESULT_CLICK_EVENT = "autocomplete/{term}/click_through"
1010
const val URL_BEHAVIOR = "behavior"
11+
const val URL_PURCHASE = "autocomplete/TERM_UNKNOWN/purchase"
1112

1213
}

library/src/main/java/io/constructor/data/remote/ConstructorApi.kt

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,24 +14,27 @@ interface ConstructorApi {
1414
@GET(ApiPaths.URL_GET_SUGGESTIONS)
1515
fun getSuggestions(@Path("value") value: String, @QueryMap data: Map<String, String>): Single<Result<AutocompleteResult>>
1616

17-
@GET(ApiPaths.URL_SELECT_EVENT)
18-
fun trackSelect(@Path("term") term: String, @QueryMap data: Map<String, String>, @QueryMap(encoded = true) encodedData: Map<String, String>): Completable
17+
@GET(ApiPaths.URL_AUTOCOMPLETE_SELECT_EVENT)
18+
fun trackAutocompleteSelect(@Path("term") term: String, @QueryMap data: Map<String, String>, @QueryMap(encoded = true) encodedData: Map<String, String>): Completable
1919

20-
@GET(ApiPaths.URL_SEARCH_EVENT)
21-
fun trackSearch(@Path("term") term: String, @QueryMap data: Map<String, String>, @QueryMap(encoded = true) encodedData: Map<String, String>): Completable
20+
@GET(ApiPaths.URL_SEARCH_SUBMIT_EVENT)
21+
fun trackSearchSubmit(@Path("term") term: String, @QueryMap data: Map<String, String>, @QueryMap(encoded = true) encodedData: Map<String, String>): Completable
2222

2323
@GET(ApiPaths.URL_SESSION_START_EVENT)
2424
fun trackSessionStart(@QueryMap params: Map<String, String>): Completable
2525

26-
@GET(ApiPaths.URL_CONVERT_EVENT)
27-
fun trackConversion(@Path("term") term: String, @Query("item_id") itemId: String, @Query("revenue") revenue: String?, @QueryMap params: Map<String, String>): Completable
26+
@GET(ApiPaths.URL_CONVERSION_EVENT)
27+
fun trackConversion(@Path("term") term: String, @Query("name") itemName: String, @Query("customer_id") customerId: String, @Query("revenue") revenue: String?, @QueryMap params: Map<String, String>): Completable
2828

29-
@GET(ApiPaths.URL_CLICK_THROUGH_EVENT)
30-
fun trackSearchResultClickThrough(@Path("term") term: String, @Query("item_id") itemId: String, @Query("position") position: String?, @QueryMap params: Map<String, String>): Completable
29+
@GET(ApiPaths.URL_SEARCH_RESULT_CLICK_EVENT)
30+
fun trackSearchResultTerm(@Path("term") term: String, @Query("name") itemName: String, @Query("customer_id") customerId: String, @QueryMap params: Map<String, String>): Completable
3131

3232
@GET(ApiPaths.URL_BEHAVIOR)
33-
fun trackSearchResultLoaded(@Query("term") term: String, @Query("num_results") resultCount: Int, @QueryMap params: Map<String, String>): Completable
33+
fun trackSearchResultsLoaded(@Query("term") term: String, @Query("num_results") resultCount: Int, @QueryMap params: Map<String, String>): Completable
3434

3535
@GET(ApiPaths.URL_BEHAVIOR)
3636
fun trackInputFocus(@Query("term") term: String?, @QueryMap params: Map<String, String>): Completable
37+
38+
@GET(ApiPaths.URL_PURCHASE)
39+
fun trackPurchase(@QueryMap params: Map<String, String>): Completable
3740
}

0 commit comments

Comments
 (0)