mapbox / mapbox-search-android

Android SDK for Mapbox Search including preconfigured UI
https://docs.mapbox.com/android/search/guides/
Other
33 stars 7 forks source link

SearchRequestException caused by valid city and postal code query. #6

Closed LaurieWuAndroid closed 2 years ago

LaurieWuAndroid commented 2 years ago

Environment

Code examples

Our querySuggestion() method:

return withContext(dispatchers.main()) {
            suspendCancellableCoroutine { cont ->
                val callback = object : SearchSelectionCallback {
                    override fun onCategoryResult(
                        suggestion: SearchSuggestion,
                        results: List<SearchResult>,
                        responseInfo: ResponseInfo,
                    ) = Unit

                    override fun onResult(
                        suggestion: SearchSuggestion,
                        result: SearchResult,
                        responseInfo: ResponseInfo,
                    ) = Unit

                    override fun onError(e: Exception) = cont.resume(Err(e))

                    override fun onSuggestions(suggestions: List<SearchSuggestion>, responseInfo: ResponseInfo) {
                        cont.resume(Ok(suggestions))
                    }
                }
                searchTask?.cancel()
                val options = proximity?.let { searchOptions.copy(proximity = it) } ?: searchOptions
                searchTask = mapboxPlaceSearch.search(query.toString(), options, callback)
            }
        }

Screen recording of the phone: searchRequestException1.mp4.zip

Observed behavior and steps

The app doesn't crash so that's good, but even with a valid city and postal code(I verified the city and postal code with https://docs.mapbox.com/playground/geocoding/) causes the exception below at SearchSelectionCallback > onError().

SearchRequestException(message='Unknown', code=422, cause=null)
        at com.mapbox.search.core.http.HttpClientImpl$makeRequest$1.onResponse(HttpClientImpl.kt:51)
        at okhttp3.internal.connection.RealCall$AsyncCall.run(RealCall.kt:519)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
        at java.lang.Thread.run(Thread.java:923)

Expected behavior

As user types letters or numbers into the search field, it should populate and show a list of suggestions.

LaurieWuAndroid commented 2 years ago

@Guardiola31337 thanks for your help so far! πŸ™‡πŸΌβ€β™€οΈ

DzmitryFomchyn commented 2 years ago

Hey @LaurieWuAndroid, Which SearchOptions are passed to the SearchEngine?

LaurieWuAndroid commented 2 years ago

Hi Dzmitry! πŸ‘‹

here's an example of the SearchOption that was passed to SearchEngine

SearchOptions(proximity=Point{type=Point, bbox=null, coordinates=[-122.3414347, 47.6546021]}, boundingBox=null,
 countries=[Country(code='us')], fuzzyMatch=true, languages=[Language(code='en')], limit=10, 
 types=[COUNTRY, REGION, POSTCODE, DISTRICT, PLACE, LOCALITY, NEIGHBORHOOD, STREET, ADDRESS, CATEGORY], 
 requestDebounce=200, origin=null, navigationOptions=null, routeOptions=null, unsafeParameters=null, 
 ignoreIndexableRecords=false, indexableRecordsDistanceThresholdMeters=null)
DzmitryFomchyn commented 2 years ago

Hey @LaurieWuAndroid, Thanks for the information.

I believe that your Search SDK configuration targets our geocoding (v5) endpoint since it's the only one available publicly for now. For this endpoint types STREET and CATEGORY are not suitable. It's really not obvious for our customers and sorry for that. Right now we are working on our documentation improvements. Also, in the next Search SDK release API references pop-ups will be available in the Android Studio. Besides that, SearchRequestException will have a detailed error message, this also will be available in the next Search SDK release.

Note that you can leave empty SearchOptions.types. In this case, all that available types will be used.

There're also a few comments for the code snippet you've shared

return withContext(dispatchers.main()) { ... }

Search SDK works on the main thread by default, so you don't have to switch to the main thread explicitly.

val options = proximity?.let { searchOptions.copy(proximity = it) } ?: searchOptions

You can leave empty (null) SearchOptions.proximity. In this case location will be accessed from the LocationEngine passed to the MapboxSearchSdk.initialie() function.

LaurieWuAndroid commented 2 years ago

Hey @DzmitryFomchyn,

Thanks for the context on the two invalid endpoint types! If I'm understanding correctly, "New York" and "11101" fall under Place and Postcode which are neither Street nor Category. Thus, wondering if we know why we encounter an exception for Place and Postcode types?

Is there a time estimate of when the next search SDK release will be?

Thank you for the tips on the lines of code! We decided to explicitly state the main thread so is helpful for devs to debug as network calls are usually expected to run on the background thread. None the less, appreciate the tips/context on the setup πŸ˜ƒ !

DzmitryFomchyn commented 2 years ago

@LaurieWuAndroid,

Thanks for the context on the two invalid endpoint types! If I'm understanding correctly, "New York" and "11101" fall under Place and Postcode which are neither Street nor Category. Thus, wondering if we know why we encounter an exception for Place and Postcode types?

The important part here is which types you pass to the SearchOptions, make sure you don't include STREET and CATEGORY to the SearchOptions.types list.

This code should work for you, please confirm that.


val searchEngine = MapboxSearchSdk.getSearchEngine()
val task = searchEngine.search(
    "New York",
    SearchOptions(types = listOf(QueryType.PLACE, QueryType.POSTCODE)),
    callback
)

Is there a time estimate of when the next search SDK release will be?

We're planning to cut a release next week.

LaurieWuAndroid commented 2 years ago

@DzmitryFomchyn , thank you for clarifying and answering my additional questions πŸ˜ƒ, and apologies for misunderstanding your previous comment. Editing the options parameter to exclude(explicitly include Place and Postcode in this case) Street and Category did work! Thanks so much for your help! πŸŽ‰πŸ₯³

Am looking forward to the next release of the search sdk πŸ˜ƒ, take care meanwhile!

DzmitryFomchyn commented 2 years ago

Thanks, @LaurieWuAndroid, let me know if you have more questions!

LaurieWuAndroid commented 2 years ago

@DzmitryFomchyn I do have a follow up question, it seems like the SearchSuggestion's descriptionText is coming back null. This isn't a huge problem for us right now, but want to flag it for the search team πŸ˜ƒ .

DzmitryFomchyn commented 2 years ago

@LaurieWuAndroid, descriptionText is a nullable property and can be null for some results.

Could you please give more information? SearchOptions you are using and received SearchSuggestions.

You can also try to remove explicit types in order to get more relevant results.

val task = searchEngine.search(
    "New York",
    SearchOptions(),
    callback
)
LaurieWuAndroid commented 2 years ago

@DzmitryFomchyn, It seems like descriptionText is returning null to about 15 random searches I been doing. Here's my SearchOptions and an item of SearchSuggestion setup/result (FYI, I added new lines to the results so we don't have to deal with the infinite horizontal scrolling of one line):

