|
1 | | -# Constructor.io Android Client Library |
| 1 | +[](https://jitpack.io/#Constructor-io/constructorio--client-android)  [](https://github.com/Constructor-io/constructorio-client-android/blob/master/LICENSE) |
2 | 2 |
|
| 3 | +# Constructor.io Android Client |
3 | 4 |
|
4 | | -An android client library for constructor.io suggestions engine |
| 5 | +An Android Client for [Constructor.io](http://constructor.io/). [Constructor.io](http://constructor.io/) provides search as a service that optimizes results using artificial intelligence (including natural language processing, re-ranking to optimize for conversions, and user personalization). |
5 | 6 |
|
6 | | -# Usage |
| 7 | +## 1. Install |
7 | 8 |
|
8 | | -## 1a. Install using gradle |
9 | | -Add following to main gradle file: |
| 9 | +Please follow the directions at [Jitpack.io](https://jitpack.io/#Constructor-io/constructorio-client-android/v1.0.0) to add the client to your project. |
10 | 10 |
|
11 | | -``` |
12 | | -allprojects { |
13 | | - repositories { |
14 | | - ... |
15 | | - maven { url 'https://jitpack.io' } |
16 | | - } |
17 | | -} |
18 | | -``` |
| 11 | +## 2. Retrieve an API key |
| 12 | +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. |
19 | 13 |
|
20 | | -and then in your app gradle file include Constructor.IO dependency: |
| 14 | +## 3. Implement the Autocomplete UI |
21 | 15 |
|
22 | | -``` |
23 | | -dependencies { |
24 | | - implementation 'com.github.Constructor-io:constructorio-client-android:v0.1' |
| 16 | +In your Application class add the following code with your key: |
| 17 | + |
| 18 | +```kotlin |
| 19 | +override fun onCreate() { |
| 20 | + super.onCreate() |
| 21 | + ConstructorIo.init(this, "YOUR API KEY") |
| 22 | + |
| 23 | + val fragment = supportFragmentManager.findFragmentById(R.id.fragment_suggestions) as SuggestionsFragment |
| 24 | + fragment.setConstructorListener(object : ConstructorListener { |
| 25 | + override fun onSuggestionSelected(term: String, group: Group?, autocompleteSection: String?) { |
| 26 | + Log.d(TAG, "onSuggestionSelected") |
| 27 | + } |
| 28 | + |
| 29 | + override fun onQuerySentToServer(query: String) { |
| 30 | + Log.d(TAG, "onQuerySentToServer") |
| 31 | + } |
| 32 | + |
| 33 | + override fun onSuggestionsRetrieved(suggestions: List<Suggestion>) { |
| 34 | + Log.d(TAG, "onSuggestionsRetrieved") |
| 35 | + } |
| 36 | + |
| 37 | + override fun onErrorGettingSuggestions(error: Throwable) { |
| 38 | + Log.d(TAG, "handle network error getting suggestion") |
| 39 | + } |
| 40 | + }) |
25 | 41 | } |
26 | 42 | ``` |
27 | 43 |
|
28 | | -## 1b. Manual import |
29 | | - |
30 | | -* Clone the android client repository from github ```git clone https://github.com/Constructor-io/constructorio-client-android.git``` |
31 | | -* Open and build the project in your IDE |
32 | | - |
33 | | -## 2. Retrieve an autocomplete key |
34 | | - |
35 | | -You can find this in your [Constructor.io dashboard](https://constructor.io/dashboard). |
36 | | - |
37 | | -Contact sales if you'd like to sign up, or support if you believe your company already has an account. |
| 44 | +### Selecting Results |
| 45 | +To respond to a user selecting an autocomplete result, use the `onSuggestionSelected` method. If the autocomplete result has both a suggested term to search for and a group to search within (as in Apples in Juice Drinks), the group will be passed into the method. |
38 | 46 |
|
39 | | -## 3. Init the Constructor.io Library |
| 47 | +### Performing Searches |
| 48 | +To respond to a user performing a search (instead of selecting an autocomplete result), use the `onQuerySentToServer` method. |
40 | 49 |
|
41 | | -In your Application class add the following code with your key: |
| 50 | +## 4. Customize the Autocomplete UI |
42 | 51 |
|
43 | | -``` |
44 | | - override fun onCreate() { |
45 | | - super.onCreate() |
46 | | - ConstructorIo.init(this, "your-key") |
47 | | - } |
48 | | -``` |
49 | | -## 4a. Use default out-of-the-box UI |
| 52 | +### Using the Default UI |
50 | 53 |
|
51 | 54 | To use the default, out-of-the-box UI, add the Sample Suggestions Fragment to your layout: |
52 | 55 |
|
53 | 56 | ```xml |
54 | | - <fragment |
55 | | - android:id="@+id/fragment_suggestions" |
56 | | - android:name="io.constructor.sample.SuggestionsFragment" |
57 | | - android:layout_width="match_parent" |
58 | | - android:layout_height="match_parent" /> |
59 | | -``` |
60 | | - |
61 | | -Skip to #5 if not customizing UI |
62 | | - |
63 | | -## 4b. Customize UI |
64 | | - |
65 | | -### Extend the suggestion screen fragment `BaseSuggestionFragment`. |
66 | | - |
67 | | -Implement the following: |
68 | | - |
69 | | -|Element name|Returned Type|Description| |
70 | | -|--|--|--| |
71 | | -|`layoutId`|`Int`|Returns your custom layout resource id for the fragment.| |
72 | | -|`getSuggestionsInputId`|`EditText`|Returns the id of the suggestion input field.| |
73 | | -|`getSuggestionListId`|`RecyclerView`|Returns your suggestion list id.| |
74 | | -|`getSuggestionAdapter`|Class that extends from `BaseSuggestionAdapter`|Returns your custom adapter.| |
75 | | -|`getProgressId`|Int`|Returns progress indicator id, used when request is being in progress. Return 0 for no progress| |
76 | | - |
77 | | -To see an example of usage, you can look at `SampleActivityCustom`. |
78 | | - |
79 | | -### Extend the suggestion items adapter `BaseSuggestionsAdapter`. |
80 | | - |
81 | | -Implement the following : |
82 | | - |
83 | | -|Element name|Returned Type|Description| |
84 | | -|--|--|--| |
85 | | -|`getItemLayoutId()`|`Int`|Returns your custom adapter item layout id for suggestion.| |
86 | | -|`getSuggestionNameId()`|`Int`|Return your text view id - the text will be the suggestion. | |
87 | | -|`getSuggestionGroupNameId()`|`Int`|Return your text view id - the text will be the suggestion group name, if found. | |
88 | | -|`onViewTypeSuggestion`|`String` text| Triggered when inflating an item which is a suggestion. Read below for more info.| |
89 | | -|`styleHighlightedSpans(spannable: Spannable, spanStart: Int, spanEnd: Int)`|`Unit`| Override to apply custom styling to highlighted part of suggestions search result. `spannable` is highlighted part of suggestion name, start and end mark position of the `spannable` within whole text.| |
90 | | - |
91 | | -abstract val styleHighlightedSpans: ((spannable: Spannable, spanStart: Int, spanEnd: Int) -> Unit)? |
92 | | - |
93 | | -In case you need to modify something in the ViewHolder (e.g make the group name bold) you can get a reference to it using `getHolder()` |
94 | | - |
95 | | -To see an example of usage, you can look at `SampleActivityCustom`. |
96 | | - |
97 | | -## 5. Get a reference to the `SuggestionsFragment` and add `ConstructorListener`: |
98 | | - |
99 | | -``` |
100 | | - val fragment = supportFragmentManager.findFragmentById(R.id.fragment_suggestions) as SuggestionsFragment |
101 | | - fragment.setConstructorListener(object : ConstructorListener { |
102 | | - override fun onSuggestionsRetrieved(suggestions: List<Suggestion>) { |
103 | | - //got suggestions for a query |
104 | | - } |
105 | | -
|
106 | | - override fun onQuerySentToServer(query: String) { |
107 | | - //request being made to server |
108 | | - } |
109 | | -
|
110 | | - override fun onSuggestionSelected(term: String, group: Group?, autocompleteSection: String?) { |
111 | | - //called when user taps on suggestion |
112 | | - } |
113 | | -
|
114 | | - override fun onErrorGettingSuggestions(error: Throwable) { |
115 | | - //called when there is error getting suggestions |
116 | | - } |
117 | | - }) |
118 | | -``` |
119 | | -# Additional references |
120 | | - |
121 | | -## Searching in Groups |
122 | | -Any data value can belong to a group. We will show the group name right below the item itself, if available. |
123 | | - |
124 | | -Let's remember the selection event. |
125 | | - |
126 | | -``` |
127 | | -fun onSuggestionSelected(term: String, group: Group?, autocompleteSection: String?) |
| 57 | +<fragment |
| 58 | + android:id="@+id/fragment_suggestions" |
| 59 | + android:name="io.constructor.sample.SuggestionsFragment" |
| 60 | + android:layout_width="match_parent" |
| 61 | + android:layout_height="match_parent"/> |
128 | 62 | ``` |
129 | 63 |
|
130 | | -Note `group` of type Group. It represents the group for an item and includes the following parameters: |
| 64 | +### Using a Custom UI |
131 | 65 |
|
132 | | -|Type|Name|Description| |
133 | | -|--|--|--| |
134 | | -|String|groupId|The group's id.| |
135 | | -|String|displayName|The group's display name.| |
136 | | -|String|path|The path to get more data for the group.| |
| 66 | +To fully customize the UI, extend the `BaseSuggestionFragment` and the `BaseSuggestionsAdapter` |
137 | 67 |
|
138 | | -Let's say you search for 'apple' and the results are: |
| 68 | +```kotlin |
| 69 | +class CustomSearchFragment : BaseSuggestionFragment() { |
139 | 70 |
|
140 | | - |
141 | | -``` |
142 | | -"suggestions": [ |
143 | | - { |
144 | | - "data": { |
145 | | - "groups": [ |
146 | | - { |
147 | | - "display_name": "food", |
148 | | - "group_id": "12", |
149 | | - "path": "/0/222/344" |
150 | | - }, |
151 | | - { |
152 | | - "display_name": "gadgets", |
153 | | - "group_id": "34", |
154 | | - "path": "/0/252/346/350" |
155 | | - } |
156 | | - ] |
157 | | - }, |
158 | | - "value": "apple" |
159 | | - } |
160 | | -] |
161 | | -``` |
162 | | - |
163 | | -We received two groups (food and gadgets) for our suggestion (apple). This means we'll have two suggestions in total: |
164 | | -1. 'apple' in group 'food' |
165 | | -2. 'apple' in group 'gadgets' |
166 | | - |
167 | | -When the user taps on (1), term will be `apple` and group name will be `food`. |
168 | | - |
169 | | -When the user taps on (2), term will be `apple` and group name will be `gadgets`. |
170 | | - |
171 | | -In other words, you can simply check whether the group property is null to find out if the user tapped on a search-in-group result: |
172 | | - |
173 | | -``` |
174 | | -fun onSuggestionSelected(term: String, group: Group?, autocompleteSection: String) { |
175 | | - if (group == null) { |
176 | | - // user tapped on an item |
177 | | -
|
178 | | - } else { |
179 | | - // user tapped on a group |
| 71 | + override fun onActivityCreated(savedInstanceState: Bundle?) { |
| 72 | + super.onActivityCreated(savedInstanceState) |
| 73 | + view?.backButton?.setOnClickListener { |
| 74 | + view?.input?.text?.clear() |
| 75 | + clearSuggestions() |
180 | 76 | } |
| 77 | + view?.searchButton?.setOnClickListener { triggerSearch() } |
181 | 78 | } |
182 | | -``` |
183 | | - |
184 | | -## ConstructorListener Interface |
185 | | - |
186 | | -### onQuerySentToServer |
187 | | - |
188 | | -`onQuerySentToServer(query: String)` |
189 | | - |
190 | | -Triggered when the query is sent to the server. |
191 | | - |
192 | | -Parameter|Type|Description |
193 | | -|--|--|--| |
194 | | -`query`|String|The query made by the user. |
195 | | - |
196 | | -### onSuggestionSelected |
197 | | - |
198 | | -`onSuggestionSelected(term: String, group: Group?, autocompleteSection: String?)` |
199 | | - |
200 | | -Triggered when a suggestion is selected. |
201 | 79 |
|
202 | | -|Parameter|Type|Description| |
203 | | -|--|--|--| |
204 | | -|`term`|String|The suggestion selected.| |
205 | | -|`group`|Group|Provides data on the group the selected term belongs to. Otherwise null.| |
206 | | -|`autocompleteSection`|String|The autocomplete section to which the selected term belongs (e.g "Search Suggestions", "Products"...)| |
207 | | - |
208 | | -### onSuggestionsRetrieved |
209 | | -`onSuggestionsRetrieved(suggestions: List<Suggestion>)` |
210 | | - |
211 | | -Triggered when the results for the query in question is retrieved. |
212 | | - |
213 | | -`suggestions` is a list of `Suggestion`s with the following parameters: |
214 | | - |
215 | | -|Parameter|Type|Description| |
216 | | -|--|--|--| |
217 | | -|`text`|String|The name of the suggestion.| |
218 | | -|`groups`|List<Group>|The top groups containing items that match for the query.| |
219 | | -|`matchedTerms`|List<String>|matched terms within the query| |
220 | | -|`sectionName`|String|name of the section eg. "Search Suggestions", "Products"| |
221 | | - |
222 | | -### onErrorGettingSuggestions |
223 | | -`override fun onErrorGettingSuggestions(error: Throwable)` |
224 | | - |
225 | | -Triggered when error occured while requesting suggestions. |
226 | | - |
227 | | -|Parameter|Type|Description| |
228 | | -|--|--|--| |
229 | | -|`error`|Throwable|Exception thrown.| |
230 | | - |
231 | | -## BaseSuggestionFragment Abstract Class |
232 | | - |
233 | | -Default fragment expose two additional methods for easier implementing custom UI: |
| 80 | + // Return your custom adapter |
| 81 | + override fun getSuggestionAdapter(): BaseSuggestionsAdapter { |
| 82 | + return CustomSuggestionsAdapter() |
| 83 | + } |
234 | 84 |
|
235 | | -### trackSearch() |
| 85 | + // Return your id of the suggestion input field |
| 86 | + override fun getSuggestionsInputId(): Int { |
| 87 | + return R.id.input |
| 88 | + } |
236 | 89 |
|
237 | | -Manually track search using text in the input box. |
| 90 | + // Return your id of the suggestion list |
| 91 | + override fun getSuggestionListId(): Int { |
| 92 | + return R.id.suggestions |
| 93 | + } |
238 | 94 |
|
239 | | -### clearSuggestions() |
| 95 | + // Return your progress indicator id, used when request is being in progress. Return 0 for no progress |
| 96 | + override fun getProgressId(): Int { |
| 97 | + return 0 |
| 98 | + } |
| 99 | + |
| 100 | + // Return your custom layout resource id for the fragment |
| 101 | + override fun layoutId(): Int { |
| 102 | + return R.layout.fragment_custom_suggestions |
| 103 | + } |
240 | 104 |
|
241 | | -Clear input box and suggestion list. |
| 105 | +} |
242 | 106 |
|
243 | | -## ConstructorIO public API |
| 107 | +class CustomSuggestionsAdapter() : BaseSuggestionsAdapter() { |
| 108 | + |
| 109 | + // Triggered when inflating an item which is a suggestion. |
| 110 | + override fun onViewTypeSuggestion(holder: ViewHolder, suggestion: String, highlightedSuggestion: Spannable, groupName: String?) { |
| 111 | + holder.suggestionName.text = highlightedSuggestion |
| 112 | + val spans = highlightedSuggestion.getSpans(0, highlightedSuggestion.length, StyleSpan::class.java) |
| 113 | + spans.forEach { highlightedSuggestion.setSpan(ForegroundColorSpan(Color.parseColor("#222222")), highlightedSuggestion.getSpanStart(it), highlightedSuggestion.getSpanEnd(it), 0) } |
| 114 | + groupName?.let { holder.suggestionGroupName.text = holder.suggestionGroupName.context.getString(R.string.suggestion_group, it) } |
| 115 | + } |
244 | 116 |
|
245 | | -### trackConversion(term: String, itemId: String, revenue: String?) |
| 117 | + // Return your custom adapter item layout id for suggestion |
| 118 | + override val itemLayoutId: Int |
| 119 | + get() = R.layout.item_suggestion |
| 120 | + |
| 121 | + // Return your text view id - the text will be the suggestion. |
| 122 | + override val suggestionNameId: Int |
| 123 | + get() = R.id.suggestionName |
| 124 | + |
| 125 | + // Return your text view id - the text will be the suggestion group name, if present |
| 126 | + override val suggestionGroupNameId: Int |
| 127 | + get() = R.id.suggestionGroupName |
246 | 128 |
|
247 | | -Track conversion event |
| 129 | +} |
| 130 | +``` |
248 | 131 |
|
249 | | -|Parameter|Type|Description| |
250 | | -|--|--|--| |
251 | | -|`term`|String|Optional term for which tracking event is reported.| |
252 | | -|`itemId`|String|Id of item for which we want to trigger an event.| |
253 | | -|`revenue`|String|Optional revenue indicator.| |
| 132 | +## 5. Instrument Behavioral Events |
254 | 133 |
|
255 | | -### trackSearchResultClickThrough(term: String, itemId: String, position: String?) |
| 134 | +The Android Client sends behavioral events to [Constructor.io](http://constructor.io/) in order to continuously learn and improve results for future Autosuggest and Search requests. The Client only sends events in response to being called by the consuming app or in response to user interaction . For example, if the consuming app never calls the SDK code, no events will be sent. Besides the explicitly passed in event parameters, all user events contain a GUID based user ID that the client sets to identify the user as well as a session ID. |
256 | 135 |
|
257 | | -Track search result click event |
| 136 | +Three types of these events exist: |
258 | 137 |
|
259 | | -|Parameter|Type|Description| |
260 | | -|--|--|--| |
261 | | -|`term`|String|Term used for search.| |
262 | | -|`itemId`|String|Id of item for which we want to track an event.| |
263 | | -|`position`|String|Optional position of clicked item on the list.| |
| 138 | +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. **Search Events** measure user interaction with search results and the consuming app has to explicitly instrument them itself |
264 | 141 |
|
265 | | -### triggerSearchResultLoadedEvent(term: String, resultCount: Int) |
| 142 | +```kotlin |
| 143 | +import io.constructor.core.ConstructorIo |
266 | 144 |
|
267 | | -Track search results loaded event |
| 145 | +// Track search results loaded (term, resultCount) |
| 146 | +ConstructorIo.trackSearchResultLoaded("a search term", 123) |
268 | 147 |
|
269 | | -|Parameter|Type|Description| |
270 | | -|--|--|--| |
271 | | -|`term`|String|Term used for search.| |
272 | | -|`resultCount`|Int|Number of items found.| |
| 148 | +// Track search result click (term, itemId, position) |
| 149 | +ConstructorIo.trackSearchResultClickThrough("a search term", "an item id", "1") |
273 | 150 |
|
| 151 | +// Track conversion (item id, term, revenue) |
| 152 | +constructorIO.trackConversion("an item id", "a search term", "45.00") |
| 153 | +``` |
0 commit comments