Convert place search to coroutines

pull/1369/head
Alex Baker 3 years ago
parent f0f6478dab
commit 14f5015fac

@ -10,17 +10,19 @@ import com.google.android.libraries.places.api.model.AutocompleteSessionToken
import com.google.android.libraries.places.api.model.Place.Field
import com.google.android.libraries.places.api.model.RectangularBounds
import com.google.android.libraries.places.api.net.*
import org.tasks.Callback
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.tasks.R
import org.tasks.data.Place
import org.tasks.data.Place.Companion.newPlace
import kotlin.coroutines.suspendCoroutine
class GooglePlacesSearchProvider(private val context: Context) : PlaceSearchProvider {
private var token: AutocompleteSessionToken? = null
private var placesClient: PlacesClient? = null
override fun restoreState(savedInstanceState: Bundle) {
token = savedInstanceState.getParcelable(EXTRA_SESSION_TOKEN)
override fun restoreState(savedInstanceState: Bundle?) {
token = savedInstanceState?.getParcelable(EXTRA_SESSION_TOKEN)
}
override fun saveState(outState: Bundle) {
@ -31,52 +33,63 @@ class GooglePlacesSearchProvider(private val context: Context) : PlaceSearchProv
return if (dark) R.drawable.places_powered_by_google_dark else R.drawable.places_powered_by_google_light
}
override fun search(
query: String,
bias: MapPosition?,
onSuccess: Callback<List<PlaceSearchResult>>,
onError: Callback<String>) {
if (!Places.isInitialized()) {
Places.initialize(context, context.getString(R.string.google_key))
}
if (placesClient == null) {
placesClient = Places.createClient(context)
}
if (token == null) {
token = AutocompleteSessionToken.newInstance()
}
val request = FindAutocompletePredictionsRequest.builder().setSessionToken(token).setQuery(query)
if (bias != null) {
request.setLocationBias(
RectangularBounds.newInstance(
LatLngBounds.builder()
.include(LatLng(bias.latitude, bias.longitude))
.build()))
}
placesClient!!
.findAutocompletePredictions(request.build())
.addOnSuccessListener { response: FindAutocompletePredictionsResponse -> onSuccess.call(toSearchResults(response.autocompletePredictions)) }
.addOnFailureListener { e: Exception -> onError.call(e.message) }
}
override suspend fun search(query: String, bias: MapPosition?): List<PlaceSearchResult> =
withContext(Dispatchers.IO) {
suspendCoroutine { cont ->
if (!Places.isInitialized()) {
Places.initialize(context, context.getString(R.string.google_key))
}
if (placesClient == null) {
placesClient = Places.createClient(context)
}
if (token == null) {
token = AutocompleteSessionToken.newInstance()
}
val request = FindAutocompletePredictionsRequest.builder().setSessionToken(token).setQuery(query)
if (bias != null) {
request.locationBias =
RectangularBounds.newInstance(
LatLngBounds.builder()
.include(LatLng(bias.latitude, bias.longitude))
.build())
}
placesClient!!
.findAutocompletePredictions(request.build())
.addOnSuccessListener { response: FindAutocompletePredictionsResponse ->
val places = toSearchResults(response.autocompletePredictions)
cont.resumeWith(Result.success(places))
}
.addOnFailureListener { e: Exception ->
cont.resumeWith(Result.failure(e))
}
}
}
override suspend fun fetch(placeSearchResult: PlaceSearchResult): Place =
withContext(Dispatchers.IO) {
suspendCoroutine { cont ->
placesClient!!
.fetchPlace(
FetchPlaceRequest.builder(
placeSearchResult.id,
listOf(
Field.ID,
Field.LAT_LNG,
Field.ADDRESS,
Field.WEBSITE_URI,
Field.NAME,
Field.PHONE_NUMBER))
.setSessionToken(token)
.build())
.addOnSuccessListener { result: FetchPlaceResponse ->
cont.resumeWith(Result.success(toPlace(result)))
}
.addOnFailureListener { e: Exception ->
cont.resumeWith(Result.failure(e))
}
}
}
override fun fetch(
placeSearchResult: PlaceSearchResult, onSuccess: Callback<Place>, onError: Callback<String>) {
placesClient!!
.fetchPlace(
FetchPlaceRequest.builder(
placeSearchResult.id,
listOf(
Field.ID,
Field.LAT_LNG,
Field.ADDRESS,
Field.WEBSITE_URI,
Field.NAME,
Field.PHONE_NUMBER))
.setSessionToken(token)
.build())
.addOnSuccessListener { result: FetchPlaceResponse -> onSuccess.call(toPlace(result)) }
.addOnFailureListener { e: Exception -> onError.call(e.message) }
}
private fun toSearchResults(predictions: List<AutocompletePrediction>): List<PlaceSearchResult> {
return predictions.map {

@ -1,87 +0,0 @@
package org.tasks.location;
import static org.tasks.data.Place.newPlace;
import android.content.Context;
import android.os.Bundle;
import androidx.annotation.Nullable;
import com.mapbox.api.geocoding.v5.MapboxGeocoding;
import com.mapbox.api.geocoding.v5.models.CarmenFeature;
import com.mapbox.api.geocoding.v5.models.GeocodingResponse;
import com.mapbox.geojson.Point;
import java.util.ArrayList;
import java.util.List;
import org.tasks.Callback;
import org.tasks.R;
import org.tasks.data.Place;
import retrofit2.Call;
import retrofit2.Response;
public class MapboxSearchProvider implements PlaceSearchProvider {
private final Context context;
private MapboxGeocoding.Builder builder;
public MapboxSearchProvider(Context context) {
this.context = context;
}
@Override
public void restoreState(Bundle savedInstanceState) {}
@Override
public void saveState(Bundle outState) {}
@Override
public int getAttributionRes(boolean dark) {
return R.drawable.mapbox_logo_icon;
}
@Override
public void search(
String query,
@Nullable MapPosition bias,
Callback<List<PlaceSearchResult>> onSuccess,
Callback<String> onError) {
if (builder == null) {
String token = context.getString(R.string.mapbox_key);
builder = MapboxGeocoding.builder().autocomplete(true).accessToken(token);
if (bias != null) {
builder.proximity(Point.fromLngLat(bias.getLongitude(), bias.getLatitude()));
}
}
builder
.query(query)
.build()
.enqueueCall(
new retrofit2.Callback<GeocodingResponse>() {
@Override
public void onResponse(
Call<GeocodingResponse> call, Response<GeocodingResponse> response) {
List<PlaceSearchResult> results = new ArrayList<>();
results.clear();
for (CarmenFeature feature : response.body().features()) {
results.add(toSearchResult(feature));
}
onSuccess.call(results);
}
@Override
public void onFailure(Call<GeocodingResponse> call, Throwable t) {
onError.call(t.getMessage());
}
});
}
@Override
public void fetch(
PlaceSearchResult placeSearchResult, Callback<Place> onSuccess, Callback<String> onError) {
onSuccess.call(placeSearchResult.getPlace());
}
private PlaceSearchResult toSearchResult(CarmenFeature feature) {
Place place = newPlace(feature);
return new PlaceSearchResult(feature.id(), place.getName(), place.getDisplayAddress(), place);
}
}

@ -0,0 +1,67 @@
package org.tasks.location
import android.content.Context
import android.os.Bundle
import com.mapbox.api.geocoding.v5.MapboxGeocoding
import com.mapbox.api.geocoding.v5.models.CarmenFeature
import com.mapbox.api.geocoding.v5.models.GeocodingResponse
import com.mapbox.geojson.Point
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.tasks.R
import org.tasks.data.Place
import org.tasks.data.Place.Companion.newPlace
import retrofit2.Call
import retrofit2.Response
import java.util.*
import kotlin.coroutines.suspendCoroutine
class MapboxSearchProvider(private val context: Context) : PlaceSearchProvider {
private var builder: MapboxGeocoding.Builder? = null
override fun restoreState(savedInstanceState: Bundle?) {}
override fun saveState(outState: Bundle) {}
override fun getAttributionRes(dark: Boolean) = R.drawable.mapbox_logo_icon
override suspend fun search(query: String, bias: MapPosition?): List<PlaceSearchResult> =
withContext(Dispatchers.IO) {
suspendCoroutine { cont ->
if (builder == null) {
val token = context.getString(R.string.mapbox_key)
builder = MapboxGeocoding.builder().autocomplete(true).accessToken(token)
if (bias != null) {
builder?.proximity(Point.fromLngLat(bias.longitude, bias.latitude))
}
}
builder
?.query(query)
?.build()
?.enqueueCall(
object : retrofit2.Callback<GeocodingResponse> {
override fun onResponse(
call: Call<GeocodingResponse>, response: Response<GeocodingResponse>) {
val results: MutableList<PlaceSearchResult> = ArrayList()
results.clear()
for (feature in response.body()!!.features()) {
results.add(toSearchResult(feature))
}
cont.resumeWith(Result.success(results))
}
override fun onFailure(call: Call<GeocodingResponse>, t: Throwable) {
cont.resumeWith(Result.failure(t))
}
})
}
}
override suspend fun fetch(placeSearchResult: PlaceSearchResult): Place =
placeSearchResult.place
private fun toSearchResult(feature: CarmenFeature): PlaceSearchResult {
val place = newPlace(feature)
return PlaceSearchResult(feature.id(), place.name, place.displayAddress, place)
}
}

@ -1,25 +0,0 @@
package org.tasks.location;
import android.os.Bundle;
import androidx.annotation.DrawableRes;
import androidx.annotation.Nullable;
import java.util.List;
import org.tasks.Callback;
import org.tasks.data.Place;
public interface PlaceSearchProvider {
void restoreState(Bundle savedInstanceState);
void saveState(Bundle outState);
@DrawableRes int getAttributionRes(boolean dark);
void search(
String query,
@Nullable MapPosition bias,
Callback<List<PlaceSearchResult>> onSuccess,
Callback<String> onError);
void fetch(
PlaceSearchResult placeSearchResult, Callback<Place> onSuccess, Callback<String> onError);
}

@ -0,0 +1,19 @@
package org.tasks.location
import android.os.Bundle
import androidx.annotation.DrawableRes
import org.tasks.Callback
import org.tasks.data.Place
interface PlaceSearchProvider {
fun restoreState(savedInstanceState: Bundle?)
fun saveState(outState: Bundle)
@DrawableRes
fun getAttributionRes(dark: Boolean): Int
suspend fun search(query: String, bias: MapPosition?): List<PlaceSearchResult>
suspend fun fetch(placeSearchResult: PlaceSearchResult): Place
}

@ -1,14 +1,10 @@
package org.tasks.location
import android.os.Bundle
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModel
import com.todoroo.andlib.utility.AndroidUtilities
import androidx.lifecycle.*
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.launch
import org.tasks.Event
import org.tasks.Strings.isNullOrEmpty
import org.tasks.data.Place
import javax.inject.Inject
@ -30,7 +26,7 @@ class PlaceSearchViewModel @Inject constructor(
error.observe(owner, onError!!)
}
fun saveState(outState: Bundle?) {
fun saveState(outState: Bundle) {
searchProvider.saveState(outState)
}
@ -38,17 +34,24 @@ class PlaceSearchViewModel @Inject constructor(
searchProvider.restoreState(savedInstanceState)
}
fun query(query: String?, bias: MapPosition?) {
AndroidUtilities.assertMainThread()
if (isNullOrEmpty(query)) {
fun query(query: String?, bias: MapPosition?) = viewModelScope.launch {
if (query.isNullOrBlank()) {
searchResults.postValue(emptyList())
} else {
searchProvider.search(query, bias, { value: List<PlaceSearchResult> -> searchResults.setValue(value) }) { message: String -> setError(message) }
try {
searchResults.value = searchProvider.search(query, bias)
} catch (e: Exception) {
e.message?.let { setError(it) }
}
}
}
fun fetch(result: PlaceSearchResult?) {
searchProvider.fetch(result, { value: Place -> selection.setValue(value) }) { message: String -> setError(message) }
fun fetch(result: PlaceSearchResult) = viewModelScope.launch {
try {
selection.value = searchProvider.fetch(result)
} catch (e: Exception) {
e.message?.let { setError(it) }
}
}
private fun setError(message: String) {

Loading…
Cancel
Save