SearchOptions(
        types = QueryType.values().toList() - QueryType.POI - QueryType.CATEGORY - QueryType.STREET,
        countries = listOf(Country.UNITED_STATES),
        languages = listOf(Language.ENGLISH),
        fuzzyMatch = true,
        requestDebounce = 200,
        limit = 10,
    )
SearchSuggestion(id='address.5122702009100974', name='Pizza Lane', address='SearchAddress(houseNumber=null, street=Pizza Lane, neighborhood=null, locality=null, postcode=49935, 
place=Iron River, district=Iron County, region=Michigan, country=United States)', descriptionText='null', 
distanceMeters='2543658.7743202057', makiIcon='null', type='IndexableRecordItem(dataProviderName='com.mapbox.search.localProvider.history', 
type=ADDRESS)', etaMinutes='null', isBatchResolveSupported='true', requestOptions='RequestOptions(query='l', options=SearchOptions(proximity=Point{type=Point, bbox=null, coordinates=[-122.34140073050078, 47.65456213180141]}, boundingBox=null, countries=[Country(code='us')], fuzzyMatch=true,
 languages=[Language(code='en')], limit=10, types=[COUNTRY, REGION, POSTCODE, DISTRICT, PLACE, LOCALITY, NEIGHBORHOOD, ADDRESS], 
requestDebounce=200, origin=Point{type=Point, bbox=null, coordinates=[-122.34140073050078, 47.65456213180141]}, navigationOptions=null, routeOptions=null, unsafeParameters=null, ignoreIndexableRecords=false, indexableRecordsDistanceThresholdMeters=null), proximityRewritten=false,
 originRewritten=true, endpoint='suggest', sessionID='be944b0d-6604-4e5f-a15e-b2013b3a00ee', requestContext=SearchRequestContext(apiType=GEOCODING, keyboardLocale=null, screenOrientation=PORTRAIT, responseUuid=))', 
record='HistoryRecord(id='address.5122702009100974', name='Pizza Lane', descriptionText=null, address=SearchAddress(houseNumber=null, street=Pizza Lane, 
neighborhood=null, locality=null, postcode=49935, place=Iron River, district=Iron County, region=Michigan, country=United States), routablePoints=null, categories=[], makiIcon=null, coordinate=Point{type=Point, bbox=null, coordinates=[-88.71353149414062, 46.06119155883789]}, type=ADDRESS, metadata=null, timestamp=1641954656445)')

I also tried the task with explicit types mentioned in the previous comment. Still getting null from descriptionText:

