Skip to content

Commit e309789

Browse files
Place Autocomplete SDK (#134)
* Place autocomplete module * Add compile options config for the Discover SDK * Discover improvements * Extract address autofill logic to base module * Place autocomplete interfaces * Move ImageInfo to common package * Photos for place autocomplete result * PlaceAutocompleteUiAdapter * Place Autocomplete Sample activities * Unit tests * Improve Place Autocomplete API; POIs support * Instrumentation tests
1 parent 9122d77 commit e309789

File tree

97 files changed

+4516
-373
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

97 files changed

+4516
-373
lines changed

.circleci/config.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ commands:
7070
./gradlew :sdk-common:lint
7171
./gradlew :autofill:lint
7272
./gradlew :discover:lint
73+
./gradlew :place-autocomplete:lint
7374
./gradlew :sample:lint
7475
./gradlew :base:ktlint
7576
./gradlew :sdk:ktlint
@@ -78,6 +79,7 @@ commands:
7879
./gradlew :sdk-common:ktlint
7980
./gradlew :autofill:ktlint
8081
./gradlew :discover:ktlint
82+
./gradlew :place-autocomplete:ktlint
8183
./gradlew :sample:ktlint
8284
./gradlew detektAll
8385
@@ -137,6 +139,7 @@ commands:
137139
./gradlew :sdk-common:testDebugUnitTest -Pcoverage
138140
./gradlew :autofill:testDebugUnitTest -Pcoverage
139141
./gradlew :discover:testDebugUnitTest -Pcoverage
142+
./gradlew :place-autocomplete:testDebugUnitTest -Pcoverage
140143
141144
store-results:
142145
parameters:
@@ -281,6 +284,8 @@ jobs:
281284
module_target: "autofill"
282285
- store-results:
283286
module_target: "discover"
287+
- store-results:
288+
module_target: "place-autocomplete"
284289
- run:
285290
name: Calculate coverage
286291
command: cd MapboxSearch && ./gradlew :sdk:testDebugUnitTestCoverage

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,13 @@
66
- [CORE] Now `SearchSuggestion` and `SearchResult` types provide a new field `fullAddress` which contains formatted text address from backend.
77
- [DISCOVER] Now `Discover` SDK provides callback-based alternatives for Kotlin suspend functions.
88
- [DISCOVER] Now `DiscoverResult` provides a new field `formattedAddress`.
9+
- [PLACE AUTOCOMPLETE] Place Autocomplete SDK is available now. See docs for more information.
10+
- [UI] `PlaceAutocompleteUiAdapter` type is available. It's a helper class for connecting `Place Autocomple SDK` with the `SearchResultsView`.
11+
- [UI] A new `SearchPlace.createFromPlaceAutocompleteResult()` function is available.
912

1013
### Breaking changes
1114
- [CORE] `com.mapbox.search.CompletionCallback` has been moved to `com.mapbox.search.common.CompletionCallback`.
15+
- [CORE] `com.mapbox.search.ImageInfo` has been moved to `com.mapbox.search.common.metadata.ImageInfo`.
1216

1317
### Bug fixes
1418
- [CORE] Fixed `SearchSuggestion.name` and `SearchResult.name` formatting for address types.

LICENSE.md

Lines changed: 326 additions & 0 deletions
Large diffs are not rendered by default.

MapboxSearch/autofill/src/androidTest/java/com/mapbox/search/autofill/AddressAutofillIntegrationTest.kt

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,10 @@ import androidx.test.platform.app.InstrumentationRegistry
77
import com.mapbox.android.core.location.LocationEngine
88
import com.mapbox.geojson.Point
99
import com.mapbox.search.base.SearchRequestContextProvider
10+
import com.mapbox.search.base.core.CoreApiType
1011
import com.mapbox.search.base.core.CoreEngineOptions
1112
import com.mapbox.search.base.core.CoreSearchEngine
13+
import com.mapbox.search.base.engine.TwoStepsToOneStepSearchEngineAdapter
1214
import com.mapbox.search.base.location.LocationEngineAdapter
1315
import com.mapbox.search.base.location.WrapperLocationProvider
1416
import com.mapbox.search.base.location.defaultLocationEngine
@@ -269,7 +271,7 @@ internal class AddressAutofillIntegrationTest {
269271
token: String,
270272
url: String,
271273
locationEngine: LocationEngine
272-
): AutofillSearchEngine {
274+
): TwoStepsToOneStepSearchEngineAdapter {
273275
val coreEngine = CoreSearchEngine(
274276
CoreEngineOptions(
275277
token,
@@ -283,7 +285,8 @@ internal class AddressAutofillIntegrationTest {
283285
),
284286
)
285287

286-
return AutofillSearchEngine(
288+
return TwoStepsToOneStepSearchEngineAdapter(
289+
apiType = CoreApiType.AUTOFILL,
287290
coreEngine = coreEngine,
288291
requestContextProvider = SearchRequestContextProvider(app),
289292
)

MapboxSearch/autofill/src/main/java/com/mapbox/search/autofill/AddressAutofill.kt

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -60,12 +60,10 @@ public interface AddressAutofill {
6060
accessToken: String,
6161
locationEngine: LocationEngine = defaultLocationEngine(),
6262
): AddressAutofill {
63-
return AddressAutofillImpl(
64-
AutofillSearchEngine.create(
65-
accessToken = accessToken,
66-
app = BaseSearchSdkInitializer.app,
67-
locationEngine = locationEngine
68-
)
63+
return AddressAutofillImpl.create(
64+
accessToken = accessToken,
65+
app = BaseSearchSdkInitializer.app,
66+
locationEngine = locationEngine
6967
)
7068
}
7169
}

MapboxSearch/autofill/src/main/java/com/mapbox/search/autofill/AddressAutofillImpl.kt

Lines changed: 67 additions & 111 deletions
Original file line numberDiff line numberDiff line change
@@ -1,153 +1,109 @@
11
package com.mapbox.search.autofill
22

3+
import android.app.Application
4+
import com.mapbox.android.core.location.LocationEngine
35
import com.mapbox.bindgen.Expected
4-
import com.mapbox.bindgen.ExpectedFactory.createError
5-
import com.mapbox.bindgen.ExpectedFactory.createValue
66
import com.mapbox.geojson.Point
7-
import com.mapbox.search.base.BaseResponseInfo
7+
import com.mapbox.search.base.SearchRequestContextProvider
8+
import com.mapbox.search.base.core.CoreApiType
9+
import com.mapbox.search.base.core.CoreEngineOptions
10+
import com.mapbox.search.base.core.CoreSearchEngine
811
import com.mapbox.search.base.core.createCoreReverseGeoOptions
912
import com.mapbox.search.base.core.createCoreSearchOptions
13+
import com.mapbox.search.base.engine.TwoStepsToOneStepSearchEngineAdapter
14+
import com.mapbox.search.base.location.LocationEngineAdapter
15+
import com.mapbox.search.base.location.WrapperLocationProvider
16+
import com.mapbox.search.base.record.IndexableRecordResolver
17+
import com.mapbox.search.base.record.SearchHistoryService
1018
import com.mapbox.search.base.result.BaseSearchResult
11-
import com.mapbox.search.base.result.BaseSearchSuggestion
12-
import com.mapbox.search.base.result.BaseSearchSuggestionType
13-
import kotlinx.coroutines.Deferred
14-
import kotlinx.coroutines.async
15-
import kotlinx.coroutines.coroutineScope
16-
17-
internal sealed class SearchSelectionResponse {
18-
19-
data class Suggestions(
20-
val suggestions: List<BaseSearchSuggestion>,
21-
val responseInfo: BaseResponseInfo,
22-
) : SearchSelectionResponse()
23-
24-
data class Result(
25-
val suggestion: BaseSearchSuggestion,
26-
val result: BaseSearchResult,
27-
val responseInfo: BaseResponseInfo,
28-
) : SearchSelectionResponse()
29-
30-
data class CategoryResult(
31-
val suggestion: BaseSearchSuggestion,
32-
val results: List<BaseSearchResult>,
33-
val responseInfo: BaseResponseInfo,
34-
) : SearchSelectionResponse()
35-
}
19+
import com.mapbox.search.base.result.SearchResultFactory
20+
import com.mapbox.search.base.utils.UserAgentProvider
21+
import java.util.concurrent.ExecutorService
22+
import java.util.concurrent.Executors
3623

3724
/**
3825
* Temporary implementation of the [AddressAutofill] based on the two-step search.
3926
*/
40-
internal class AddressAutofillImpl(private val searchEngine: AutofillSearchEngine) : AddressAutofill {
27+
internal class AddressAutofillImpl(private val searchEngine: TwoStepsToOneStepSearchEngineAdapter) : AddressAutofill {
4128

42-
override suspend fun suggestions(point: Point, options: AddressAutofillOptions): Expected<Exception, List<AddressAutofillSuggestion>> {
29+
override suspend fun suggestions(
30+
point: Point,
31+
options: AddressAutofillOptions
32+
): Expected<Exception, List<AddressAutofillSuggestion>> {
4333
val coreOptions = createCoreReverseGeoOptions(
4434
point = point,
4535
countries = options.countries?.map { it.code },
4636
language = options.language?.let { listOf(it.code) },
4737
)
4838

49-
return searchEngine.search(coreOptions).mapValue { (results, _) ->
39+
return searchEngine.reverseGeocoding(coreOptions).mapValue { (results, _) ->
5040
results.toAddressAutofillSuggestions()
5141
}
5242
}
5343

54-
override suspend fun suggestions(query: Query, options: AddressAutofillOptions): Expected<Exception, List<AddressAutofillSuggestion>> {
55-
val response = searchEngine.search(
56-
query = query.query,
57-
options = createCoreSearchOptions(
58-
countries = options.countries?.map { it.code },
59-
language = options.language?.let { listOf(it.code) },
60-
limit = 10,
61-
ignoreUR = true,
62-
)
44+
override suspend fun suggestions(
45+
query: Query,
46+
options: AddressAutofillOptions
47+
): Expected<Exception, List<AddressAutofillSuggestion>> {
48+
val coreOptions = createCoreSearchOptions(
49+
countries = options.countries?.map { it.code },
50+
language = options.language?.let { listOf(it.code) },
51+
limit = 10,
52+
ignoreUR = true,
6353
)
64-
65-
return if (response.isValue) {
66-
forwardGeocoding(requireNotNull(response.value).first)
67-
} else {
68-
createError(requireNotNull(response.error))
54+
return searchEngine.searchResolveImmediately(query.query, coreOptions).mapValue {
55+
it.toAddressAutofillSuggestions()
6956
}
7057
}
7158

72-
private suspend fun forwardGeocoding(suggestions: List<BaseSearchSuggestion>): Expected<Exception, List<AddressAutofillSuggestion>> {
73-
return when {
74-
suggestions.isEmpty() -> {
75-
createValue(emptyList())
76-
}
77-
suggestions.all { it.isBatchResolveSupported } -> {
78-
searchEngine.select(suggestions).mapValue { (_, results, _) ->
79-
results.toAddressAutofillSuggestions()
80-
}
81-
}
82-
else -> {
83-
coroutineScope {
84-
val deferred: List<Deferred<Expected<Exception, SearchSelectionResponse>>> = suggestions
85-
// Filtering in order to avoid infinite recursion
86-
// because of some specific suggestions like "Did you mean recursion?"
87-
.filter { it.type !is BaseSearchSuggestionType.Query }
88-
.map { suggestion ->
89-
async {
90-
searchEngine.select(suggestion)
91-
}
92-
}
59+
internal companion object {
9360

94-
val selectionResponses = deferred.map { it.await() }
61+
private val DEFAULT_EXECUTOR: ExecutorService = Executors.newSingleThreadExecutor { runnable ->
62+
Thread(runnable, "AddressAutofill executor")
63+
}
9564

96-
val responses: List<Expected<Exception, List<AddressAutofillSuggestion>>> = selectionResponses
97-
.map { result ->
98-
if (result.isValue) {
99-
when (val response = requireNotNull(result.value)) {
100-
is SearchSelectionResponse.Suggestions -> {
101-
forwardGeocoding(response.suggestions)
102-
}
103-
is SearchSelectionResponse.Result -> {
104-
val autofillSuggestion = response.result.toAddressAutofillSuggestion()
105-
if (autofillSuggestion != null) {
106-
createValue(listOf(autofillSuggestion))
107-
} else {
108-
createValue(emptyList())
109-
}
110-
}
111-
is SearchSelectionResponse.CategoryResult -> {
112-
createValue(response.results.toAddressAutofillSuggestions())
113-
}
114-
}
115-
} else {
116-
createError(requireNotNull(result.error))
117-
}
118-
}
65+
fun create(
66+
accessToken: String,
67+
app: Application,
68+
locationEngine: LocationEngine,
69+
): AddressAutofillImpl {
70+
val coreEngine = CoreSearchEngine(
71+
CoreEngineOptions(
72+
accessToken,
73+
null,
74+
CoreApiType.AUTOFILL,
75+
UserAgentProvider.userAgent,
76+
null
77+
),
78+
WrapperLocationProvider(
79+
LocationEngineAdapter(app, locationEngine),
80+
null
81+
),
82+
)
11983

120-
// If at least one response completed successfully, return it.
121-
if (responses.isNotEmpty() && responses.all { it.isError }) {
122-
responses.first()
123-
} else {
124-
responses.asSequence()
125-
.mapNotNull { it.value }
126-
.flatten()
127-
.toList()
128-
.let {
129-
createValue(it)
130-
}
131-
}
132-
}
133-
}
134-
}
135-
}
84+
val engine = TwoStepsToOneStepSearchEngineAdapter(
85+
apiType = CoreApiType.AUTOFILL,
86+
coreEngine = coreEngine,
87+
requestContextProvider = SearchRequestContextProvider(app),
88+
historyService = SearchHistoryService.STUB,
89+
searchResultFactory = SearchResultFactory(IndexableRecordResolver.EMPTY),
90+
engineExecutorService = DEFAULT_EXECUTOR
91+
)
13692

137-
private companion object {
93+
return AddressAutofillImpl(engine)
94+
}
13895

139-
fun List<BaseSearchResult>.toAddressAutofillSuggestions() = mapNotNull { it.toAddressAutofillSuggestion() }
96+
private fun List<BaseSearchResult>.toAddressAutofillSuggestions() = mapNotNull { it.toAddressAutofillSuggestion() }
14097

141-
fun BaseSearchResult.toAddressAutofillSuggestion(): AddressAutofillSuggestion? {
98+
private fun BaseSearchResult.toAddressAutofillSuggestion(): AddressAutofillSuggestion? {
14299
// Filtering incomplete results
143100
val autofillAddress = AddressComponents.fromCoreSdkAddress(address, metadata) ?: return null
144-
val validCoordinate = coordinate
145101

146102
return AddressAutofillSuggestion(
147103
name = name,
148104
formattedAddress = fullAddress ?: autofillAddress.formattedAddress(),
149105
address = autofillAddress,
150-
coordinate = validCoordinate,
106+
coordinate = coordinate,
151107
)
152108
}
153109
}

MapboxSearch/base/build.gradle

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,8 @@ dependencies {
7373
api project(path: ':sdk-common')
7474
api "com.mapbox.search:mapbox-search-android-native:$search_native_version"
7575

76+
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlin_coroutines_version"
77+
7678
implementation "com.mapbox.common:common:$common_sdk_version"
7779

7880
// Explicit dependency for http service implementation, should be declared on the Common SDK side

0 commit comments

Comments
 (0)