Skip to content

Commit 20db0b5

Browse files
authored
Merge pull request #18 from Constructor-io/develop
Version 1.0.0
2 parents 0eac0d7 + 626fcd7 commit 20db0b5

File tree

21 files changed

+448
-149
lines changed

21 files changed

+448
-149
lines changed

library/build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,12 +90,12 @@ dependencies {
9090
implementation 'io.reactivex.rxjava2:rxkotlin:2.2.0'
9191
testImplementation 'io.mockk:mockk:1.7.14'
9292
testImplementation 'org.robolectric:robolectric:3.6.1'
93+
testImplementation 'com.squareup.okhttp3:mockwebserver:3.11.0'
9394
implementation supportLibs
9495
implementation networkLibs
9596
implementation otherLibs
9697
implementation 'com.android.support:support-v4:27.1.1'
9798
implementation 'com.android.support:cardview-v7:27.1.1'
98-
// APT dependencies
9999
kapt annotationProcessorLibs
100100
kaptTest daggerCompiler
101101
kaptAndroidTest daggerCompiler

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

Lines changed: 61 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import android.content.Context
55
import io.constructor.BuildConfig
66
import io.constructor.data.DataManager
77
import io.constructor.data.local.PreferencesHelper
8+
import io.constructor.data.memory.TestCellMemoryHolder
89
import io.constructor.data.model.SuggestionViewModel
910
import io.constructor.injection.component.AppComponent
1011
import io.constructor.injection.component.DaggerAppComponent
@@ -14,15 +15,20 @@ import io.constructor.util.broadcastIntent
1415
import io.constructor.util.d
1516
import io.constructor.util.e
1617
import io.constructor.util.urlEncode
18+
import io.reactivex.disposables.CompositeDisposable
1719
import io.reactivex.schedulers.Schedulers
1820
import java.util.*
1921

22+
typealias ConstructorError = ((Throwable) -> Unit)?
23+
2024
@SuppressLint("StaticFieldLeak")
2125
object ConstructorIo {
2226

2327
private lateinit var dataManager: DataManager
2428
private lateinit var preferenceHelper: PreferencesHelper
29+
private lateinit var testCellMemoryHolder: TestCellMemoryHolder
2530
private lateinit var context: Context
31+
private var disposable = CompositeDisposable()
2632

2733
internal val component: AppComponent by lazy {
2834
DaggerAppComponent.builder()
@@ -31,11 +37,16 @@ object ConstructorIo {
3137
.build()
3238
}
3339

34-
var sessionIncrementEventHandler: (String) -> Unit = {
35-
dataManager.trackSessionStart(arrayOf(Constants.QueryConstants.SESSION to it,
40+
private var sessionIncrementEventHandler: (String) -> Unit = {
41+
trackSessionStartInternal(it)
42+
}
43+
44+
private fun trackSessionStartInternal(sessionId: String, errorCallback: ConstructorError = null) {
45+
disposable.add(dataManager.trackSessionStart(arrayOf(Constants.QueryConstants.SESSION to sessionId,
3646
Constants.QueryConstants.ACTION to Constants.QueryValues.EVENT_SESSION_START)).subscribeOn(Schedulers.io()).subscribe({}, {
47+
errorCallback?.invoke(it)
3748
d("Error triggering Session Change event")
38-
})
49+
}))
3950
}
4051

4152
fun init(context: Context?, apiKey: String, defaultItemSection: String = BuildConfig.AUTOCOMPLETE_SECTION) {
@@ -45,128 +56,126 @@ object ConstructorIo {
4556
this.context = context.applicationContext
4657
dataManager = component.dataManager()
4758
preferenceHelper = component.preferenceHelper()
59+
testCellMemoryHolder = component.testCellMemoryHolder()
4860
preferenceHelper.token = apiKey
4961
preferenceHelper.defaultItemSection = defaultItemSection
5062
if (preferenceHelper.id.isBlank()) {
5163
preferenceHelper.id = UUID.randomUUID().toString()
5264
}
5365
}
5466

55-
internal fun testInit(context: Context?, apiKey: String, dataManager: DataManager, preferenceHelper: PreferencesHelper) {
67+
fun getSessionId() = preferenceHelper.getSessionId()
68+
69+
fun getClientId() = preferenceHelper.id
70+
71+
fun setTestCellValues(pair1: Pair<String, String>, pair2: Pair<String, String>? = null, pair3: Pair<String, String>? = null) {
72+
testCellMemoryHolder.testCellParams = listOf(pair1, pair2, pair3)
73+
}
74+
75+
fun clearTestCellValues() {
76+
testCellMemoryHolder.testCellParams = emptyList()
77+
}
78+
79+
internal fun testInit(context: Context?, apiKey: String, dataManager: DataManager, preferenceHelper: PreferencesHelper, testCellMemoryHolder: TestCellMemoryHolder) {
5680
if (context == null) {
5781
throw IllegalStateException("Context is null, please init library using ConstructorIo.with(context)")
5882
}
5983
this.context = context.applicationContext
6084
this.dataManager = dataManager
6185
this.preferenceHelper = preferenceHelper
86+
this.testCellMemoryHolder = testCellMemoryHolder
6287
preferenceHelper.token = apiKey
6388
if (preferenceHelper.id.isBlank()) {
6489
preferenceHelper.id = UUID.randomUUID().toString()
6590
}
6691
}
6792

68-
internal fun getAutocompleteResults(query: String) = dataManager.getAutocompleteResults(query)
93+
fun getAutocompleteResults(query: String) = dataManager.getAutocompleteResults(query)
6994

70-
internal fun trackSelect(query: String, suggestion: SuggestionViewModel) {
95+
fun trackSelect(query: String, suggestion: SuggestionViewModel, errorCallback: ConstructorError = null) {
7196
val sessionId = preferenceHelper.getSessionId(sessionIncrementEventHandler)
7297
val encodedParams: ArrayList<Pair<String, String>> = arrayListOf()
7398
suggestion.group?.groupId?.let { encodedParams.add(Constants.QueryConstants.GROUP_ID.urlEncode() to it) }
7499
suggestion.group?.displayName?.let { encodedParams.add(Constants.QueryConstants.GROUP_DISPLAY_NAME.urlEncode() to it.urlEncode()) }
75-
dataManager.trackSelect(suggestion.term,
100+
disposable.add(dataManager.trackSelect(suggestion.term,
76101
arrayOf(Constants.QueryConstants.SESSION to sessionId.toString(),
77102
Constants.QueryConstants.AUTOCOMPLETE_SECTION to suggestion.section!!,
78103
Constants.QueryConstants.ORIGINAL_QUERY to query,
79104
Constants.QueryConstants.EVENT to Constants.QueryValues.EVENT_CLICK),
80105
encodedParams.toTypedArray())
81-
.subscribe({ response ->
82-
if (response.isSuccessful) {
83-
d("trigger select success") //To change body of created functions use File | Settings | File Templates.
84-
context.broadcastIntent(Constants.EVENT_QUERY_SENT, Constants.EXTRA_TERM to query)
85-
}
106+
.subscribe({
107+
context.broadcastIntent(Constants.EVENT_QUERY_SENT, Constants.EXTRA_TERM to query)
86108
}, { t ->
87109
t.printStackTrace()
110+
errorCallback?.invoke(t)
88111
e("trigger select error: ${t.message}") //To change body of created functions use File | Settings | File Templates.
89-
})
112+
}))
90113
}
91114

92-
internal fun trackSearch(query: String, suggestion: SuggestionViewModel) {
115+
fun trackSearch(query: String, suggestion: SuggestionViewModel, errorCallback: ConstructorError = null) {
93116
val sessionId = preferenceHelper.getSessionId(sessionIncrementEventHandler)
94117
val encodedParams: ArrayList<Pair<String, String>> = arrayListOf()
95118
suggestion.group?.groupId?.let { encodedParams.add(Constants.QueryConstants.GROUP_ID.urlEncode() to it) }
96119
suggestion.group?.displayName?.let { encodedParams.add(Constants.QueryConstants.GROUP_DISPLAY_NAME.urlEncode() to it.urlEncode()) }
97-
dataManager.trackSearch(suggestion.term,
120+
disposable.add(dataManager.trackSearch(suggestion.term,
98121
arrayOf(Constants.QueryConstants.SESSION to sessionId.toString(),
99122
Constants.QueryConstants.ORIGINAL_QUERY to query,
100123
Constants.QueryConstants.EVENT to Constants.QueryValues.EVENT_SEARCH), encodedParams.toTypedArray())
101124
.subscribe({
102-
if (it.isSuccessful) {
103-
d("trigger search success")
104-
context.broadcastIntent(Constants.EVENT_QUERY_SENT, Constants.EXTRA_TERM to query)
105-
}
125+
context.broadcastIntent(Constants.EVENT_QUERY_SENT, Constants.EXTRA_TERM to query)
106126
}, {
107127
it.printStackTrace()
128+
errorCallback?.invoke(it)
108129
e("trigger search error: ${it.message}")
109-
})
130+
}))
110131
}
111132

112-
fun trackConversion(itemId: String, term: String = "TERM_UNKNOWN", revenue: String? = null) {
133+
fun trackConversion(itemId: String, term: String = "TERM_UNKNOWN", revenue: String? = null, errorCallback: ConstructorError = null) {
113134
val sessionId = preferenceHelper.getSessionId(sessionIncrementEventHandler)
114-
dataManager.trackConversion(term, itemId, revenue,
135+
disposable.add(dataManager.trackConversion(term, itemId, revenue,
115136
arrayOf(Constants.QueryConstants.SESSION to sessionId.toString(),
116137
Constants.QueryConstants.AUTOCOMPLETE_SECTION to preferenceHelper.defaultItemSection)).subscribeOn(Schedulers.io())
117-
.subscribe({ response ->
118-
if (response.isSuccessful) {
119-
d("Conversion event success")
120-
}
121-
}, { t ->
138+
.subscribe({}, { t ->
122139
t.printStackTrace()
140+
errorCallback?.invoke(t)
123141
e("Conversion event error: ${t.message}")
124-
})
142+
}))
125143
}
126144

127-
fun trackSearchResultClickThrough(term: String, itemId: String, position: String? = null) {
145+
fun trackSearchResultClickThrough(term: String, itemId: String, position: String? = null, errorCallback: ConstructorError = null) {
128146
val sessionId = preferenceHelper.getSessionId(sessionIncrementEventHandler)
129-
dataManager.trackSearchResultClickThrough(term, itemId, position,
147+
disposable.add(dataManager.trackSearchResultClickThrough(term, itemId, position,
130148
arrayOf(Constants.QueryConstants.SESSION to sessionId.toString(),
131149
Constants.QueryConstants.AUTOCOMPLETE_SECTION to preferenceHelper.defaultItemSection)).subscribeOn(Schedulers.io())
132-
.subscribe({ response ->
133-
if (response.isSuccessful) {
134-
d("Conversion click through event success")
135-
}
136-
}, { t ->
150+
.subscribe({}, { t ->
137151
t.printStackTrace()
152+
errorCallback?.invoke(t)
138153
e("Conversion click through event error: ${t.message}")
139-
})
154+
}))
140155
}
141156

142-
fun trackSearchResultLoaded(term: String, resultCount: Int) {
157+
fun trackSearchResultLoaded(term: String, resultCount: Int, errorCallback: ConstructorError = null) {
143158
val sessionId = preferenceHelper.getSessionId(sessionIncrementEventHandler)
144-
dataManager.trackSearchResultLoaded(term, resultCount,
159+
disposable.add(dataManager.trackSearchResultLoaded(term, resultCount,
145160
arrayOf(Constants.QueryConstants.SESSION to sessionId.toString(),
146161
Constants.QueryConstants.ACTION to Constants.QueryValues.EVENT_SEARCH_RESULTS)).subscribeOn(Schedulers.io())
147-
.subscribe({ response ->
148-
if (response.isSuccessful) {
149-
d("Conversion event success")
150-
}
151-
}, { t ->
162+
.subscribe({}, { t ->
152163
t.printStackTrace()
164+
errorCallback?.invoke(t)
153165
e("Conversion event error: ${t.message}")
154-
})
166+
}))
155167
}
156168

157-
internal fun trackInputFocus(term: String?) {
169+
fun trackInputFocus(term: String?, errorCallback: ConstructorError = null) {
158170
val sessionId = preferenceHelper.getSessionId(sessionIncrementEventHandler)
159-
dataManager.trackInputFocus(term,
171+
disposable.add(dataManager.trackInputFocus(term,
160172
arrayOf(Constants.QueryConstants.SESSION to sessionId.toString(),
161173
Constants.QueryConstants.ACTION to Constants.QueryValues.EVENT_INPUT_FOCUS)).subscribeOn(Schedulers.io())
162-
.subscribe({ response ->
163-
if (response.isSuccessful) {
164-
d("Input focus event success")
165-
}
166-
}, { t ->
174+
.subscribe({}, { t ->
167175
t.printStackTrace()
176+
errorCallback?.invoke(t)
168177
e("Input focus event error: ${t.message}")
169-
})
178+
}))
170179
}
171180

172181
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
package io.constructor.data
2+
3+
import io.reactivex.annotations.Nullable
4+
5+
class ConstructorData<V> private constructor(@param:Nullable @field:Nullable
6+
private val value: V?, @param:Nullable @field:Nullable
7+
private val error: Throwable?, var networkError: Boolean = false) {
8+
9+
val isEmpty: Boolean
10+
get() = value == null && error == null
11+
12+
val isError: Boolean
13+
get() = error != null
14+
15+
fun onValue(action: (V) -> Unit): ConstructorData<V> {
16+
if (value != null) {
17+
action.invoke(value)
18+
}
19+
return this
20+
}
21+
22+
fun onEmpty(action: () -> Unit): ConstructorData<V> {
23+
if (isEmpty) {
24+
action.invoke()
25+
}
26+
return this
27+
}
28+
29+
fun onError(action: (Throwable) -> Unit): ConstructorData<V> {
30+
if (error != null) {
31+
action.invoke(error)
32+
}
33+
return this
34+
}
35+
36+
fun hasValue(): Boolean {
37+
return value != null
38+
}
39+
40+
@Nullable
41+
fun get(): V? {
42+
return value
43+
}
44+
45+
fun error(): Throwable? {
46+
return error
47+
}
48+
49+
override fun toString(): String {
50+
return "Data{" + "value=" + value + ", error=" + error + '}'.toString()
51+
}
52+
53+
companion object {
54+
55+
fun <V> of(@Nullable value: V): ConstructorData<V> {
56+
return ConstructorData(nullIfEmptyCollection(value), null)
57+
}
58+
59+
private fun <V> nullIfEmptyCollection(value: V): V? {
60+
return if (value is Collection<*> && (value as Collection<*>).isEmpty())
61+
null
62+
else
63+
value
64+
}
65+
66+
fun <V> empty(): ConstructorData<V> {
67+
return ConstructorData(null, null)
68+
}
69+
70+
fun <V> error(error: Throwable?): ConstructorData<V> {
71+
return ConstructorData(null, error)
72+
}
73+
74+
fun <V> networkError(msg: String?): ConstructorData<V> {
75+
return ConstructorData(null, Exception(msg), true)
76+
}
77+
78+
fun <V> asError(data: ConstructorData<V>): ConstructorData<V> {
79+
return error(data.error)
80+
}
81+
}
82+
}

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

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,50 +2,54 @@ package io.constructor.data
22

33
import io.constructor.data.model.Suggestion
44
import io.constructor.data.remote.ConstructorApi
5+
import io.reactivex.Completable
56
import io.reactivex.Observable
6-
import io.reactivex.Single
7-
import retrofit2.HttpException
8-
import retrofit2.Response
97
import javax.inject.Inject
108
import javax.inject.Singleton
119

1210
@Singleton
1311
class DataManager @Inject
1412
constructor(private val constructorApi: ConstructorApi) {
1513

16-
fun getAutocompleteResults(text: String): Single<MutableList<Suggestion>> = constructorApi.getSuggestions(text).toObservable().map {
17-
if (it.isSuccessful) {
18-
it.body()
14+
fun getAutocompleteResults(text: String): Observable<ConstructorData<List<Suggestion>?>> = constructorApi.getSuggestions(text).map {
15+
if (!it.isError) {
16+
it.response()?.let {
17+
if (it.isSuccessful) {
18+
ConstructorData.of(it.body()?.sections?.suggestions)
19+
} else {
20+
ConstructorData.networkError(it.errorBody()?.string())
21+
}
22+
} ?: ConstructorData.error(it.error())
1923
} else {
20-
throw HttpException(it)
24+
ConstructorData.error(it.error())
2125
}
22-
}.flatMapIterable { result -> result.sections.suggestions }.toList()
26+
}.toObservable()
2327

24-
fun trackSelect(term: String, params: Array<Pair<String, String>> = arrayOf(), encodedParams: Array<Pair<String, String>> = arrayOf()): Observable<Response<String>> {
28+
fun trackSelect(term: String, params: Array<Pair<String, String>> = arrayOf(), encodedParams: Array<Pair<String, String>> = arrayOf()): Completable {
2529
return constructorApi.trackSelect(term, params.toMap(), encodedParams.toMap())
2630
}
2731

28-
fun trackSearch(term: String, params: Array<Pair<String, String>> = arrayOf(), encodedParams: Array<Pair<String, String>> = arrayOf()): Observable<Response<String>> {
32+
fun trackSearch(term: String, params: Array<Pair<String, String>> = arrayOf(), encodedParams: Array<Pair<String, String>> = arrayOf()): Completable {
2933
return constructorApi.trackSearch(term, params.toMap(), encodedParams.toMap())
3034
}
3135

32-
fun trackSessionStart(params: Array<Pair<String, String>>): Observable<Response<String>> {
36+
fun trackSessionStart(params: Array<Pair<String, String>>): Completable {
3337
return constructorApi.trackSessionStart(params.toMap())
3438
}
3539

36-
fun trackConversion(term: String, itemId: String, revenue: String? = null, params: Array<Pair<String, String>> = arrayOf()): Observable<Response<String>> {
40+
fun trackConversion(term: String, itemId: String, revenue: String? = null, params: Array<Pair<String, String>> = arrayOf()): Completable {
3741
return constructorApi.trackConversion(term, itemId, revenue, params.toMap())
3842
}
3943

40-
fun trackSearchResultClickThrough(term: String, itemId: String, position: String? = null, params: Array<Pair<String, String>> = arrayOf()): Observable<Response<String>> {
44+
fun trackSearchResultClickThrough(term: String, itemId: String, position: String? = null, params: Array<Pair<String, String>> = arrayOf()): Completable {
4145
return constructorApi.trackSearchResultClickThrough(term, itemId, position, params.toMap())
4246
}
4347

44-
fun trackSearchResultLoaded(term: String, reultCount: Int, params: Array<Pair<String, String>>): Observable<Response<String>> {
48+
fun trackSearchResultLoaded(term: String, reultCount: Int, params: Array<Pair<String, String>>): Completable {
4549
return constructorApi.trackSearchResultLoaded(term, reultCount, params.toMap())
4650
}
4751

48-
fun trackInputFocus(term: String?, params: Array<Pair<String, String>>): Observable<Response<String>> {
52+
fun trackInputFocus(term: String?, params: Array<Pair<String, String>>): Completable {
4953
return constructorApi.trackInputFocus(term, params.toMap())
5054
}
5155

0 commit comments

Comments
 (0)