SearchSuggestion(id='region.17349986251855570', name='New York', address='SearchAddress(houseNumber=null, street=null, neighborhood=null, locality=null, postcode=null, place=null, 
district=null, region=null, country=United States)', descriptionText='null', distanceMeters='3656898.5921321833', makiIcon='null', type='IndexableRecordItem(dataProviderName='com.mapbox.search.localProvider.history', 
type=REGION)', 
etaMinutes='null', isBatchResolveSupported='true', requestOptions='RequestOptions(query='New York', 
options=SearchOptions(proximity=Point{type=Point, bbox=null, 
coordinates=[-122.34145135612214, 47.6546276473711]}, boundingBox=null, countries=null, fuzzyMatch=null, languages=[Language(code='en')], limit=null, types=null, 
requestDebounce=null, origin=Point{type=Point, bbox=null, coordinates=[-122.34145135612214, 47.6546276473711]}, navigationOptions=null, routeOptions=null, 
unsafeParameters=null, ignoreIndexableRecords=false, indexableRecordsDistanceThresholdMeters=null), proximityRewritten=true, originRewritten=true, 
endpoint='suggest', sessionID='d0b0d4ca-7f0a-444c-8f5c-50afd2299cb3', requestContext=SearchRequestContext(apiType=GEOCODING, 
keyboardLocale=null, screenOrientation=PORTRAIT, responseUuid=))', record='HistoryRecord(id='region.17349986251855570', 
name='New York', descriptionText=null, address=SearchAddress(houseNumber=null, street=null, neighborhood=null, locality=null, postcode=null, place=null, district=null,
region=null, country=United States), routablePoints=null, categories=[], makiIcon=null, coordinate=Point{type=Point, bbox=null, coordinates=[-75.46524810791016, 42.7512092590332]}, 
type=REGION, metadata=null, timestamp=1641953420006)')
DzmitryFomchyn commented 2 years ago

The suggestions you've shared have SearchSuggestionType.IndexableRecordItem types, i.e. those suggestions that point to IndexableRecords (such as history or favorite). These suggestions have discriptionText only if corresponding IndexableRecord have descriptions text

https://github.com/mapbox/mapbox-search-android/blob/b87b1cca35dcd12e58827252ba9e946a78306fee/MapboxSearch/sdk/src/main/java/com/mapbox/search/result/IndexableRecordSearchSuggestion.kt#L31-L32

You can set SearchOptions.ignoreIndexableRecords to true if you don't need IndexableRecords in responses.

As I said before, descriptionText is a nullable property, we can't guarantee its presence.

In our UI SDK, we use fallback for a case when there's no descriptionText

https://github.com/mapbox/mapbox-search-android/blob/b87b1cca35dcd12e58827252ba9e946a78306fee/MapboxSearch/ui/src/main/java/com/mapbox/search/ui/utils/SearchEntityPresentation.kt#L29-L37

You can use something similar in your project.

If you run this code

        val searchEngine = MapboxSearchSdk.getSearchEngine()
        searchEngine.search(
            "Starbucks",
            SearchOptions(
                proximity = Point.fromLngLat(-73.9856429431713, 40.74842420227826),
                ignoreIndexableRecords = true,
            ),
            object : SearchSuggestionsCallback {
                override fun onSuggestions(suggestions: List<SearchSuggestion>, responseInfo: ResponseInfo) {
                    val descriptions = suggestions.joinToString {
                        "type: ${it.type} - description: ${it.descriptionText}"
                    }

                    Log.d("Test", descriptions)
                }

                override fun onError(e: Exception) {
                    TODO("Not yet implemented")
                }
            }
        )

it should print to Logcat something similar to

type: SearchResultSuggestion(types=[POI]) - description: 605 3rd Ave, New York City, New York 10158, United States of America, type: SearchResultSuggestion(types=[POI]) - description: 277 Park Ave, New York City, New York 10172, United States of America, type: SearchResultSuggestion(types=[POI]) - description: 45 E 51st St, New York City, New York 10022, United States of America, type: SearchResultSuggestion(types=[POI]) - description: 4 Columbus Cir, New York City, New York 10019, United States of America, type: SearchResultSuggestion(types=[POI]) - description: 360 Lexington Ave, New York City, New York 10017, United States of America
LaurieWuAndroid commented 2 years ago

Wow! Thanks so much @DzmitryFomchyn for this thorough collection of context and suggestions to solutions! LOVE IT! πŸŽ‰ Thanks for putting the effort into linking all the mapbox repo references as well as the code snippet that can be useful for my project specifically πŸ‘πŸΌ ❀️ !

I tried the suggestion above, I'm receiving null for Starbuck's query's descriptionText πŸ₯²

DzmitryFomchyn commented 2 years ago

Hey @LaurieWuAndroid, I'm sorry, I didn't realize that you are using geocoding API, which is the only public API now. For geocoding descriptionAddress is always null. But you can use formatted address instead

suggestions.map { 
    it.address?.formattedAddress(SearchAddress.FormatStyle.Medium)
}

F function that we use in our UI SDK for displaying subtitles is valid for geocoding as well:

https://github.com/mapbox/mapbox-search-android/blob/b87b1cca35dcd12e58827252ba9e946a78306fee/MapboxSearch/ui/src/main/java/com/mapbox/search/ui/utils/SearchEntityPresentation.kt#L29-L37

LaurieWuAndroid commented 2 years ago

πŸ’‘ I see I see, thank you for the bonus tips! Is been really helpful πŸ˜ƒ!

DzmitryFomchyn commented 2 years ago

You are welcome! Let me know if you have more questions.