diff --git a/CHANGELOG.md b/CHANGELOG.md index cac96a3be..bd38bb29a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ ### Breaking changes - [CORE] Access token needs to be assigned via `MapboxOptions.accessToken` now +- [Address Autofill, Place Autocomplete] Search is a two-steps action now, i.e. it returns `Suggestion`s (without the geo coordinate) at the first step and `Result` after suggestion selection. The `coordinate` field is no longer available in `Suggestion`. +- [Address Autofill] `suggestions()` is renamed to `reverseGeocoding()` ### Mapbox dependencies - Search Native SDK `2.0.0-alpha.4` diff --git a/MapboxSearch/autofill/api/api-metalava.txt b/MapboxSearch/autofill/api/api-metalava.txt index 9e2b471ea..1aecf716f 100644 --- a/MapboxSearch/autofill/api/api-metalava.txt +++ b/MapboxSearch/autofill/api/api-metalava.txt @@ -4,8 +4,8 @@ package com.mapbox.search.autofill { public interface AddressAutofill { method public default static com.mapbox.search.autofill.AddressAutofill create(com.mapbox.common.location.LocationProvider? locationProvider = ()); method public default static final com.mapbox.search.autofill.AddressAutofill create(); + method public suspend Object? reverseGeocoding(com.mapbox.geojson.Point point, com.mapbox.search.autofill.AddressAutofillOptions options, kotlin.coroutines.Continuation>> p); method public suspend Object? select(com.mapbox.search.autofill.AddressAutofillSuggestion suggestion, kotlin.coroutines.Continuation> p); - method public suspend Object? suggestions(com.mapbox.geojson.Point point, com.mapbox.search.autofill.AddressAutofillOptions options, kotlin.coroutines.Continuation>> p); method public suspend Object? suggestions(com.mapbox.search.autofill.Query query, com.mapbox.search.autofill.AddressAutofillOptions options, kotlin.coroutines.Continuation>> p); field public static final com.mapbox.search.autofill.AddressAutofill.Companion Companion; } @@ -26,16 +26,16 @@ package com.mapbox.search.autofill { @kotlinx.parcelize.Parcelize public final class AddressAutofillResult implements android.os.Parcelable { method public com.mapbox.search.autofill.AddressComponents getAddress(); + method public com.mapbox.geojson.Point getCoordinate(); method public com.mapbox.search.autofill.AddressAutofillSuggestion getSuggestion(); property public final com.mapbox.search.autofill.AddressComponents address; + property public final com.mapbox.geojson.Point coordinate; property public final com.mapbox.search.autofill.AddressAutofillSuggestion suggestion; } @kotlinx.parcelize.Parcelize public final class AddressAutofillSuggestion implements android.os.Parcelable { - method public com.mapbox.geojson.Point getCoordinate(); method public String getFormattedAddress(); method public String getName(); - property public final com.mapbox.geojson.Point coordinate; property public final String formattedAddress; property public final String name; } diff --git a/MapboxSearch/autofill/api/autofill.api b/MapboxSearch/autofill/api/autofill.api index 0377ee28d..f81227ea0 100644 --- a/MapboxSearch/autofill/api/autofill.api +++ b/MapboxSearch/autofill/api/autofill.api @@ -2,8 +2,8 @@ public abstract interface class com/mapbox/search/autofill/AddressAutofill { public static final field Companion Lcom/mapbox/search/autofill/AddressAutofill$Companion; public static fun create ()Lcom/mapbox/search/autofill/AddressAutofill; public static fun create (Lcom/mapbox/common/location/LocationProvider;)Lcom/mapbox/search/autofill/AddressAutofill; + public abstract fun reverseGeocoding (Lcom/mapbox/geojson/Point;Lcom/mapbox/search/autofill/AddressAutofillOptions;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public abstract fun select (Lcom/mapbox/search/autofill/AddressAutofillSuggestion;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public abstract fun suggestions (Lcom/mapbox/geojson/Point;Lcom/mapbox/search/autofill/AddressAutofillOptions;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public abstract fun suggestions (Lcom/mapbox/search/autofill/Query;Lcom/mapbox/search/autofill/AddressAutofillOptions;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } @@ -41,6 +41,7 @@ public final class com/mapbox/search/autofill/AddressAutofillResult : android/os public fun describeContents ()I public fun equals (Ljava/lang/Object;)Z public final fun getAddress ()Lcom/mapbox/search/autofill/AddressComponents; + public final fun getCoordinate ()Lcom/mapbox/geojson/Point; public final fun getSuggestion ()Lcom/mapbox/search/autofill/AddressAutofillSuggestion; public fun hashCode ()I public fun toString ()Ljava/lang/String; @@ -59,7 +60,6 @@ public final class com/mapbox/search/autofill/AddressAutofillSuggestion : androi public static final field CREATOR Landroid/os/Parcelable$Creator; public fun describeContents ()I public fun equals (Ljava/lang/Object;)Z - public final fun getCoordinate ()Lcom/mapbox/geojson/Point; public final fun getFormattedAddress ()Ljava/lang/String; public final fun getName ()Ljava/lang/String; public fun hashCode ()I diff --git a/MapboxSearch/autofill/src/androidTest/java/com/mapbox/search/autofill/AddressAutofillIntegrationTest.kt b/MapboxSearch/autofill/src/androidTest/java/com/mapbox/search/autofill/AddressAutofillIntegrationTest.kt index 997d463ed..4c1539ee3 100644 --- a/MapboxSearch/autofill/src/androidTest/java/com/mapbox/search/autofill/AddressAutofillIntegrationTest.kt +++ b/MapboxSearch/autofill/src/androidTest/java/com/mapbox/search/autofill/AddressAutofillIntegrationTest.kt @@ -13,13 +13,14 @@ import com.mapbox.search.base.core.CoreApiType import com.mapbox.search.base.core.CoreEngineOptions import com.mapbox.search.base.core.CoreSearchEngine import com.mapbox.search.base.core.getUserActivityReporter -import com.mapbox.search.base.engine.TwoStepsToOneStepSearchEngineAdapter import com.mapbox.search.base.location.LocationEngineAdapter import com.mapbox.search.base.location.WrapperLocationProvider import com.mapbox.search.base.location.defaultLocationProvider import com.mapbox.search.common.IsoCountryCode import com.mapbox.search.common.IsoLanguageCode import com.mapbox.search.common.SearchRequestException +import kotlinx.coroutines.async +import kotlinx.coroutines.awaitAll import kotlinx.coroutines.runBlocking import okhttp3.mockwebserver.Dispatcher import okhttp3.mockwebserver.MockResponse @@ -53,7 +54,7 @@ internal class AddressAutofillIntegrationTest { ) addressAutofill = AddressAutofillImpl( - searchEngine = engine, + autofillEngine = engine, activityReporter = getUserActivityReporter() ) } @@ -96,7 +97,7 @@ internal class AddressAutofillIntegrationTest { val point = Point.fromLngLat(-77.03398187174899, 38.8999032596197) runBlocking { - addressAutofill.suggestions(point, options) + addressAutofill.reverseGeocoding(point, options) } val request = mockServer.takeRequest() @@ -148,7 +149,6 @@ internal class AddressAutofillIntegrationTest { val firstSuggestion = results.first() assertEquals("740 15th St NW", firstSuggestion.name) - assertEquals(Point.fromLngLat(-77.03375, 38.89936), firstSuggestion.coordinate) val selectionResponse = runBlocking { addressAutofill.select(firstSuggestion) @@ -167,6 +167,13 @@ internal class AddressAutofillIntegrationTest { assertEquals("United States", resultAddress.country) assertEquals("us", resultAddress.countryIso1) assertEquals("US-DC", resultAddress.countryIso2) + + val autofillResult = runBlocking { + addressAutofill.select(firstSuggestion) + } + assertTrue(autofillResult.isValue) + val autofillResultValue: AddressAutofillResult = requireNotNull(autofillResult.value) + assertEquals(Point.fromLngLat(-77.03375, 38.89936), autofillResultValue.coordinate) } @Test @@ -187,7 +194,16 @@ internal class AddressAutofillIntegrationTest { assertTrue(response.isValue) val results = requireNotNull(response.value) - assertEquals(2, results.size) + assertEquals(3, results.size) + + val autofillResult = runBlocking { + results.map { suggestion -> + async { + addressAutofill.select(suggestion) + } + }.awaitAll() + }.filter { result -> result.isValue } + assertEquals(2, autofillResult.size) } @Test @@ -208,10 +224,15 @@ internal class AddressAutofillIntegrationTest { val response = runBlocking { addressAutofill.suggestions(TEST_QUERY, AddressAutofillOptions()) } + assertTrue(response.isValue) + assertEquals(3, response.value!!.size) - assertTrue(response.isError) - val error = requireNotNull(response.error) - assertEquals(SearchRequestException("", 501), error) + response.value!!.map { suggestion -> + val selectionResult = runBlocking { + addressAutofill.select(suggestion) + } + assertTrue(selectionResult.isError) + } } @Test @@ -282,7 +303,7 @@ internal class AddressAutofillIntegrationTest { app: Application, url: String, locationProvider: LocationProvider? - ): TwoStepsToOneStepSearchEngineAdapter { + ): AutofillSearchEngine { val coreEngine = CoreSearchEngine( CoreEngineOptions( baseUrl = url, @@ -295,8 +316,7 @@ internal class AddressAutofillIntegrationTest { ), ) - return TwoStepsToOneStepSearchEngineAdapter( - apiType = CoreApiType.AUTOFILL, + return AutofillSearchEngine( coreEngine = coreEngine, requestContextProvider = SearchRequestContextProvider(app), ) diff --git a/MapboxSearch/autofill/src/main/java/com/mapbox/search/autofill/AddressAutofill.kt b/MapboxSearch/autofill/src/main/java/com/mapbox/search/autofill/AddressAutofill.kt index 0f6d873c4..e3f3bfc86 100644 --- a/MapboxSearch/autofill/src/main/java/com/mapbox/search/autofill/AddressAutofill.kt +++ b/MapboxSearch/autofill/src/main/java/com/mapbox/search/autofill/AddressAutofill.kt @@ -20,10 +20,10 @@ public interface AddressAutofill { * @param options Request options. * @return Result of the search request, one of error or value. */ - public suspend fun suggestions( + public suspend fun reverseGeocoding( point: Point, options: AddressAutofillOptions - ): Expected> + ): Expected> /** * Performs forward geocoding request. diff --git a/MapboxSearch/autofill/src/main/java/com/mapbox/search/autofill/AddressAutofillImpl.kt b/MapboxSearch/autofill/src/main/java/com/mapbox/search/autofill/AddressAutofillImpl.kt index f32e673e5..1967b4591 100644 --- a/MapboxSearch/autofill/src/main/java/com/mapbox/search/autofill/AddressAutofillImpl.kt +++ b/MapboxSearch/autofill/src/main/java/com/mapbox/search/autofill/AddressAutofillImpl.kt @@ -13,13 +13,14 @@ import com.mapbox.search.base.core.CoreSearchEngine import com.mapbox.search.base.core.createCoreReverseGeoOptions import com.mapbox.search.base.core.createCoreSearchOptions import com.mapbox.search.base.core.getUserActivityReporter -import com.mapbox.search.base.engine.TwoStepsToOneStepSearchEngineAdapter import com.mapbox.search.base.location.LocationEngineAdapter import com.mapbox.search.base.location.WrapperLocationProvider import com.mapbox.search.base.record.IndexableRecordResolver import com.mapbox.search.base.record.SearchHistoryService import com.mapbox.search.base.result.BaseSearchResult +import com.mapbox.search.base.result.BaseSearchSuggestion import com.mapbox.search.base.result.SearchResultFactory +import com.mapbox.search.base.utils.extension.flatMap import com.mapbox.search.internal.bindgen.UserActivityReporterInterface import java.util.concurrent.ExecutorService import java.util.concurrent.Executors @@ -28,14 +29,15 @@ import java.util.concurrent.Executors * Temporary implementation of the [AddressAutofill] based on the two-step search. */ internal class AddressAutofillImpl( - private val searchEngine: TwoStepsToOneStepSearchEngineAdapter, - private val activityReporter: UserActivityReporterInterface + private val autofillEngine: AutofillSearchEngine, + private val activityReporter: UserActivityReporterInterface, + private val resultFactory: AddressAutofillResultFactory = AddressAutofillResultFactory() ) : AddressAutofill { - override suspend fun suggestions( + override suspend fun reverseGeocoding( point: Point, options: AddressAutofillOptions - ): Expected> { + ): Expected> { activityReporter.reportActivity("address-autofill-reverse-geocoding") val coreOptions = createCoreReverseGeoOptions( @@ -44,8 +46,17 @@ internal class AddressAutofillImpl( language = options.language?.let { listOf(it.code) }, ) - return searchEngine.reverseGeocoding(coreOptions).mapValue { (results, _) -> - results.toAddressAutofillSuggestions() + return autofillEngine.search(coreOptions).mapValue { (results, _) -> + results.mapNotNull { + val expected = resultFactory.createAddressAutofillResultOrNull(it) + if (expected.isValue) expected.value else null + } + }.let { result -> + if (result.isValue && result.value.isNullOrEmpty()) { + ExpectedFactory.createError(Exception("No results for point $point")) + } else { + result + } } } @@ -62,8 +73,9 @@ internal class AddressAutofillImpl( ignoreUR = true, addonAPI = mapOf("types" to "address", "streets" to "true") ) - return searchEngine.searchResolveImmediately(query.query, coreOptions).mapValue { - it.toAddressAutofillSuggestions() + + return autofillEngine.search(query.query, coreOptions).mapValue { (suggestions, _) -> + resultFactory.createAddressAutofillSuggestions(suggestions) } } @@ -72,7 +84,28 @@ internal class AddressAutofillImpl( ): Expected { activityReporter.reportActivity("address-autofill-suggestion-select") - return ExpectedFactory.createValue(AddressAutofillResult(suggestion, suggestion.address)) + return if (suggestion.underlying == null) { + ExpectedFactory.createError(Exception("AddressAutofillSuggestion doesn't contain underlying suggestion")) + } else { + val baseResult = selectRaw(suggestion.underlying).value + if (baseResult == null) { + ExpectedFactory.createError(Exception("No results for suggestion $suggestion")) + } else { + resultFactory.createAddressAutofillResultOrNull(baseResult) + } + } + } + + private suspend fun selectRaw(suggestion: BaseSearchSuggestion): Expected { + return autofillEngine.select(suggestion).flatMap { + when (it) { + is AutofillSearchEngine.SearchSelectionResponse.Result -> ExpectedFactory.createValue(it.result) + else -> { + // Shouldn't happen because we don't allow suggestions of type Category and Query + ExpectedFactory.createError(Exception("Unsupported suggestion type: $suggestion")) + } + } + } } internal companion object { @@ -98,8 +131,7 @@ internal class AddressAutofillImpl( ), ) - val engine = TwoStepsToOneStepSearchEngineAdapter( - apiType = CoreApiType.AUTOFILL, + val engine = AutofillSearchEngine( coreEngine = coreEngine, requestContextProvider = SearchRequestContextProvider(app), historyService = SearchHistoryService.STUB, @@ -108,23 +140,9 @@ internal class AddressAutofillImpl( ) return AddressAutofillImpl( - searchEngine = engine, + autofillEngine = engine, activityReporter = getUserActivityReporter() ) } - - private fun List.toAddressAutofillSuggestions() = mapNotNull { it.toAddressAutofillSuggestion() } - - private fun BaseSearchResult.toAddressAutofillSuggestion(): AddressAutofillSuggestion? { - // Filtering incomplete results - val autofillAddress = AddressComponents.fromCoreSdkAddress(address, metadata) ?: return null - - return AddressAutofillSuggestion( - name = name, - formattedAddress = fullAddress ?: autofillAddress.formattedAddress(), - address = autofillAddress, - coordinate = coordinate, - ) - } } } diff --git a/MapboxSearch/autofill/src/main/java/com/mapbox/search/autofill/AddressAutofillResult.kt b/MapboxSearch/autofill/src/main/java/com/mapbox/search/autofill/AddressAutofillResult.kt index 4d2a3719f..d83782853 100644 --- a/MapboxSearch/autofill/src/main/java/com/mapbox/search/autofill/AddressAutofillResult.kt +++ b/MapboxSearch/autofill/src/main/java/com/mapbox/search/autofill/AddressAutofillResult.kt @@ -1,6 +1,7 @@ package com.mapbox.search.autofill import android.os.Parcelable +import com.mapbox.geojson.Point import kotlinx.parcelize.Parcelize /** @@ -14,6 +15,11 @@ public class AddressAutofillResult internal constructor( */ public val suggestion: AddressAutofillSuggestion, + /** + * Place geographic point. + */ + public val coordinate: Point, + /** * Detailed address components like street, house number, etc. */ @@ -30,6 +36,7 @@ public class AddressAutofillResult internal constructor( other as AddressAutofillResult if (suggestion != other.suggestion) return false + if (coordinate != other.coordinate) return false if (address != other.address) return false return true @@ -40,6 +47,7 @@ public class AddressAutofillResult internal constructor( */ override fun hashCode(): Int { var result = suggestion.hashCode() + result = 31 * result + coordinate.hashCode() result = 31 * result + address.hashCode() return result } @@ -48,6 +56,6 @@ public class AddressAutofillResult internal constructor( * @suppress */ override fun toString(): String { - return "AddressAutofillResult(suggestion=$suggestion, address=$address)" + return "AddressAutofillResult(suggestion=$suggestion, coordinate=$coordinate, address=$address)" } } diff --git a/MapboxSearch/autofill/src/main/java/com/mapbox/search/autofill/AddressAutofillResultFactory.kt b/MapboxSearch/autofill/src/main/java/com/mapbox/search/autofill/AddressAutofillResultFactory.kt new file mode 100644 index 000000000..50ff66fd2 --- /dev/null +++ b/MapboxSearch/autofill/src/main/java/com/mapbox/search/autofill/AddressAutofillResultFactory.kt @@ -0,0 +1,51 @@ +package com.mapbox.search.autofill + +import com.mapbox.bindgen.Expected +import com.mapbox.bindgen.ExpectedFactory +import com.mapbox.search.base.result.BaseSearchResult +import com.mapbox.search.base.result.BaseSearchSuggestion + +internal class AddressAutofillResultFactory { + + fun createAddressAutofillResultOrNull(result: BaseSearchResult): Expected { + return result.toAddressAutofillResult()?.let { + ExpectedFactory.createValue(it) + } ?: ExpectedFactory.createError(Exception("Unable to create AddressAutofillResult from $result")) + } + + private fun BaseSearchResult.toAddressAutofillResult(): AddressAutofillResult? { + val suggestion = this.toAddressAutofillSuggestion() ?: return null + return AddressAutofillResult( + suggestion = suggestion, + coordinate = this.coordinate, + address = suggestion.address + ) + } + + private fun BaseSearchResult.toAddressAutofillSuggestion(): AddressAutofillSuggestion? { + // Filtering incomplete results + val autofillAddress = AddressComponents.fromCoreSdkAddress(address, metadata) ?: return null + + return AddressAutofillSuggestion( + name = name, + formattedAddress = fullAddress ?: autofillAddress.formattedAddress(), + address = autofillAddress, + underlying = null, + ) + } + + fun createAddressAutofillSuggestions(baseSuggestions: List): List = + baseSuggestions.mapNotNull { suggestion -> suggestion.toAddressAutofillSuggestion() } + + private fun BaseSearchSuggestion.toAddressAutofillSuggestion(): AddressAutofillSuggestion? { + // Filtering incomplete results + val autofillAddress = AddressComponents.fromCoreSdkAddress(address, metadata) ?: return null + + return AddressAutofillSuggestion( + name = name, + formattedAddress = fullAddress ?: autofillAddress.formattedAddress(), + address = autofillAddress, + underlying = this + ) + } +} diff --git a/MapboxSearch/autofill/src/main/java/com/mapbox/search/autofill/AddressAutofillSuggestion.kt b/MapboxSearch/autofill/src/main/java/com/mapbox/search/autofill/AddressAutofillSuggestion.kt index b04ad7d8c..2f9cb9457 100644 --- a/MapboxSearch/autofill/src/main/java/com/mapbox/search/autofill/AddressAutofillSuggestion.kt +++ b/MapboxSearch/autofill/src/main/java/com/mapbox/search/autofill/AddressAutofillSuggestion.kt @@ -1,7 +1,7 @@ package com.mapbox.search.autofill import android.os.Parcelable -import com.mapbox.geojson.Point +import com.mapbox.search.base.result.BaseSearchSuggestion import kotlinx.parcelize.Parcelize /** @@ -20,16 +20,14 @@ public class AddressAutofillSuggestion internal constructor( */ public val formattedAddress: String, - /** - * Address geographic point. - */ - public val coordinate: Point, - /** * @suppress */ @JvmSynthetic internal val address: AddressComponents, + + @JvmSynthetic + internal val underlying: BaseSearchSuggestion? ) : Parcelable { /** @@ -43,7 +41,6 @@ public class AddressAutofillSuggestion internal constructor( if (name != other.name) return false if (formattedAddress != other.formattedAddress) return false - if (coordinate != other.coordinate) return false return true } @@ -54,7 +51,6 @@ public class AddressAutofillSuggestion internal constructor( override fun hashCode(): Int { var result = name.hashCode() result = 31 * result + formattedAddress.hashCode() - result = 31 * result + coordinate.hashCode() return result } @@ -64,8 +60,7 @@ public class AddressAutofillSuggestion internal constructor( override fun toString(): String { return "AddressAutofillSuggestion(" + "name='$name', " + - "formattedAddress='$formattedAddress', " + - "coordinate=$coordinate" + + "formattedAddress='$formattedAddress'" + ")" } } diff --git a/MapboxSearch/autofill/src/main/java/com/mapbox/search/autofill/AutofillSearchEngine.kt b/MapboxSearch/autofill/src/main/java/com/mapbox/search/autofill/AutofillSearchEngine.kt new file mode 100644 index 000000000..cee9a6b68 --- /dev/null +++ b/MapboxSearch/autofill/src/main/java/com/mapbox/search/autofill/AutofillSearchEngine.kt @@ -0,0 +1,273 @@ +package com.mapbox.search.autofill + +import android.app.Application +import com.mapbox.bindgen.Expected +import com.mapbox.bindgen.ExpectedFactory.createError +import com.mapbox.bindgen.ExpectedFactory.createValue +import com.mapbox.common.location.LocationProvider +import com.mapbox.search.base.BaseResponseInfo +import com.mapbox.search.base.BaseSearchCallback +import com.mapbox.search.base.BaseSearchSdkInitializer +import com.mapbox.search.base.BaseSearchSelectionCallback +import com.mapbox.search.base.BaseSearchSuggestionsCallback +import com.mapbox.search.base.SearchRequestContextProvider +import com.mapbox.search.base.core.CoreApiType +import com.mapbox.search.base.core.CoreEngineOptions +import com.mapbox.search.base.core.CoreReverseGeoOptions +import com.mapbox.search.base.core.CoreSearchEngine +import com.mapbox.search.base.core.CoreSearchEngineInterface +import com.mapbox.search.base.core.CoreSearchOptions +import com.mapbox.search.base.engine.BaseSearchEngine +import com.mapbox.search.base.engine.OneStepRequestCallbackWrapper +import com.mapbox.search.base.engine.TwoStepsRequestCallbackWrapper +import com.mapbox.search.base.failDebug +import com.mapbox.search.base.location.LocationEngineAdapter +import com.mapbox.search.base.location.WrapperLocationProvider +import com.mapbox.search.base.record.IndexableRecordResolver +import com.mapbox.search.base.record.SearchHistoryService +import com.mapbox.search.base.result.BaseGeocodingCompatSearchSuggestion +import com.mapbox.search.base.result.BaseIndexableRecordSearchSuggestion +import com.mapbox.search.base.result.BaseSearchResult +import com.mapbox.search.base.result.BaseSearchSuggestion +import com.mapbox.search.base.result.BaseServerSearchSuggestion +import com.mapbox.search.base.result.SearchResultFactory +import com.mapbox.search.base.result.mapToCore +import com.mapbox.search.base.task.AsyncOperationTaskImpl +import com.mapbox.search.common.AsyncOperationTask +import com.mapbox.search.common.concurrent.SearchSdkMainThreadWorker +import com.mapbox.search.internal.bindgen.ApiType +import kotlinx.coroutines.suspendCancellableCoroutine +import java.util.concurrent.Executor +import java.util.concurrent.ExecutorService +import java.util.concurrent.Executors + +internal class AutofillSearchEngine( + private val coreEngine: CoreSearchEngineInterface, + private val requestContextProvider: SearchRequestContextProvider, + private val historyService: SearchHistoryService = SearchHistoryService.STUB, + private val searchResultFactory: SearchResultFactory = SearchResultFactory(IndexableRecordResolver.EMPTY), + private val engineExecutorService: ExecutorService = DEFAULT_EXECUTOR +) : BaseSearchEngine() { + + fun search( + query: String, + options: CoreSearchOptions, + executor: Executor, + callback: BaseSearchSuggestionsCallback, + ): AsyncOperationTask { + return makeRequest(callback) { task -> + val requestContext = requestContextProvider.provide(ApiType.AUTOFILL) + val requestId = coreEngine.search( + query, emptyList(), options, + TwoStepsRequestCallbackWrapper( + apiType = ApiType.AUTOFILL, + coreEngine = coreEngine, + historyService = historyService, + searchResultFactory = searchResultFactory, + callbackExecutor = executor, + workerExecutor = engineExecutorService, + searchRequestTask = task, + searchRequestContext = requestContext, + suggestion = null, + addResultToHistory = false, + ) + ) + task.addOnCancelledCallback { + coreEngine.cancel(requestId) + } + } + } + + suspend fun search(query: String, options: CoreSearchOptions): Expected, BaseResponseInfo>> { + return suspendCancellableCoroutine { continuation -> + val task = search(query, options, SearchSdkMainThreadWorker.mainExecutor, object : BaseSearchSuggestionsCallback { + override fun onSuggestions(suggestions: List, responseInfo: BaseResponseInfo) { + continuation.resumeWith( + Result.success(createValue(suggestions to responseInfo)) + ) + } + + override fun onError(e: Exception) { + continuation.resumeWith(Result.success(createError(e))) + } + }) + + continuation.invokeOnCancellation { + task.cancel() + } + } + } + + fun select( + suggestion: BaseSearchSuggestion, + executor: Executor, + callback: BaseSearchSelectionCallback, + ): AsyncOperationTask { + return when (suggestion) { + is BaseServerSearchSuggestion -> { + makeRequest(callback as BaseSearchSuggestionsCallback) { task -> + val requestContext = suggestion.requestOptions.requestContext + val requestId = coreEngine.retrieve( + suggestion.requestOptions.core, + suggestion.rawSearchResult.mapToCore(), + TwoStepsRequestCallbackWrapper( + apiType = ApiType.AUTOFILL, + coreEngine = coreEngine, + historyService = historyService, + searchResultFactory = searchResultFactory, + callbackExecutor = executor, + workerExecutor = engineExecutorService, + searchRequestTask = task, + searchRequestContext = requestContext, + suggestion = suggestion, + addResultToHistory = false, + ) + ) + task.addOnCancelledCallback { + coreEngine.cancel(requestId) + } + } + } + is BaseGeocodingCompatSearchSuggestion, + is BaseIndexableRecordSearchSuggestion -> { + val errorMsg = "Unsupported in Autofill suggestion type: $suggestion" + failDebug { errorMsg } + executor.execute { + callback.onError(Exception(errorMsg)) + } + AsyncOperationTaskImpl.COMPLETED + } + } + } + + suspend fun select(suggestion: BaseSearchSuggestion): Expected { + return suspendCancellableCoroutine { continuation -> + val task = select(suggestion, SearchSdkMainThreadWorker.mainExecutor, object : BaseSearchSelectionCallback { + override fun onSuggestions(suggestions: List, responseInfo: BaseResponseInfo) { + continuation.resumeWith( + Result.success(createValue(SearchSelectionResponse.Suggestions(suggestions, responseInfo))) + ) + } + + override fun onResult(suggestion: BaseSearchSuggestion, result: BaseSearchResult, responseInfo: BaseResponseInfo) { + continuation.resumeWith( + Result.success(createValue(SearchSelectionResponse.Result(suggestion, result, responseInfo))) + ) + } + + override fun onResults( + suggestion: BaseSearchSuggestion, + results: List, + responseInfo: BaseResponseInfo + ) { + continuation.resumeWith( + Result.success(createValue(SearchSelectionResponse.Results(suggestion, results, responseInfo))) + ) + } + + override fun onError(e: Exception) { + continuation.resumeWith(Result.success(createError(e))) + } + }) + + continuation.invokeOnCancellation { + task.cancel() + } + } + } + + fun search( + options: CoreReverseGeoOptions, + executor: Executor, + callback: BaseSearchCallback, + ): AsyncOperationTask { + return makeRequest(callback) { task -> + val requestContext = requestContextProvider.provide(CoreApiType.AUTOFILL) + val requestId = coreEngine.reverseGeocoding( + options, + OneStepRequestCallbackWrapper( + searchResultFactory = searchResultFactory, + callbackExecutor = executor, + workerExecutor = engineExecutorService, + searchRequestTask = task, + searchRequestContext = requestContext, + isOffline = false, + ) + ) + task.addOnCancelledCallback { + coreEngine.cancel(requestId) + } + } + } + + suspend fun search(options: CoreReverseGeoOptions): Expected, BaseResponseInfo>> { + return suspendCancellableCoroutine { continuation -> + val task = search(options, SearchSdkMainThreadWorker.mainExecutor, object : BaseSearchCallback { + override fun onResults(results: List, responseInfo: BaseResponseInfo) { + continuation.resumeWith( + Result.success(createValue(results to responseInfo)) + ) + } + + override fun onError(e: Exception) { + continuation.resumeWith(Result.success(createError(e))) + } + }) + + continuation.invokeOnCancellation { + task.cancel() + } + } + } + + sealed class SearchSelectionResponse { + + data class Suggestions( + val suggestions: List, + val responseInfo: BaseResponseInfo, + ) : SearchSelectionResponse() + + data class Result( + val suggestion: BaseSearchSuggestion, + val result: BaseSearchResult, + val responseInfo: BaseResponseInfo, + ) : SearchSelectionResponse() + + data class Results( + val suggestion: BaseSearchSuggestion, + val results: List, + val responseInfo: BaseResponseInfo, + ) : SearchSelectionResponse() + } + + companion object { + + private val DEFAULT_EXECUTOR: ExecutorService = Executors.newSingleThreadExecutor { runnable -> + Thread(runnable, "AddressAutofill executor") + } + + fun create( + app: Application, + locationProvider: LocationProvider, + apiType: ApiType = CoreApiType.AUTOFILL + ): AutofillSearchEngine { + val coreEngine = CoreSearchEngine( + CoreEngineOptions( + baseUrl = null, + apiType = apiType, + sdkInformation = BaseSearchSdkInitializer.sdkInformation, + eventsUrl = null, + ), + WrapperLocationProvider( + LocationEngineAdapter(app, locationProvider), + null + ), + ) + + return AutofillSearchEngine( + coreEngine = coreEngine, + requestContextProvider = SearchRequestContextProvider(app), + ) + } + } +} diff --git a/MapboxSearch/autofill/src/test/java/com/mapbox/search/autofill/AddressAutofillResultTest.kt b/MapboxSearch/autofill/src/test/java/com/mapbox/search/autofill/AddressAutofillResultTest.kt index 205882de5..803e358f7 100644 --- a/MapboxSearch/autofill/src/test/java/com/mapbox/search/autofill/AddressAutofillResultTest.kt +++ b/MapboxSearch/autofill/src/test/java/com/mapbox/search/autofill/AddressAutofillResultTest.kt @@ -23,7 +23,8 @@ internal class AddressAutofillResultTest { ToStringVerifier( clazz = AddressAutofillResult::class, objectsFactory = ReflectionObjectsFactory( - extraCreators = CommonSdkTypeObjectCreators.ALL_CREATORS + extraCreators = CommonSdkTypeObjectCreators.ALL_CREATORS + + TypeObjectCreator.SUGGESTION_CREATOR ) ).verify() } diff --git a/MapboxSearch/autofill/src/test/java/com/mapbox/search/autofill/AddressAutofillSuggestionTest.kt b/MapboxSearch/autofill/src/test/java/com/mapbox/search/autofill/AddressAutofillSuggestionTest.kt index 69f2f8798..0f6da07e5 100644 --- a/MapboxSearch/autofill/src/test/java/com/mapbox/search/autofill/AddressAutofillSuggestionTest.kt +++ b/MapboxSearch/autofill/src/test/java/com/mapbox/search/autofill/AddressAutofillSuggestionTest.kt @@ -23,9 +23,10 @@ internal class AddressAutofillSuggestionTest { ToStringVerifier( clazz = AddressAutofillSuggestion::class, objectsFactory = ReflectionObjectsFactory( - extraCreators = CommonSdkTypeObjectCreators.ALL_CREATORS + extraCreators = CommonSdkTypeObjectCreators.ALL_CREATORS + + TypeObjectCreator.SUGGESTION_CREATOR ), - ignoredProperties = listOf("address"), + ignoredProperties = listOf("address", "underlying"), ).verify() } } diff --git a/MapboxSearch/autofill/src/test/java/com/mapbox/search/autofill/TypeObjectCreator.kt b/MapboxSearch/autofill/src/test/java/com/mapbox/search/autofill/TypeObjectCreator.kt new file mode 100644 index 000000000..98cd4e851 --- /dev/null +++ b/MapboxSearch/autofill/src/test/java/com/mapbox/search/autofill/TypeObjectCreator.kt @@ -0,0 +1,34 @@ +package com.mapbox.search.autofill + +import com.mapbox.search.base.result.BaseSearchAddress +import com.mapbox.search.common.tests.CustomTypeObjectCreatorImpl +import com.mapbox.search.common.tests.createTestResultMetadata + +internal object TypeObjectCreator { + + val SUGGESTION_CREATOR = CustomTypeObjectCreatorImpl(AddressAutofillSuggestion::class) { mode -> + val searchAddress = BaseSearchAddress( + houseNumber = "5", + street = "Rue De Marseille", + neighborhood = "Porte-Saint-Martin", + locality = "10th arrondissement of Paris", + postcode = "75010", + place = "Paris", + district = "Paris district", + region = "Paris region", + country = "France" + ) + + val coreMetadata = createTestResultMetadata( + data = hashMapOf("iso_3166_1" to "fra", "iso_3166_2" to "fr") + ) + + val addressComponents = requireNotNull( + AddressComponents.fromCoreSdkAddress(searchAddress, coreMetadata) + ) + + listOf( + AddressAutofillSuggestion("name", "formattedAddress", addressComponents, null) + )[mode.ordinal] + } +} diff --git a/MapboxSearch/place-autocomplete/api/api-metalava.txt b/MapboxSearch/place-autocomplete/api/api-metalava.txt index a471f7eef..d7b7ed8ee 100644 --- a/MapboxSearch/place-autocomplete/api/api-metalava.txt +++ b/MapboxSearch/place-autocomplete/api/api-metalava.txt @@ -97,7 +97,6 @@ package com.mapbox.search.autocomplete { @kotlinx.parcelize.Parcelize public final class PlaceAutocompleteSuggestion implements android.os.Parcelable { method public java.util.List? getCategories(); - method public com.mapbox.geojson.Point getCoordinate(); method public Double? getDistanceMeters(); method public Double? getEtaMinutes(); method public String? getFormattedAddress(); @@ -106,7 +105,6 @@ package com.mapbox.search.autocomplete { method public java.util.List? getRoutablePoints(); method public com.mapbox.search.autocomplete.PlaceAutocompleteType getType(); property public final java.util.List? categories; - property public final com.mapbox.geojson.Point coordinate; property public final Double? distanceMeters; property public final Double? etaMinutes; property public final String? formattedAddress; diff --git a/MapboxSearch/place-autocomplete/api/place-autocomplete.api b/MapboxSearch/place-autocomplete/api/place-autocomplete.api index 2c97d2465..d2814e28e 100644 --- a/MapboxSearch/place-autocomplete/api/place-autocomplete.api +++ b/MapboxSearch/place-autocomplete/api/place-autocomplete.api @@ -121,7 +121,6 @@ public final class com/mapbox/search/autocomplete/PlaceAutocompleteSuggestion : public fun describeContents ()I public fun equals (Ljava/lang/Object;)Z public final fun getCategories ()Ljava/util/List; - public final fun getCoordinate ()Lcom/mapbox/geojson/Point; public final fun getDistanceMeters ()Ljava/lang/Double; public final fun getEtaMinutes ()Ljava/lang/Double; public final fun getFormattedAddress ()Ljava/lang/String; diff --git a/MapboxSearch/place-autocomplete/src/androidTest/java/com/mapbox/search/autocomplete/PlaceAutocompleteIntegrationTest.kt b/MapboxSearch/place-autocomplete/src/androidTest/java/com/mapbox/search/autocomplete/PlaceAutocompleteIntegrationTest.kt index 44e6ef6bb..eee724530 100644 --- a/MapboxSearch/place-autocomplete/src/androidTest/java/com/mapbox/search/autocomplete/PlaceAutocompleteIntegrationTest.kt +++ b/MapboxSearch/place-autocomplete/src/androidTest/java/com/mapbox/search/autocomplete/PlaceAutocompleteIntegrationTest.kt @@ -13,7 +13,6 @@ import com.mapbox.search.base.SearchRequestContextProvider import com.mapbox.search.base.core.CoreEngineOptions import com.mapbox.search.base.core.CoreSearchEngine import com.mapbox.search.base.core.getUserActivityReporter -import com.mapbox.search.base.engine.TwoStepsToOneStepSearchEngineAdapter import com.mapbox.search.base.location.LocationEngineAdapter import com.mapbox.search.base.location.WrapperLocationProvider import com.mapbox.search.base.location.defaultLocationProvider @@ -306,10 +305,15 @@ internal class PlaceAutocompleteIntegrationTest { val response = runBlocking { placeAutocomplete.suggestions(TEST_QUERY) } + assertTrue(response.isValue) + assertEquals(3, response.value!!.size) - assertTrue(response.isError) - val error = requireNotNull(response.error) - assertEquals(SearchRequestException("", 501), error) + response.value!!.map { suggestion -> + val selectionResult = runBlocking { + placeAutocomplete.select(suggestion) + } + assertTrue(selectionResult.isError) + } } @Test @@ -404,7 +408,7 @@ internal class PlaceAutocompleteIntegrationTest { assertEquals(3, suggestions.size) assertEquals("Starbucks", suggestions[0].name) - assertEquals("901 15th St NW, Washington, District of Columbia 20005, United States of America", suggestions[0].formattedAddress) + assertEquals("1401 New York Ave NW, Washington, District of Columbia 20005, United States of America", suggestions[0].formattedAddress) assertEquals(null, suggestions[0].routablePoints) val selectionResponse = runBlocking { @@ -513,7 +517,7 @@ internal class PlaceAutocompleteIntegrationTest { app: Application, url: String, locationProvider: LocationProvider? - ): TwoStepsToOneStepSearchEngineAdapter { + ): PlaceAutocompleteEngine { val coreEngine = CoreSearchEngine( CoreEngineOptions( baseUrl = url, @@ -526,8 +530,7 @@ internal class PlaceAutocompleteIntegrationTest { ), ) - return TwoStepsToOneStepSearchEngineAdapter( - apiType = ApiType.SBS, + return PlaceAutocompleteEngine( coreEngine = coreEngine, requestContextProvider = SearchRequestContextProvider(app), ) diff --git a/MapboxSearch/base/src/main/java/com/mapbox/search/base/engine/TwoStepsToOneStepSearchEngineAdapter.kt b/MapboxSearch/place-autocomplete/src/main/java/com/mapbox/search/autocomplete/PlaceAutocompleteEngine.kt similarity index 59% rename from MapboxSearch/base/src/main/java/com/mapbox/search/base/engine/TwoStepsToOneStepSearchEngineAdapter.kt rename to MapboxSearch/place-autocomplete/src/main/java/com/mapbox/search/autocomplete/PlaceAutocompleteEngine.kt index 373fa64bd..083a85d62 100644 --- a/MapboxSearch/base/src/main/java/com/mapbox/search/base/engine/TwoStepsToOneStepSearchEngineAdapter.kt +++ b/MapboxSearch/place-autocomplete/src/main/java/com/mapbox/search/autocomplete/PlaceAutocompleteEngine.kt @@ -1,19 +1,30 @@ -package com.mapbox.search.base.engine +package com.mapbox.search.autocomplete +import android.app.Application import com.mapbox.bindgen.Expected import com.mapbox.bindgen.ExpectedFactory +import com.mapbox.common.location.LocationProvider import com.mapbox.search.base.BaseResponseInfo import com.mapbox.search.base.BaseSearchCallback import com.mapbox.search.base.BaseSearchMultipleSelectionCallback +import com.mapbox.search.base.BaseSearchSdkInitializer import com.mapbox.search.base.BaseSearchSelectionCallback import com.mapbox.search.base.BaseSearchSuggestionsCallback import com.mapbox.search.base.SearchRequestContextProvider import com.mapbox.search.base.assertDebug import com.mapbox.search.base.core.CoreApiType +import com.mapbox.search.base.core.CoreEngineOptions import com.mapbox.search.base.core.CoreReverseGeoOptions +import com.mapbox.search.base.core.CoreSearchEngine import com.mapbox.search.base.core.CoreSearchEngineInterface import com.mapbox.search.base.core.CoreSearchOptions +import com.mapbox.search.base.engine.BaseSearchEngine +import com.mapbox.search.base.engine.OneStepRequestCallbackWrapper +import com.mapbox.search.base.engine.TwoStepsBatchRequestCallbackWrapper +import com.mapbox.search.base.engine.TwoStepsRequestCallbackWrapper import com.mapbox.search.base.failDebug +import com.mapbox.search.base.location.LocationEngineAdapter +import com.mapbox.search.base.location.WrapperLocationProvider import com.mapbox.search.base.logger.logd import com.mapbox.search.base.record.IndexableRecordResolver import com.mapbox.search.base.record.SearchHistoryService @@ -21,95 +32,30 @@ import com.mapbox.search.base.result.BaseGeocodingCompatSearchSuggestion import com.mapbox.search.base.result.BaseIndexableRecordSearchSuggestion import com.mapbox.search.base.result.BaseSearchResult import com.mapbox.search.base.result.BaseSearchSuggestion -import com.mapbox.search.base.result.BaseSearchSuggestionType import com.mapbox.search.base.result.BaseServerSearchSuggestion import com.mapbox.search.base.result.SearchResultFactory import com.mapbox.search.base.result.mapToCore import com.mapbox.search.base.task.AsyncOperationTaskImpl -import com.mapbox.search.base.utils.extension.suspendFlatMap import com.mapbox.search.common.AsyncOperationTask import com.mapbox.search.common.concurrent.SearchSdkMainThreadWorker -import kotlinx.coroutines.Deferred -import kotlinx.coroutines.async -import kotlinx.coroutines.coroutineScope +import com.mapbox.search.internal.bindgen.ApiType import kotlinx.coroutines.suspendCancellableCoroutine import java.util.concurrent.Executor import java.util.concurrent.ExecutorService import java.util.concurrent.Executors -/* -Search Engine that turns 2-step API into 1-step API by resolving all the suggestions during search call. -Currently used for the Address Autofill and Place Autocomplete use cases. - -TODO can be optimised by implementing [OneStepRequestCallbackWrapper] and [TwoStepsRequestCallbackWrapper] -using coroutines and removing extra executors - */ -class TwoStepsToOneStepSearchEngineAdapter( - private val apiType: CoreApiType, +internal class PlaceAutocompleteEngine( private val coreEngine: CoreSearchEngineInterface, private val requestContextProvider: SearchRequestContextProvider, private val historyService: SearchHistoryService = SearchHistoryService.STUB, - private val searchResultFactory: SearchResultFactory = SearchResultFactory(IndexableRecordResolver.EMPTY), + private val searchResultFactory: SearchResultFactory = SearchResultFactory( + IndexableRecordResolver.EMPTY + ), private val engineExecutorService: ExecutorService = DEFAULT_EXECUTOR, + private val apiType: ApiType = ApiType.SBS ) : BaseSearchEngine() { - suspend fun reverseGeocoding( - options: CoreReverseGeoOptions - ): Expected, BaseResponseInfo>> { - return suspendCancellableCoroutine { continuation -> - val task = reverseGeocoding(options, SearchSdkMainThreadWorker.mainExecutor, object : BaseSearchCallback { - override fun onResults(results: List, responseInfo: BaseResponseInfo) { - continuation.resumeWith( - Result.success(ExpectedFactory.createValue(results to responseInfo)) - ) - } - - override fun onError(e: Exception) { - continuation.resumeWith(Result.success(ExpectedFactory.createError(e))) - } - }) - - continuation.invokeOnCancellation { - task.cancel() - } - } - } - - private fun reverseGeocoding( - options: CoreReverseGeoOptions, - executor: Executor, - callback: BaseSearchCallback, - ): AsyncOperationTask { - return makeRequest(callback) { task -> - val requestContext = requestContextProvider.provide(apiType) - val requestId = coreEngine.reverseGeocoding( - options, - OneStepRequestCallbackWrapper( - searchResultFactory = searchResultFactory, - callbackExecutor = executor, - workerExecutor = engineExecutorService, - searchRequestTask = task, - searchRequestContext = requestContext, - isOffline = false, - ) - ) - task.addOnCancelledCallback { - coreEngine.cancel(requestId) - } - } - } - - suspend fun searchResolveImmediately( - query: String, - options: CoreSearchOptions, - allowCategorySuggestions: Boolean = true - ): Expected> { - return search(query = query, options = options).suspendFlatMap { (suggestions, _) -> - resolveAll(suggestions, allowCategorySuggestions) - } - } - - private fun search( + fun search( query: String, options: CoreSearchOptions, executor: Executor, @@ -138,29 +84,19 @@ class TwoStepsToOneStepSearchEngineAdapter( } } - suspend fun search( - query: String, - options: CoreSearchOptions - ): Expected, BaseResponseInfo>> { + suspend fun search(query: String, options: CoreSearchOptions): Expected, BaseResponseInfo>> { return suspendCancellableCoroutine { continuation -> - val task = search( - query, - options, - SearchSdkMainThreadWorker.mainExecutor, - object : BaseSearchSuggestionsCallback { - override fun onSuggestions( - suggestions: List, - responseInfo: BaseResponseInfo - ) { - continuation.resumeWith( - Result.success(ExpectedFactory.createValue(suggestions to responseInfo)) - ) - } + val task = search(query, options, SearchSdkMainThreadWorker.mainExecutor, object : BaseSearchSuggestionsCallback { + override fun onSuggestions(suggestions: List, responseInfo: BaseResponseInfo) { + continuation.resumeWith( + Result.success(ExpectedFactory.createValue(suggestions to responseInfo)) + ) + } - override fun onError(e: Exception) { - continuation.resumeWith(Result.success(ExpectedFactory.createError(e))) - } - }) + override fun onError(e: Exception) { + continuation.resumeWith(Result.success(ExpectedFactory.createError(e))) + } + }) continuation.invokeOnCancellation { task.cancel() @@ -168,7 +104,7 @@ class TwoStepsToOneStepSearchEngineAdapter( } } - private fun select( + fun select( suggestion: BaseSearchSuggestion, executor: Executor, callback: BaseSearchSelectionCallback, @@ -200,7 +136,7 @@ class TwoStepsToOneStepSearchEngineAdapter( } is BaseGeocodingCompatSearchSuggestion, is BaseIndexableRecordSearchSuggestion -> { - val errorMsg = "Unsupported suggestion type: $suggestion" + val errorMsg = "Unsupported in Autofill suggestion type: $suggestion" failDebug { errorMsg } executor.execute { callback.onError(Exception(errorMsg)) @@ -215,28 +151,13 @@ class TwoStepsToOneStepSearchEngineAdapter( val task = select(suggestion, SearchSdkMainThreadWorker.mainExecutor, object : BaseSearchSelectionCallback { override fun onSuggestions(suggestions: List, responseInfo: BaseResponseInfo) { continuation.resumeWith( - Result.success( - ExpectedFactory.createValue( - SearchSelectionResponse.Suggestions( - suggestions, - responseInfo - ) - ) - ) + Result.success(ExpectedFactory.createValue(SearchSelectionResponse.Suggestions(suggestions, responseInfo))) ) } override fun onResult(suggestion: BaseSearchSuggestion, result: BaseSearchResult, responseInfo: BaseResponseInfo) { continuation.resumeWith( - Result.success( - ExpectedFactory.createValue( - SearchSelectionResponse.Result( - suggestion, - result, - responseInfo - ) - ) - ) + Result.success(ExpectedFactory.createValue(SearchSelectionResponse.Result(suggestion, result, responseInfo))) ) } @@ -246,15 +167,7 @@ class TwoStepsToOneStepSearchEngineAdapter( responseInfo: BaseResponseInfo ) { continuation.resumeWith( - Result.success( - ExpectedFactory.createValue( - SearchSelectionResponse.Results( - suggestion, - results, - responseInfo - ) - ) - ) + Result.success(ExpectedFactory.createValue(SearchSelectionResponse.Results(suggestion, results, responseInfo))) ) } @@ -269,7 +182,7 @@ class TwoStepsToOneStepSearchEngineAdapter( } } - private fun selectBatch( + fun select( suggestions: List, executor: Executor, callback: BaseSearchMultipleSelectionCallback, @@ -335,29 +248,24 @@ class TwoStepsToOneStepSearchEngineAdapter( } } - private suspend fun selectBatch( - suggestions: List - ): Expected, List, BaseResponseInfo>> { + suspend fun select(suggestions: List): Expected, List, BaseResponseInfo>> { return suspendCancellableCoroutine { continuation -> - val task = selectBatch( - suggestions, - SearchSdkMainThreadWorker.mainExecutor, - object : BaseSearchMultipleSelectionCallback { - override fun onResult( - suggestions: List, - results: List, - responseInfo: BaseResponseInfo - ) { - continuation.resumeWith( - Result.success(ExpectedFactory.createValue(Triple(suggestions, results, responseInfo))) - ) - } + val task = select(suggestions, SearchSdkMainThreadWorker.mainExecutor, object : BaseSearchMultipleSelectionCallback { - override fun onError(e: Exception) { - continuation.resumeWith(Result.success(ExpectedFactory.createError(e))) - } + override fun onResult( + suggestions: List, + results: List, + responseInfo: BaseResponseInfo + ) { + continuation.resumeWith( + Result.success(ExpectedFactory.createValue(Triple(suggestions, results, responseInfo))) + ) } - ) + + override fun onError(e: Exception) { + continuation.resumeWith(Result.success(ExpectedFactory.createError(e))) + } + }) continuation.invokeOnCancellation { task.cancel() @@ -365,63 +273,46 @@ class TwoStepsToOneStepSearchEngineAdapter( } } - suspend fun resolveAll( - suggestions: List, - allowCategorySuggestions: Boolean, - ): Expected> { - return when { - suggestions.isEmpty() -> { - ExpectedFactory.createValue(emptyList()) - } - suggestions.all { it.isBatchResolveSupported } -> { - selectBatch(suggestions).mapValue { (_, results, _) -> results } + fun search( + options: CoreReverseGeoOptions, + executor: Executor, + callback: BaseSearchCallback, + ): AsyncOperationTask { + return makeRequest(callback) { task -> + val requestContext = requestContextProvider.provide(apiType) + val requestId = coreEngine.reverseGeocoding( + options, + OneStepRequestCallbackWrapper( + searchResultFactory = searchResultFactory, + callbackExecutor = executor, + workerExecutor = engineExecutorService, + searchRequestTask = task, + searchRequestContext = requestContext, + isOffline = false, + ) + ) + task.addOnCancelledCallback { + coreEngine.cancel(requestId) } - else -> { - coroutineScope { - val deferredSuggestions: List>> = suggestions - .filter { - when (it.type) { - // Filtering in order to avoid infinite recursion - // because of some specific suggestions like "Did you mean recursion?" - is BaseSearchSuggestionType.Query -> false - is BaseSearchSuggestionType.Category -> allowCategorySuggestions - else -> true - } - } - .map { suggestion -> - async { select(suggestion) } - } - - val responses: List>> = deferredSuggestions - .map { deferred -> - deferred.await().suspendFlatMap { response -> - when (response) { - is SearchSelectionResponse.Suggestions -> { - resolveAll(response.suggestions, allowCategorySuggestions) - } - is SearchSelectionResponse.Result -> { - ExpectedFactory.createValue(listOf(response.result)) - } - is SearchSelectionResponse.Results -> { - ExpectedFactory.createValue(response.results) - } - } - } - } - - // If at least one response completed successfully, return it. - if (responses.isNotEmpty() && responses.all { it.isError }) { - responses.first() - } else { - responses.asSequence() - .mapNotNull { it.value } - .flatten() - .toList() - .let { - ExpectedFactory.createValue(it) - } - } + } + } + + suspend fun search(options: CoreReverseGeoOptions): Expected, BaseResponseInfo>> { + return suspendCancellableCoroutine { continuation -> + val task = search(options, SearchSdkMainThreadWorker.mainExecutor, object : BaseSearchCallback { + override fun onResults(results: List, responseInfo: BaseResponseInfo) { + continuation.resumeWith( + Result.success(ExpectedFactory.createValue(results to responseInfo)) + ) + } + + override fun onError(e: Exception) { + continuation.resumeWith(Result.success(ExpectedFactory.createError(e))) } + }) + + continuation.invokeOnCancellation { + task.cancel() } } } @@ -446,9 +337,34 @@ class TwoStepsToOneStepSearchEngineAdapter( ) : SearchSelectionResponse() } - private companion object { - val DEFAULT_EXECUTOR: ExecutorService = Executors.newSingleThreadExecutor { runnable -> - Thread(runnable, "TwoStepsToOneStepSearchEngine executor") + companion object { + + private val DEFAULT_EXECUTOR: ExecutorService = + Executors.newSingleThreadExecutor { runnable -> + Thread(runnable, "AddressAutofill executor") + } + + fun create( + app: Application, + locationProvider: LocationProvider, + ): BaseSearchEngine { + val coreEngine = CoreSearchEngine( + CoreEngineOptions( + baseUrl = null, + apiType = CoreApiType.SBS, + sdkInformation = BaseSearchSdkInitializer.sdkInformation, + eventsUrl = null, + ), + WrapperLocationProvider( + LocationEngineAdapter(app, locationProvider), + null + ), + ) + + return PlaceAutocompleteEngine( + coreEngine = coreEngine, + requestContextProvider = SearchRequestContextProvider(app), + ) } } } diff --git a/MapboxSearch/place-autocomplete/src/main/java/com/mapbox/search/autocomplete/PlaceAutocompleteImpl.kt b/MapboxSearch/place-autocomplete/src/main/java/com/mapbox/search/autocomplete/PlaceAutocompleteImpl.kt index 7e796f1c3..250f6d156 100644 --- a/MapboxSearch/place-autocomplete/src/main/java/com/mapbox/search/autocomplete/PlaceAutocompleteImpl.kt +++ b/MapboxSearch/place-autocomplete/src/main/java/com/mapbox/search/autocomplete/PlaceAutocompleteImpl.kt @@ -15,8 +15,6 @@ import com.mapbox.search.base.core.CoreSearchEngine import com.mapbox.search.base.core.createCoreReverseGeoOptions import com.mapbox.search.base.core.createCoreSearchOptions import com.mapbox.search.base.core.getUserActivityReporter -import com.mapbox.search.base.engine.TwoStepsToOneStepSearchEngineAdapter -import com.mapbox.search.base.engine.TwoStepsToOneStepSearchEngineAdapter.SearchSelectionResponse import com.mapbox.search.base.failDebug import com.mapbox.search.base.location.LocationEngineAdapter import com.mapbox.search.base.location.WrapperLocationProvider @@ -35,7 +33,7 @@ import java.util.concurrent.ExecutorService import java.util.concurrent.Executors internal class PlaceAutocompleteImpl( - private val searchEngine: TwoStepsToOneStepSearchEngineAdapter, + private val searchEngine: PlaceAutocompleteEngine, private val activityReporter: UserActivityReporterInterface, private val resultFactory: PlaceAutocompleteResultFactory = PlaceAutocompleteResultFactory() ) : PlaceAutocomplete { @@ -80,7 +78,7 @@ internal class PlaceAutocompleteImpl( types = generateCoreTypes(options.types), ) - return searchEngine.reverseGeocoding(coreOptions) + return searchEngine.search(coreOptions) .mapValue { resultFactory.createPlaceAutocompleteSuggestions(it.first) } } @@ -91,6 +89,7 @@ internal class PlaceAutocompleteImpl( is Underlying.Suggestion -> selectRaw(underlying.suggestion).flatMap { resultFactory.createPlaceAutocompleteResultOrError(it) } + is Underlying.Result -> resultFactory.createPlaceAutocompleteResultOrError(underlying.result) } } @@ -98,14 +97,7 @@ internal class PlaceAutocompleteImpl( private suspend fun createSuggestions( rawSuggestions: List ): Expected> { - // In some cases searchEngine.resolveAll() can be significantly faster than suggestions resolving one by one, - if (rawSuggestions.all { it.coordinate == null }) { - return searchEngine.resolveAll(rawSuggestions, allowCategorySuggestions = false).mapValue { - resultFactory.createPlaceAutocompleteSuggestions(it) - } - } - - val suggestions = rawSuggestions + val suggestions: List> = rawSuggestions .mapNotNull { suggestion -> val type = when (val suggestionType = suggestion.type) { is BaseSearchSuggestionType.SearchResultSuggestion -> suggestionType.types @@ -115,21 +107,18 @@ internal class PlaceAutocompleteImpl( } null } + is BaseSearchSuggestionType.Category, is BaseSearchSuggestionType.Brand, is BaseSearchSuggestionType.Query -> null - }?.firstNotNullOfOrNull { PlaceAutocompleteType.createFromBaseType(it) } ?: return@mapNotNull null + }?.firstNotNullOfOrNull { PlaceAutocompleteType.createFromBaseType(it) } + ?: return@mapNotNull null - val coordinate = suggestion.coordinate - if (coordinate != null) { - ExpectedFactory.createValue( - resultFactory.createPlaceAutocompleteSuggestion(coordinate, type, suggestion) + ExpectedFactory.createValue( + resultFactory.createPlaceAutocompleteSuggestion( + type, suggestion ) - } else { - selectRaw(suggestion).mapValue { - resultFactory.createPlaceAutocompleteSuggestion(type, it) - } - } + ) } // If at least one response completed successfully, return it. @@ -148,7 +137,7 @@ internal class PlaceAutocompleteImpl( private suspend fun selectRaw(suggestion: BaseSearchSuggestion): Expected { return searchEngine.select(suggestion).flatMap { when (it) { - is SearchSelectionResponse.Result -> ExpectedFactory.createValue(it.result) + is PlaceAutocompleteEngine.SearchSelectionResponse.Result -> ExpectedFactory.createValue(it.result) else -> { // Shouldn't happen because we don't allow suggestions of type Category and Query ExpectedFactory.createError(Exception("Unsupported suggestion type: $suggestion")) @@ -173,9 +162,10 @@ internal class PlaceAutocompleteImpl( private val ALL_TYPES = PlaceAutocompleteType.ALL_DECLARED_TYPES.map { it.coreType } - private val DEFAULT_EXECUTOR: ExecutorService = Executors.newSingleThreadExecutor { runnable -> - Thread(runnable, "Place Autocomplete executor") - } + private val DEFAULT_EXECUTOR: ExecutorService = + Executors.newSingleThreadExecutor { runnable -> + Thread(runnable, "Place Autocomplete executor") + } fun create( app: Application, @@ -196,8 +186,7 @@ internal class PlaceAutocompleteImpl( ), ) - val engine = TwoStepsToOneStepSearchEngineAdapter( - apiType = apiType, + val engine = PlaceAutocompleteEngine( coreEngine = coreEngine, requestContextProvider = SearchRequestContextProvider(app), historyService = SearchHistoryService.STUB, diff --git a/MapboxSearch/place-autocomplete/src/main/java/com/mapbox/search/autocomplete/PlaceAutocompleteResultFactory.kt b/MapboxSearch/place-autocomplete/src/main/java/com/mapbox/search/autocomplete/PlaceAutocompleteResultFactory.kt index ce07cc787..75980da1c 100644 --- a/MapboxSearch/place-autocomplete/src/main/java/com/mapbox/search/autocomplete/PlaceAutocompleteResultFactory.kt +++ b/MapboxSearch/place-autocomplete/src/main/java/com/mapbox/search/autocomplete/PlaceAutocompleteResultFactory.kt @@ -2,7 +2,6 @@ package com.mapbox.search.autocomplete import com.mapbox.bindgen.Expected import com.mapbox.bindgen.ExpectedFactory -import com.mapbox.geojson.Point import com.mapbox.search.base.core.countryIso1 import com.mapbox.search.base.core.countryIso2 import com.mapbox.search.base.mapToPlatform @@ -14,7 +13,6 @@ import com.mapbox.search.base.utils.extension.nullIfEmpty internal class PlaceAutocompleteResultFactory { fun createPlaceAutocompleteSuggestion( - coordinate: Point, type: PlaceAutocompleteType, suggestion: BaseSearchSuggestion, ): PlaceAutocompleteSuggestion { @@ -22,7 +20,6 @@ internal class PlaceAutocompleteResultFactory { PlaceAutocompleteSuggestion( name = name, formattedAddress = formattedAddress(), - coordinate = coordinate, routablePoints = suggestion.routablePoints?.map { it.mapToPlatform() }, makiIcon = makiIcon, distanceMeters = distanceMeters, @@ -42,7 +39,6 @@ internal class PlaceAutocompleteResultFactory { PlaceAutocompleteSuggestion( name = name, formattedAddress = formattedAddress(), - coordinate = coordinate, routablePoints = routablePoints?.map { it.mapToPlatform() }, makiIcon = makiIcon, distanceMeters = distanceMeters, @@ -55,6 +51,7 @@ internal class PlaceAutocompleteResultFactory { } fun createPlaceAutocompleteSuggestions(results: List): List { +// throw IllegalStateException(results.toString()) return results.mapNotNull { result -> val type = result.types.firstNotNullOfOrNull { PlaceAutocompleteType.createFromBaseType(it) @@ -66,12 +63,14 @@ internal class PlaceAutocompleteResultFactory { fun createPlaceAutocompleteResultOrError(result: BaseSearchResult): Expected { return createPlaceAutocompleteResult(result)?.let { ExpectedFactory.createValue(it) - } ?: ExpectedFactory.createError(Exception("Unable to create PlaceAutocompleteResult from $result")) + } + ?: ExpectedFactory.createError(Exception("Unable to create PlaceAutocompleteResult from $result")) } private fun createPlaceAutocompleteResult(result: BaseSearchResult): PlaceAutocompleteResult? { with(result) { - val type = types.firstNotNullOfOrNull { PlaceAutocompleteType.createFromBaseType(it) } ?: return null + val type = types.firstNotNullOfOrNull { PlaceAutocompleteType.createFromBaseType(it) } + ?: return null return PlaceAutocompleteResult( name = name, diff --git a/MapboxSearch/place-autocomplete/src/main/java/com/mapbox/search/autocomplete/PlaceAutocompleteSuggestion.kt b/MapboxSearch/place-autocomplete/src/main/java/com/mapbox/search/autocomplete/PlaceAutocompleteSuggestion.kt index 91666a16b..cd680454e 100644 --- a/MapboxSearch/place-autocomplete/src/main/java/com/mapbox/search/autocomplete/PlaceAutocompleteSuggestion.kt +++ b/MapboxSearch/place-autocomplete/src/main/java/com/mapbox/search/autocomplete/PlaceAutocompleteSuggestion.kt @@ -1,7 +1,6 @@ package com.mapbox.search.autocomplete import android.os.Parcelable -import com.mapbox.geojson.Point import com.mapbox.search.base.result.BaseSearchResult import com.mapbox.search.base.result.BaseSearchSuggestion import com.mapbox.search.base.utils.extension.safeCompareTo @@ -24,11 +23,6 @@ public class PlaceAutocompleteSuggestion internal constructor( */ public val formattedAddress: String?, - /** - * Place geographic point. - */ - public val coordinate: Point, - /** * List of points near [coordinate], that represents entries to associated building. */ @@ -78,7 +72,6 @@ public class PlaceAutocompleteSuggestion internal constructor( if (name != other.name) return false if (formattedAddress != other.formattedAddress) return false - if (coordinate != other.coordinate) return false if (routablePoints != other.routablePoints) return false if (makiIcon != other.makiIcon) return false if (!distanceMeters.safeCompareTo(other.distanceMeters)) return false @@ -95,7 +88,6 @@ public class PlaceAutocompleteSuggestion internal constructor( override fun hashCode(): Int { var result = name.hashCode() result = 31 * result + (formattedAddress?.hashCode() ?: 0) - result = 31 * result + coordinate.hashCode() result = 31 * result + (routablePoints?.hashCode() ?: 0) result = 31 * result + (makiIcon?.hashCode() ?: 0) result = 31 * result + (distanceMeters?.hashCode() ?: 0) @@ -112,7 +104,6 @@ public class PlaceAutocompleteSuggestion internal constructor( return "PlaceAutocompleteSuggestion(" + "name='$name', " + "formattedAddress=$formattedAddress, " + - "coordinate=$coordinate, " + "routablePoints=$routablePoints, " + "makiIcon=$makiIcon, " + "distanceMeters=$distanceMeters, " + diff --git a/MapboxSearch/place-autocomplete/src/test/java/com/mapbox/search/autocomplete/PlaceAutocompleteImplTest.kt b/MapboxSearch/place-autocomplete/src/test/java/com/mapbox/search/autocomplete/PlaceAutocompleteImplTest.kt index 41e19e4f4..dad769f51 100644 --- a/MapboxSearch/place-autocomplete/src/test/java/com/mapbox/search/autocomplete/PlaceAutocompleteImplTest.kt +++ b/MapboxSearch/place-autocomplete/src/test/java/com/mapbox/search/autocomplete/PlaceAutocompleteImplTest.kt @@ -10,7 +10,6 @@ import com.mapbox.search.base.core.CoreReverseGeoOptions import com.mapbox.search.base.core.CoreSearchOptions import com.mapbox.search.base.core.createCoreReverseGeoOptions import com.mapbox.search.base.core.createCoreSearchOptions -import com.mapbox.search.base.engine.TwoStepsToOneStepSearchEngineAdapter import com.mapbox.search.base.result.BaseSearchResult import com.mapbox.search.base.result.BaseSearchSuggestion import com.mapbox.search.base.utils.extension.mapToCore @@ -33,7 +32,7 @@ import org.junit.jupiter.api.Test internal class PlaceAutocompleteImplTest { - private lateinit var searchEngine: TwoStepsToOneStepSearchEngineAdapter + private lateinit var searchEngine: PlaceAutocompleteEngine private lateinit var activityReporter: UserActivityReporterInterface private lateinit var resultFactory: PlaceAutocompleteResultFactory private lateinit var placeAutocomplete: PlaceAutocomplete @@ -50,15 +49,11 @@ internal class PlaceAutocompleteImplTest { resultFactory = resultFactory ) - coEvery { searchEngine.searchResolveImmediately(any(), any()) } answers { - ExpectedFactory.createValue(emptyList()) - } - coEvery { searchEngine.search(any(), any()) } answers { ExpectedFactory.createValue(emptyList() to mockk()) } - coEvery { searchEngine.reverseGeocoding(any()) } answers { + coEvery { searchEngine.search(any()) } answers { ExpectedFactory.createValue(emptyList() to mockk()) } } @@ -69,10 +64,6 @@ internal class PlaceAutocompleteImplTest { ExpectedFactory.createValue(TEST_BASE_SUGGESTIONS to mockk()) } - coEvery { searchEngine.resolveAll(any(), any()) } answers { - ExpectedFactory.createValue(TEST_BASE_RESULTS) - } - val response = runBlocking { placeAutocomplete.suggestions(TEST_QUERY) } @@ -80,8 +71,6 @@ internal class PlaceAutocompleteImplTest { assertEquals(TEST_AUTOCOMPLETE_SUGGESTIONS, response.value) coVerify(exactly = 1) { searchEngine.search(eq(TEST_QUERY), any()) } - coVerify(exactly = 1) { searchEngine.resolveAll(eq(TEST_BASE_SUGGESTIONS), eq(false)) } - verify(exactly = 1) { resultFactory.createPlaceAutocompleteSuggestions(listOf(testBaseResult)) } verify(exactly = 1) { activityReporter.reportActivity(eq("place-autocomplete-forward-geocoding")) } } @@ -169,7 +158,7 @@ internal class PlaceAutocompleteImplTest { @Test fun `check correct returned data for reverse geocoding request`() { - coEvery { searchEngine.reverseGeocoding(any()) } answers { + coEvery { searchEngine.search(any()) } answers { ExpectedFactory.createValue(TEST_BASE_RESULTS to mockk()) } @@ -179,7 +168,7 @@ internal class PlaceAutocompleteImplTest { assertEquals(TEST_AUTOCOMPLETE_SUGGESTIONS, response.value) - coVerify(exactly = 1) { searchEngine.reverseGeocoding(any()) } + coVerify(exactly = 1) { searchEngine.search(any()) } verify(exactly = 1) { resultFactory.createPlaceAutocompleteSuggestions(listOf(testBaseResult)) } verify(exactly = 1) { activityReporter.reportActivity(eq("place-autocomplete-reverse-geocoding")) } } @@ -187,7 +176,7 @@ internal class PlaceAutocompleteImplTest { @Test fun `check correct returned error for forward reverse request`() { val error = Exception() - coEvery { searchEngine.reverseGeocoding(any()) } answers { + coEvery { searchEngine.search(any()) } answers { ExpectedFactory.createError(error) } @@ -197,7 +186,7 @@ internal class PlaceAutocompleteImplTest { assertSame(error, response.error) - coVerify(exactly = 1) { searchEngine.reverseGeocoding(any()) } + coVerify(exactly = 1) { searchEngine.search(any()) } verify(exactly = 0) { resultFactory.createPlaceAutocompleteSuggestions(any()) } verify(exactly = 1) { activityReporter.reportActivity(eq("place-autocomplete-reverse-geocoding")) } } @@ -224,14 +213,14 @@ internal class PlaceAutocompleteImplTest { ) runBlocking { placeAutocomplete.suggestions(TEST_POINT, options) } - coVerify { searchEngine.reverseGeocoding(coreOptions) } + coVerify { searchEngine.search(coreOptions) } verify(exactly = 1) { activityReporter.reportActivity(eq("place-autocomplete-reverse-geocoding")) } } @Test fun `check request types for reverse geocoding request with administrativeUnits = null`() { val slotOptions = slot() - coEvery { searchEngine.reverseGeocoding(capture(slotOptions)) } answers { + coEvery { searchEngine.search(capture(slotOptions)) } answers { ExpectedFactory.createValue(emptyList() to mockk()) } @@ -247,7 +236,7 @@ internal class PlaceAutocompleteImplTest { @Test fun `check request types for reverse geocoding request with empty administrativeUnits`() { val slotOptions = slot() - coEvery { searchEngine.reverseGeocoding(capture(slotOptions)) } answers { + coEvery { searchEngine.search(capture(slotOptions)) } answers { ExpectedFactory.createValue(emptyList() to mockk()) } @@ -268,8 +257,7 @@ internal class PlaceAutocompleteImplTest { assertEquals(TEST_AUTOCOMPLETE_RESULT, response.value) - coVerify(exactly = 0) { searchEngine.select(any()) } - coVerify(exactly = 0) { searchEngine.resolveAll(any(), any()) } + coVerify(exactly = 0) { searchEngine.select(any()) } verify(exactly = 1) { resultFactory.createPlaceAutocompleteResultOrError(eq(testBaseResult)) } verify(exactly = 1) { activityReporter.reportActivity(eq("place-autocomplete-suggestion-select")) } } diff --git a/MapboxSearch/place-autocomplete/src/test/java/com/mapbox/search/autocomplete/PlaceAutocompleteResultFactoryTest.kt b/MapboxSearch/place-autocomplete/src/test/java/com/mapbox/search/autocomplete/PlaceAutocompleteResultFactoryTest.kt index a0ca8d1be..524e14213 100644 --- a/MapboxSearch/place-autocomplete/src/test/java/com/mapbox/search/autocomplete/PlaceAutocompleteResultFactoryTest.kt +++ b/MapboxSearch/place-autocomplete/src/test/java/com/mapbox/search/autocomplete/PlaceAutocompleteResultFactoryTest.kt @@ -20,9 +20,8 @@ internal class PlaceAutocompleteResultFactoryTest { @Test fun `test create PlaceAutocompleteSuggestion from base suggestion`() { val baseSuggestion = createTestBaseSearchSuggestion(testBaseRawSearchSuggestionWithCoordinates) - val coordinate = testBaseRawSearchSuggestionWithCoordinates.center!! val type = PlaceAutocompleteType.AdministrativeUnit.Address - val suggestion = factory.createPlaceAutocompleteSuggestion(coordinate, type, baseSuggestion) + val suggestion = factory.createPlaceAutocompleteSuggestion(type, baseSuggestion) compare(testBaseRawSearchSuggestionWithCoordinates, suggestion) } @@ -52,7 +51,6 @@ internal class PlaceAutocompleteResultFactoryTest { private fun compare(baseResult: BaseRawSearchResult, suggestion: PlaceAutocompleteSuggestion) { assertEquals(baseResult.names.first(), suggestion.name) assertEquals(baseResult.fullAddress, suggestion.formattedAddress) - assertEquals(baseResult.center, suggestion.coordinate) assertEquals(baseResult.routablePoints, suggestion.routablePoints?.map { it.mapToCore() }) assertEquals(baseResult.icon, suggestion.makiIcon) assertEquals(baseResult.distanceMeters, suggestion.distanceMeters) diff --git a/MapboxSearch/place-autocomplete/src/test/java/com/mapbox/search/autocomplete/test/utils/TestDataFactory.kt b/MapboxSearch/place-autocomplete/src/test/java/com/mapbox/search/autocomplete/test/utils/TestDataFactory.kt index db561f56e..82c45f835 100644 --- a/MapboxSearch/place-autocomplete/src/test/java/com/mapbox/search/autocomplete/test/utils/TestDataFactory.kt +++ b/MapboxSearch/place-autocomplete/src/test/java/com/mapbox/search/autocomplete/test/utils/TestDataFactory.kt @@ -53,14 +53,13 @@ internal val filledCoreSearchAddress: CoreSearchAddress = createCoreSearchAddres internal val testBaseRawSearchResult: BaseRawSearchResult = createTestBaseRawSearchResult( id = "test-id", - names = listOf("test-name"), + names = listOf("test-suggestion"), descriptionAddress = "test-description", addresses = listOf(filledCoreSearchAddress), fullAddress = "test-full-address", center = Point.fromLngLat(10.0, 15.0), - routablePoints = listOf(RoutablePoint(Point.fromLngLat(11.0, 16.0), "test")), distanceMeters = 123.0, - icon = "test", + icon = "test-suggestion", etaMinutes = 5.0, types = listOf(BaseRawResultType.ADDRESS), action = null, diff --git a/MapboxSearch/sample/src/main/java/com/mapbox/search/sample/AddressAutofillUiActivity.kt b/MapboxSearch/sample/src/main/java/com/mapbox/search/sample/AddressAutofillUiActivity.kt index 9b5ad610d..905e0d0a0 100644 --- a/MapboxSearch/sample/src/main/java/com/mapbox/search/sample/AddressAutofillUiActivity.kt +++ b/MapboxSearch/sample/src/main/java/com/mapbox/search/sample/AddressAutofillUiActivity.kt @@ -118,10 +118,7 @@ class AddressAutofillUiActivity : AppCompatActivity() { addressAutofillUiAdapter.addSearchListener(object : AddressAutofillUiAdapter.SearchListener { override fun onSuggestionSelected(suggestion: AddressAutofillSuggestion) { - selectSuggestion( - suggestion, - fromReverseGeocoding = false, - ) + selectSuggestion(suggestion) } override fun onSuggestionsShown(suggestions: List) { @@ -178,15 +175,12 @@ class AddressAutofillUiActivity : AppCompatActivity() { private fun findAddress(point: Point) { lifecycleScope.launchWhenStarted { - val response = addressAutofill.suggestions(point, AddressAutofillOptions()) - response.onValue { suggestions -> - if (suggestions.isEmpty()) { + val response = addressAutofill.reverseGeocoding(point, AddressAutofillOptions()) + response.onValue { result -> + if (result.isEmpty()) { showToast(R.string.address_autofill_error_pin_correction) } else { - selectSuggestion( - suggestions.first(), - fromReverseGeocoding = true - ) + showAddressAutofillResult(result.first(), fromReverseGeocoding = true) } }.onError { showToast(R.string.address_autofill_error_pin_correction) @@ -194,11 +188,11 @@ class AddressAutofillUiActivity : AppCompatActivity() { } } - private fun selectSuggestion(suggestion: AddressAutofillSuggestion, fromReverseGeocoding: Boolean) { + private fun selectSuggestion(suggestion: AddressAutofillSuggestion) { lifecycleScope.launchWhenStarted { val response = addressAutofill.select(suggestion) response.onValue { result -> - showAddressAutofillResult(result, fromReverseGeocoding) + showAddressAutofillResult(result, fromReverseGeocoding = false) }.onError { showToast(R.string.address_autofill_error_select) } @@ -219,7 +213,7 @@ class AddressAutofillUiActivity : AppCompatActivity() { if (!fromReverseGeocoding) { mapView.mapboxMap.setCamera( CameraOptions.Builder() - .center(result.suggestion.coordinate) + .center(result.coordinate) .zoom(16.0) .build() ) diff --git a/MapboxSearch/sample/src/main/java/com/mapbox/search/sample/PlaceAutocompleteUiActivity.kt b/MapboxSearch/sample/src/main/java/com/mapbox/search/sample/PlaceAutocompleteUiActivity.kt index c4aad61b4..7bfab122a 100644 --- a/MapboxSearch/sample/src/main/java/com/mapbox/search/sample/PlaceAutocompleteUiActivity.kt +++ b/MapboxSearch/sample/src/main/java/com/mapbox/search/sample/PlaceAutocompleteUiActivity.kt @@ -250,7 +250,7 @@ class PlaceAutocompleteUiActivity : AppCompatActivity() { lifecycleScope.launchWhenStarted { placeAutocomplete.select(suggestion).onValue { result -> - mapMarkersManager.showMarker(suggestion.coordinate) + mapMarkersManager.showMarker(result.coordinate) searchPlaceView.open(SearchPlace.createFromPlaceAutocompleteResult(result)) queryEditText.hideKeyboard() searchResultsView.isVisible = false diff --git a/MapboxSearch/sdk/src/test/java/com/mapbox/search/analytics/events/AnalyticsServiceImplTest.kt b/MapboxSearch/sdk/src/test/java/com/mapbox/search/analytics/events/AnalyticsServiceImplTest.kt index b0a2ce708..f4c10c7d2 100644 --- a/MapboxSearch/sdk/src/test/java/com/mapbox/search/analytics/events/AnalyticsServiceImplTest.kt +++ b/MapboxSearch/sdk/src/test/java/com/mapbox/search/analytics/events/AnalyticsServiceImplTest.kt @@ -23,11 +23,8 @@ import com.mapbox.search.common.tests.TestExecutor import com.mapbox.search.common.tests.catchThrowable import com.mapbox.search.common.tests.createTestCoreSearchResponseSuccess import com.mapbox.search.result.SearchSuggestion -// import com.mapbox.search.tests_support.BlockingCompletionCallback import com.mapbox.search.tests_support.createTestBaseSearchSuggestion -// import com.mapbox.search.tests_support.createTestFavoriteRecord import com.mapbox.search.tests_support.createTestRequestOptions -// import com.mapbox.search.tests_support.createTestSearchResult import com.mapbox.test.dsl.TestCase import io.mockk.Called import io.mockk.every diff --git a/MapboxSearch/ui/api/api-metalava.txt b/MapboxSearch/ui/api/api-metalava.txt index ba4ee36d1..87bb84287 100644 --- a/MapboxSearch/ui/api/api-metalava.txt +++ b/MapboxSearch/ui/api/api-metalava.txt @@ -2,7 +2,7 @@ package com.mapbox.search.ui.adapter.autocomplete { public final class PlaceAutocompleteUiAdapter { - ctor public PlaceAutocompleteUiAdapter(com.mapbox.search.ui.view.SearchResultsView view, com.mapbox.search.autocomplete.PlaceAutocomplete placeAutocomplete, com.mapbox.common.location.LocationProvider? locationEngine = ()); + ctor public PlaceAutocompleteUiAdapter(com.mapbox.search.ui.view.SearchResultsView view, com.mapbox.search.autocomplete.PlaceAutocomplete placeAutocomplete); method public void addSearchListener(com.mapbox.search.ui.adapter.autocomplete.PlaceAutocompleteUiAdapter.SearchListener listener); method public void removeSearchListener(com.mapbox.search.ui.adapter.autocomplete.PlaceAutocompleteUiAdapter.SearchListener listener); method public suspend Object? search(String query, com.mapbox.geojson.BoundingBox? region = null, com.mapbox.geojson.Point? proximity = null, com.mapbox.search.autocomplete.PlaceAutocompleteOptions options = com.mapbox.search.autocomplete.PlaceAutocompleteOptions(), kotlin.coroutines.Continuation p = com.mapbox.search.autocomplete.PlaceAutocompleteOptions()); @@ -23,7 +23,7 @@ package com.mapbox.search.ui.adapter.autocomplete { package com.mapbox.search.ui.adapter.autofill { public final class AddressAutofillUiAdapter { - ctor public AddressAutofillUiAdapter(com.mapbox.search.ui.view.SearchResultsView view, com.mapbox.search.autofill.AddressAutofill addressAutofill, com.mapbox.common.location.LocationProvider? locationEngine = ()); + ctor public AddressAutofillUiAdapter(com.mapbox.search.ui.view.SearchResultsView view, com.mapbox.search.autofill.AddressAutofill addressAutofill); method public void addSearchListener(com.mapbox.search.ui.adapter.autofill.AddressAutofillUiAdapter.SearchListener listener); method public void removeSearchListener(com.mapbox.search.ui.adapter.autofill.AddressAutofillUiAdapter.SearchListener listener); method public suspend Object? search(com.mapbox.search.autofill.Query query, com.mapbox.search.autofill.AddressAutofillOptions options = com.mapbox.search.autofill.AddressAutofillOptions(), kotlin.coroutines.Continuation p = com.mapbox.search.autofill.AddressAutofillOptions()); diff --git a/MapboxSearch/ui/api/ui.api b/MapboxSearch/ui/api/ui.api index d8a4efdf4..a6a91919d 100644 --- a/MapboxSearch/ui/api/ui.api +++ b/MapboxSearch/ui/api/ui.api @@ -6,8 +6,7 @@ public final class com/mapbox/search/ui/BuildConfig { } public final class com/mapbox/search/ui/adapter/autocomplete/PlaceAutocompleteUiAdapter { - public fun (Lcom/mapbox/search/ui/view/SearchResultsView;Lcom/mapbox/search/autocomplete/PlaceAutocomplete;Lcom/mapbox/common/location/LocationProvider;)V - public synthetic fun (Lcom/mapbox/search/ui/view/SearchResultsView;Lcom/mapbox/search/autocomplete/PlaceAutocomplete;Lcom/mapbox/common/location/LocationProvider;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Lcom/mapbox/search/ui/view/SearchResultsView;Lcom/mapbox/search/autocomplete/PlaceAutocomplete;)V public final fun addSearchListener (Lcom/mapbox/search/ui/adapter/autocomplete/PlaceAutocompleteUiAdapter$SearchListener;)V public final fun removeSearchListener (Lcom/mapbox/search/ui/adapter/autocomplete/PlaceAutocompleteUiAdapter$SearchListener;)V public final fun search (Ljava/lang/String;Lcom/mapbox/geojson/BoundingBox;Lcom/mapbox/geojson/Point;Lcom/mapbox/search/autocomplete/PlaceAutocompleteOptions;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; @@ -25,8 +24,7 @@ public abstract interface class com/mapbox/search/ui/adapter/autocomplete/PlaceA } public final class com/mapbox/search/ui/adapter/autofill/AddressAutofillUiAdapter { - public fun (Lcom/mapbox/search/ui/view/SearchResultsView;Lcom/mapbox/search/autofill/AddressAutofill;Lcom/mapbox/common/location/LocationProvider;)V - public synthetic fun (Lcom/mapbox/search/ui/view/SearchResultsView;Lcom/mapbox/search/autofill/AddressAutofill;Lcom/mapbox/common/location/LocationProvider;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Lcom/mapbox/search/ui/view/SearchResultsView;Lcom/mapbox/search/autofill/AddressAutofill;)V public final fun addSearchListener (Lcom/mapbox/search/ui/adapter/autofill/AddressAutofillUiAdapter$SearchListener;)V public final fun removeSearchListener (Lcom/mapbox/search/ui/adapter/autofill/AddressAutofillUiAdapter$SearchListener;)V public final fun search (Lcom/mapbox/search/autofill/Query;Lcom/mapbox/search/autofill/AddressAutofillOptions;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; diff --git a/MapboxSearch/ui/src/main/java/com/mapbox/search/ui/adapter/autocomplete/PlaceAutocompleteItemsCreator.kt b/MapboxSearch/ui/src/main/java/com/mapbox/search/ui/adapter/autocomplete/PlaceAutocompleteItemsCreator.kt index 332119c3d..cfbe5e57b 100644 --- a/MapboxSearch/ui/src/main/java/com/mapbox/search/ui/adapter/autocomplete/PlaceAutocompleteItemsCreator.kt +++ b/MapboxSearch/ui/src/main/java/com/mapbox/search/ui/adapter/autocomplete/PlaceAutocompleteItemsCreator.kt @@ -2,29 +2,23 @@ package com.mapbox.search.ui.adapter.autocomplete import android.content.Context import androidx.annotation.ColorInt -import com.mapbox.common.location.LocationProvider import com.mapbox.search.autocomplete.PlaceAutocompleteSuggestion -import com.mapbox.search.base.utils.extension.distanceTo -import com.mapbox.search.base.utils.extension.toPoint import com.mapbox.search.common.HighlightsCalculator import com.mapbox.search.ui.R import com.mapbox.search.ui.adapter.BaseItemsCreator import com.mapbox.search.ui.adapter.engines.SearchEntityPresentation import com.mapbox.search.ui.utils.extenstion.resolveAttrOrThrow import com.mapbox.search.ui.view.SearchResultAdapterItem -import kotlin.coroutines.resume -import kotlin.coroutines.suspendCoroutine internal class PlaceAutocompleteItemsCreator( private val context: Context, - private val locationProvider: LocationProvider?, private val searchEntityPresentation: SearchEntityPresentation = SearchEntityPresentation(context), @ColorInt private val selectionSpanColor: Int = context.resolveAttrOrThrow(R.attr.mapboxSearchSdkPrimaryAccentColor), highlightsCalculator: HighlightsCalculator = HighlightsCalculator.INSTANCE ) : BaseItemsCreator(context, selectionSpanColor, highlightsCalculator) { - suspend fun createForSuggestions( + fun createForSuggestions( suggestions: List, query: String ): List { @@ -33,21 +27,10 @@ internal class PlaceAutocompleteItemsCreator( } return suggestions.map { suggestion -> - val distance = if (suggestion.distanceMeters != null) { - suggestion.distanceMeters - } else { - suspendCoroutine { continuation -> - locationProvider?.getLastLocation { - val result = it?.toPoint()?.distanceTo(suggestion.coordinate) - continuation.resume(result) - } - } - } - SearchResultAdapterItem.Result( title = highlight(suggestion.name, query), subtitle = suggestion.formattedAddress, - distanceMeters = distance, + distanceMeters = suggestion.distanceMeters, drawable = searchEntityPresentation.getDrawable(suggestion), isPopulateQueryVisible = true, payload = suggestion, diff --git a/MapboxSearch/ui/src/main/java/com/mapbox/search/ui/adapter/autocomplete/PlaceAutocompleteUiAdapter.kt b/MapboxSearch/ui/src/main/java/com/mapbox/search/ui/adapter/autocomplete/PlaceAutocompleteUiAdapter.kt index feeb474ab..219a67cf2 100644 --- a/MapboxSearch/ui/src/main/java/com/mapbox/search/ui/adapter/autocomplete/PlaceAutocompleteUiAdapter.kt +++ b/MapboxSearch/ui/src/main/java/com/mapbox/search/ui/adapter/autocomplete/PlaceAutocompleteUiAdapter.kt @@ -1,10 +1,7 @@ package com.mapbox.search.ui.adapter.autocomplete -import android.Manifest import androidx.lifecycle.coroutineScope import androidx.lifecycle.findViewTreeLifecycleOwner -import com.mapbox.common.location.LocationProvider -import com.mapbox.common.location.LocationServiceFactory import com.mapbox.geojson.BoundingBox import com.mapbox.geojson.Point import com.mapbox.search.autocomplete.PlaceAutocomplete @@ -12,7 +9,6 @@ import com.mapbox.search.autocomplete.PlaceAutocompleteOptions import com.mapbox.search.autocomplete.PlaceAutocompleteSuggestion import com.mapbox.search.base.core.getUserActivityReporter import com.mapbox.search.base.failDebug -import com.mapbox.search.base.location.defaultLocationProvider import com.mapbox.search.internal.bindgen.UserActivityReporter import com.mapbox.search.ui.view.SearchResultAdapterItem import com.mapbox.search.ui.view.SearchResultsView @@ -39,17 +35,9 @@ public class PlaceAutocompleteUiAdapter( * Place autocomplete engine. */ private val placeAutocomplete: PlaceAutocomplete, - - /** - * The mechanism responsible for providing location approximations to the SDK. - * By default [LocationProvider] is provided by [LocationServiceFactory]. - * Note that this class requires [Manifest.permission.ACCESS_COARSE_LOCATION] or - * [Manifest.permission.ACCESS_FINE_LOCATION] to work properly. - */ - locationEngine: LocationProvider? = defaultLocationProvider(), ) { - private val itemsCreator = PlaceAutocompleteItemsCreator(view.context, locationEngine) + private val itemsCreator = PlaceAutocompleteItemsCreator(view.context) private val searchListeners = CopyOnWriteArrayList() diff --git a/MapboxSearch/ui/src/main/java/com/mapbox/search/ui/adapter/autofill/AddressAutofillUiAdapter.kt b/MapboxSearch/ui/src/main/java/com/mapbox/search/ui/adapter/autofill/AddressAutofillUiAdapter.kt index 5f1640bb8..82f7660ef 100644 --- a/MapboxSearch/ui/src/main/java/com/mapbox/search/ui/adapter/autofill/AddressAutofillUiAdapter.kt +++ b/MapboxSearch/ui/src/main/java/com/mapbox/search/ui/adapter/autofill/AddressAutofillUiAdapter.kt @@ -1,17 +1,13 @@ package com.mapbox.search.ui.adapter.autofill -import android.Manifest import androidx.lifecycle.coroutineScope import androidx.lifecycle.findViewTreeLifecycleOwner -import com.mapbox.common.location.LocationProvider -import com.mapbox.common.location.LocationServiceFactory import com.mapbox.search.autofill.AddressAutofill import com.mapbox.search.autofill.AddressAutofillOptions import com.mapbox.search.autofill.AddressAutofillSuggestion import com.mapbox.search.autofill.Query import com.mapbox.search.base.core.getUserActivityReporter import com.mapbox.search.base.failDebug -import com.mapbox.search.base.location.defaultLocationProvider import com.mapbox.search.internal.bindgen.UserActivityReporter import com.mapbox.search.ui.view.SearchResultAdapterItem import com.mapbox.search.ui.view.SearchResultsView @@ -38,17 +34,9 @@ public class AddressAutofillUiAdapter( * Address autofill engine. */ private val addressAutofill: AddressAutofill, - - /** - * The mechanism responsible for providing location approximations to the SDK. - * By default [LocationProvider] is provided by [LocationServiceFactory]. - * Note that this class requires [Manifest.permission.ACCESS_COARSE_LOCATION] or - * [Manifest.permission.ACCESS_FINE_LOCATION] to work properly. - */ - locationEngine: LocationProvider? = defaultLocationProvider(), ) { - private val itemsCreator = AutofillItemsCreator(view.context, locationEngine) + private val itemsCreator = AutofillItemsCreator(view.context) private val searchListeners = CopyOnWriteArrayList() @Volatile diff --git a/MapboxSearch/ui/src/main/java/com/mapbox/search/ui/adapter/autofill/AutofillItemsCreator.kt b/MapboxSearch/ui/src/main/java/com/mapbox/search/ui/adapter/autofill/AutofillItemsCreator.kt index 853c321ff..6ed288b18 100644 --- a/MapboxSearch/ui/src/main/java/com/mapbox/search/ui/adapter/autofill/AutofillItemsCreator.kt +++ b/MapboxSearch/ui/src/main/java/com/mapbox/search/ui/adapter/autofill/AutofillItemsCreator.kt @@ -2,29 +2,21 @@ package com.mapbox.search.ui.adapter.autofill import android.content.Context import androidx.annotation.ColorInt -import com.mapbox.common.location.Location -import com.mapbox.common.location.LocationProvider import com.mapbox.search.autofill.AddressAutofillSuggestion -import com.mapbox.search.base.utils.extension.distanceTo -import com.mapbox.search.base.utils.extension.toPoint import com.mapbox.search.common.HighlightsCalculator import com.mapbox.search.ui.R import com.mapbox.search.ui.adapter.BaseItemsCreator import com.mapbox.search.ui.utils.extenstion.resolveAttrOrThrow import com.mapbox.search.ui.view.SearchResultAdapterItem -import kotlin.coroutines.Continuation -import kotlin.coroutines.resume -import kotlin.coroutines.suspendCoroutine internal class AutofillItemsCreator( private val context: Context, - private val locationProvider: LocationProvider?, @ColorInt private val selectionSpanColor: Int = context.resolveAttrOrThrow(R.attr.mapboxSearchSdkPrimaryAccentColor), highlightsCalculator: HighlightsCalculator = HighlightsCalculator.INSTANCE ) : BaseItemsCreator(context, selectionSpanColor, highlightsCalculator) { - suspend fun createForSuggestions( + fun createForSuggestions( suggestions: List, query: String ): List { @@ -33,15 +25,10 @@ internal class AutofillItemsCreator( } return suggestions.map { suggestion -> - val locationRequest = suspendCoroutine { continuation: Continuation -> - locationProvider?.getLastLocation { continuation.resume(it) } - }?.toPoint() - val distance: Double? = locationRequest?.distanceTo(suggestion.coordinate) - SearchResultAdapterItem.Result( title = highlight(suggestion.formattedAddress, query), subtitle = null, - distanceMeters = distance, + distanceMeters = null, drawable = R.drawable.mapbox_search_sdk_ic_search_result_address, payload = suggestion )