From 19c927474f528f26cafd68ec984dbc504d6b866d Mon Sep 17 00:00:00 2001 From: Nikolay Unuchek Date: Wed, 25 Mar 2020 10:56:28 +0300 Subject: [PATCH 01/21] added kotlin support migrate JavaVersion.VERSION_1_7 -> JavaVersion.VERSION_1_8 migrate to use AndroidX build:gradle 2.3.3-> 3.6.1 compileSdkVersion = 25 -> 28 targetSdkVersion = 25 -> 26 buildToolsVersion = "25.0.3" -> "28.0.3" gms:play-services-location:11.0.4 -> 17.0.0 added support of com.google.android.libraries.places:places-compat --- android-reactive-location/build.gradle | 19 +- .../DataBufferObservable.java | 25 +-- .../ReactiveLocationProvider.java | 65 +++++-- ...ReactiveLocationProviderConfiguration.java | 2 +- .../BaseObservableOnSubscribe.java | 10 +- .../PendingResultObservableOnSubscribe.java | 29 +-- .../TaskResultObservableOnSubscribe.kt | 32 ++++ .../geocode/GeocodeObservable.java | 12 +- .../AddGeofenceObservableOnSubscribe.java | 2 +- ...eByPendingIntentObservableOnSubscribe.java | 2 +- ...ofenceRequestIdsObservableOnSubscribe.java | 8 +- ...ionIntentUpdatesObservableOnSubscribe.java | 2 +- .../MockLocationObservableOnSubscribe.java | 2 +- ...ionIntentUpdatesObservableOnSubscribe.java | 2 +- build.gradle | 12 +- gradle.properties | 2 + gradle/wrapper/gradle-wrapper.properties | 4 +- maven_push.gradle | 10 +- sample/build.gradle | 19 +- .../sample/BaseActivity.java | 21 +-- .../sample/GeofenceBroadcastReceiver.java | 3 +- .../sample/MainActivity.java | 51 +++--- .../sample/MockLocationsActivity.java | 8 +- .../sample/PlacesActivity.java | 165 ------------------ .../sample/PlacesActivity.kt | 130 ++++++++++++++ .../sample/PlacesResultActivity.java | 82 --------- .../sample/PlacesResultActivity.kt | 68 ++++++++ .../sample/utils/RxTextView.java | 5 +- 28 files changed, 386 insertions(+), 406 deletions(-) create mode 100644 android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/TaskResultObservableOnSubscribe.kt delete mode 100755 sample/src/main/java/pl/charmas/android/reactivelocation2/sample/PlacesActivity.java create mode 100755 sample/src/main/java/pl/charmas/android/reactivelocation2/sample/PlacesActivity.kt delete mode 100755 sample/src/main/java/pl/charmas/android/reactivelocation2/sample/PlacesResultActivity.java create mode 100755 sample/src/main/java/pl/charmas/android/reactivelocation2/sample/PlacesResultActivity.kt diff --git a/android-reactive-location/build.gradle b/android-reactive-location/build.gradle index fc254d07..7f16b349 100644 --- a/android-reactive-location/build.gradle +++ b/android-reactive-location/build.gradle @@ -1,4 +1,6 @@ apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-android-extensions' apply plugin: 'maven' android { @@ -6,22 +8,27 @@ android { buildToolsVersion rootProject.ext.buildToolsVersion defaultConfig { - minSdkVersion 14 + minSdkVersion 16 targetSdkVersion rootProject.ext.targetSdkVersion } compileOptions { - sourceCompatibility JavaVersion.VERSION_1_7 - targetCompatibility JavaVersion.VERSION_1_7 + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 } } //TODO: local maven deployment dependencies { - compile 'com.google.android.gms:play-services-location:11.0.4' - compile 'com.google.android.gms:play-services-places:11.0.4' - compile 'io.reactivex.rxjava2:rxjava:2.0.5' + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" + implementation "androidx.core:core-ktx:1.2.0" + + api ('com.google.android.gms:play-services-location:17.0.0'){ + exclude group: 'com.google.android.gms', module: 'play-services-places' + } + api 'com.google.android.libraries.places:places-compat:2.2.0' + api 'io.reactivex.rxjava2:rxjava:2.2.19' } // Comment this to deploy to local maven repository diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/DataBufferObservable.java b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/DataBufferObservable.java index ad1e57fb..1bb44718 100644 --- a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/DataBufferObservable.java +++ b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/DataBufferObservable.java @@ -1,12 +1,8 @@ package pl.charmas.android.reactivelocation2; -import com.google.android.gms.common.data.AbstractDataBuffer; - +import com.google.android.gms.common.data.DataBuffer; import io.reactivex.Observable; -import io.reactivex.ObservableEmitter; -import io.reactivex.ObservableOnSubscribe; import io.reactivex.disposables.Disposables; -import io.reactivex.functions.Action; /** @@ -25,21 +21,12 @@ private DataBufferObservable() { * @param item type * @return observable that emits all items from buffer and on unsubscription releases it */ - public static Observable from(final AbstractDataBuffer buffer) { - return Observable.create(new ObservableOnSubscribe() { - - @Override - public void subscribe(final ObservableEmitter emitter) { - for (T item : buffer) { - emitter.onNext(item); - } - emitter.setDisposable(Disposables.fromAction(new Action() { - @Override - public void run() throws Exception { - buffer.release(); - } - })); + public static Observable from(final DataBuffer buffer) { + return Observable.create(emitter -> { + for (T item : buffer) { + emitter.onNext(item); } + emitter.setDisposable(Disposables.fromAction(() -> buffer.release())); }); } } diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/ReactiveLocationProvider.java b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/ReactiveLocationProvider.java index f6c3533b..8c1db747 100644 --- a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/ReactiveLocationProvider.java +++ b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/ReactiveLocationProvider.java @@ -5,9 +5,8 @@ import android.location.Address; import android.location.Location; import android.os.Handler; -import android.support.annotation.Nullable; -import android.support.annotation.RequiresPermission; - +import androidx.annotation.Nullable; +import androidx.annotation.RequiresPermission; import com.google.android.gms.common.api.Api; import com.google.android.gms.common.api.GoogleApiClient; import com.google.android.gms.common.api.PendingResult; @@ -29,16 +28,15 @@ import com.google.android.gms.location.places.PlacePhotoResult; import com.google.android.gms.location.places.Places; import com.google.android.gms.maps.model.LatLngBounds; - -import java.util.List; -import java.util.Locale; - +import com.google.android.gms.tasks.Task; +import com.google.android.libraries.places.compat.AutocompletePredictionBufferResponse; import io.reactivex.Observable; import io.reactivex.functions.Function; import pl.charmas.android.reactivelocation2.observables.GoogleAPIClientObservableOnSubscribe; import pl.charmas.android.reactivelocation2.observables.ObservableContext; import pl.charmas.android.reactivelocation2.observables.ObservableFactory; import pl.charmas.android.reactivelocation2.observables.PendingResultObservableOnSubscribe; +import pl.charmas.android.reactivelocation2.observables.TaskResultObservableOnSubscribe; import pl.charmas.android.reactivelocation2.observables.activity.ActivityUpdatesObservableOnSubscribe; import pl.charmas.android.reactivelocation2.observables.geocode.GeocodeObservable; import pl.charmas.android.reactivelocation2.observables.geocode.ReverseGeocodeObservable; @@ -50,6 +48,9 @@ import pl.charmas.android.reactivelocation2.observables.location.MockLocationObservableOnSubscribe; import pl.charmas.android.reactivelocation2.observables.location.RemoveLocationIntentUpdatesObservableOnSubscribe; +import java.util.List; +import java.util.Locale; + /** * Factory of observables that can manipulate location @@ -363,15 +364,24 @@ public Observable apply(GoogleApiClient api) { * * @param placeId id for place * @return observable that emits places buffer and completes + * + * @deprecated use {@link ReactiveLocationProvider#getPlaceCompatById(java.lang.String)} */ + @Deprecated public Observable getPlaceById(@Nullable final String placeId) { return getGoogleApiClientObservable(Places.PLACE_DETECTION_API, Places.GEO_DATA_API) - .flatMap(new Function>() { - @Override - public Observable apply(GoogleApiClient api) { - return fromPendingResult(Places.GeoDataApi.getPlaceById(api, placeId)); - } - }); + .flatMap( api -> fromPendingResult(Places.GeoDataApi.getPlaceById(api, placeId))); + } + + /** + * Returns observable that fetches a place from the Places API using the place ID. + * + * @param placeId id for place + * @return observable that emits places buffer and completes + */ + public Observable getPlaceCompatById(@Nullable final String placeId) { + return getGoogleApiClientObservable(Places.PLACE_DETECTION_API, Places.GEO_DATA_API) + .flatMap( api -> fromTaskResult(com.google.android.libraries.places.compat.Places.getGeoDataClient(this.ctx.getContext()).getPlaceById(placeId))); } /** @@ -383,15 +393,28 @@ public Observable apply(GoogleApiClient api) { * @param bounds bounds where to fetch suggestions from * @param filter filter * @return observable with suggestions buffer and completes + * + * @deprecated use {@link ReactiveLocationProvider#getPlaceCompatAutocompletePredictions(java.lang.String, com.google.android.gms.maps.model.LatLngBounds, com.google.android.libraries.places.compat.AutocompleteFilter)} */ + @Deprecated public Observable getPlaceAutocompletePredictions(final String query, final LatLngBounds bounds, final AutocompleteFilter filter) { return getGoogleApiClientObservable(Places.PLACE_DETECTION_API, Places.GEO_DATA_API) - .flatMap(new Function>() { - @Override - public Observable apply(GoogleApiClient api) { - return fromPendingResult(Places.GeoDataApi.getAutocompletePredictions(api, query, bounds, filter)); - } - }); + .flatMap(api -> fromPendingResult(Places.GeoDataApi.getAutocompletePredictions(api, query, bounds, filter))); + } + + /** + * Returns observable that fetches autocomplete predictions from Places API. To flatmap and autorelease + * {@link com.google.android.libraries.places.compat.AutocompletePredictionBufferResponse} you can use + * {@link DataBufferObservable}. + * + * @param query search query + * @param bounds bounds where to fetch suggestions from + * @param filter filter + * @return observable with suggestions buffer and completes + */ + public Observable getPlaceCompatAutocompletePredictions(final String query, final LatLngBounds bounds, final com.google.android.libraries.places.compat.AutocompleteFilter filter) { + return getGoogleApiClientObservable(Places.PLACE_DETECTION_API, Places.GEO_DATA_API) + .flatMap(api -> fromTaskResult(com.google.android.libraries.places.compat.Places.getGeoDataClient(this.ctx.getContext()).getAutocompletePredictions(query, bounds, filter))); } /** @@ -452,4 +475,8 @@ public Observable getGoogleApiClientObservable(Api... apis) { public static Observable fromPendingResult(PendingResult result) { return Observable.create(new PendingResultObservableOnSubscribe<>(result)); } + + public static Observable fromTaskResult(Task result) { + return Observable.create(new TaskResultObservableOnSubscribe<>(result)); + } } diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/ReactiveLocationProviderConfiguration.java b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/ReactiveLocationProviderConfiguration.java index 2cbfe264..dc72428c 100644 --- a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/ReactiveLocationProviderConfiguration.java +++ b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/ReactiveLocationProviderConfiguration.java @@ -1,7 +1,7 @@ package pl.charmas.android.reactivelocation2; import android.os.Handler; -import android.support.annotation.Nullable; +import androidx.annotation.Nullable; /** * Configuration for location provider. Pleas use builder to create an instance. diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/BaseObservableOnSubscribe.java b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/BaseObservableOnSubscribe.java index 3b7adf6e..e6853a6e 100644 --- a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/BaseObservableOnSubscribe.java +++ b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/BaseObservableOnSubscribe.java @@ -3,20 +3,18 @@ import android.content.Context; import android.os.Bundle; import android.os.Handler; -import android.support.annotation.NonNull; - +import androidx.annotation.NonNull; import com.google.android.gms.common.ConnectionResult; import com.google.android.gms.common.api.Api; import com.google.android.gms.common.api.GoogleApiClient; - -import java.util.Arrays; -import java.util.List; - import io.reactivex.ObservableEmitter; import io.reactivex.ObservableOnSubscribe; import io.reactivex.disposables.Disposables; import io.reactivex.functions.Action; +import java.util.Arrays; +import java.util.List; + public abstract class BaseObservableOnSubscribe implements ObservableOnSubscribe { private final Context ctx; diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/PendingResultObservableOnSubscribe.java b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/PendingResultObservableOnSubscribe.java index f70eacad..5bc2e3ef 100644 --- a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/PendingResultObservableOnSubscribe.java +++ b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/PendingResultObservableOnSubscribe.java @@ -1,15 +1,10 @@ package pl.charmas.android.reactivelocation2.observables; -import android.support.annotation.NonNull; - import com.google.android.gms.common.api.PendingResult; import com.google.android.gms.common.api.Result; -import com.google.android.gms.common.api.ResultCallback; - import io.reactivex.ObservableEmitter; import io.reactivex.ObservableOnSubscribe; import io.reactivex.disposables.Disposables; -import io.reactivex.functions.Action; public class PendingResultObservableOnSubscribe implements ObservableOnSubscribe { private final PendingResult result; @@ -20,24 +15,18 @@ public PendingResultObservableOnSubscribe(PendingResult result) { } @Override - public void subscribe(final ObservableEmitter emitter) throws Exception { - result.setResultCallback(new ResultCallback() { - @Override - public void onResult(@NonNull T t) { - if (!emitter.isDisposed()) { - emitter.onNext(t); - emitter.onComplete(); - } - complete = true; + public void subscribe(final ObservableEmitter emitter) { + result.setResultCallback(t -> { + if (!emitter.isDisposed()) { + emitter.onNext(t); + emitter.onComplete(); } + complete = true; }); - emitter.setDisposable(Disposables.fromAction(new Action() { - @Override - public void run() { - if (!complete) { - result.cancel(); - } + emitter.setDisposable(Disposables.fromAction(() -> { + if (!complete) { + result.cancel(); } })); } diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/TaskResultObservableOnSubscribe.kt b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/TaskResultObservableOnSubscribe.kt new file mode 100644 index 00000000..6a923678 --- /dev/null +++ b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/TaskResultObservableOnSubscribe.kt @@ -0,0 +1,32 @@ +package pl.charmas.android.reactivelocation2.observables + +import com.google.android.gms.tasks.Task +import io.reactivex.ObservableEmitter +import io.reactivex.ObservableOnSubscribe + +class TaskResultObservableOnSubscribe(private val result: Task) : + ObservableOnSubscribe { + override fun subscribe(emitter: ObservableEmitter) { + result.addOnSuccessListener { t: T -> + if (!emitter.isDisposed) { + emitter.onNext(t) + emitter.onComplete() + } + } + result.addOnCompleteListener { command -> + if (!emitter.isDisposed) { + val value = command.result + if (value != null) { + emitter.onNext(value) + }else{ + emitter.onComplete() + } + } + } + result.addOnFailureListener { exception -> + if (!emitter.isDisposed) { + emitter.onError(exception) + } + } + } +} \ No newline at end of file diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/geocode/GeocodeObservable.java b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/geocode/GeocodeObservable.java index e74f1fc8..6d037e92 100644 --- a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/geocode/GeocodeObservable.java +++ b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/geocode/GeocodeObservable.java @@ -3,19 +3,17 @@ import android.content.Context; import android.location.Address; import android.location.Geocoder; -import android.support.annotation.NonNull; - +import androidx.annotation.NonNull; import com.google.android.gms.maps.model.LatLngBounds; - -import java.io.IOException; -import java.util.List; -import java.util.Locale; - import io.reactivex.Observable; import io.reactivex.ObservableEmitter; import io.reactivex.ObservableOnSubscribe; import pl.charmas.android.reactivelocation2.observables.ObservableFactory; +import java.io.IOException; +import java.util.List; +import java.util.Locale; + public class GeocodeObservable implements ObservableOnSubscribe> { private final Context ctx; private final String locationName; diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/geofence/AddGeofenceObservableOnSubscribe.java b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/geofence/AddGeofenceObservableOnSubscribe.java index 300973d9..2a5e69ca 100644 --- a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/geofence/AddGeofenceObservableOnSubscribe.java +++ b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/geofence/AddGeofenceObservableOnSubscribe.java @@ -1,7 +1,7 @@ package pl.charmas.android.reactivelocation2.observables.geofence; import android.app.PendingIntent; -import android.support.annotation.NonNull; +import androidx.annotation.NonNull; import com.google.android.gms.common.api.GoogleApiClient; import com.google.android.gms.common.api.ResultCallback; diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/geofence/RemoveGeofenceByPendingIntentObservableOnSubscribe.java b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/geofence/RemoveGeofenceByPendingIntentObservableOnSubscribe.java index 551d9b70..0e814d44 100644 --- a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/geofence/RemoveGeofenceByPendingIntentObservableOnSubscribe.java +++ b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/geofence/RemoveGeofenceByPendingIntentObservableOnSubscribe.java @@ -1,7 +1,7 @@ package pl.charmas.android.reactivelocation2.observables.geofence; import android.app.PendingIntent; -import android.support.annotation.NonNull; +import androidx.annotation.NonNull; import com.google.android.gms.common.api.GoogleApiClient; import com.google.android.gms.common.api.ResultCallback; diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/geofence/RemoveGeofenceRequestIdsObservableOnSubscribe.java b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/geofence/RemoveGeofenceRequestIdsObservableOnSubscribe.java index 557b86e9..6f84035a 100644 --- a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/geofence/RemoveGeofenceRequestIdsObservableOnSubscribe.java +++ b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/geofence/RemoveGeofenceRequestIdsObservableOnSubscribe.java @@ -1,18 +1,16 @@ package pl.charmas.android.reactivelocation2.observables.geofence; -import android.support.annotation.NonNull; - +import androidx.annotation.NonNull; import com.google.android.gms.common.api.GoogleApiClient; import com.google.android.gms.common.api.ResultCallback; import com.google.android.gms.common.api.Status; import com.google.android.gms.location.LocationServices; - -import java.util.List; - import io.reactivex.ObservableEmitter; import pl.charmas.android.reactivelocation2.observables.ObservableContext; import pl.charmas.android.reactivelocation2.observables.StatusException; +import java.util.List; + class RemoveGeofenceRequestIdsObservableOnSubscribe extends RemoveGeofenceObservableOnSubscribe { private final List geofenceRequestIds; diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/location/AddLocationIntentUpdatesObservableOnSubscribe.java b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/location/AddLocationIntentUpdatesObservableOnSubscribe.java index 5774a55f..2f4d60e5 100644 --- a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/location/AddLocationIntentUpdatesObservableOnSubscribe.java +++ b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/location/AddLocationIntentUpdatesObservableOnSubscribe.java @@ -1,7 +1,7 @@ package pl.charmas.android.reactivelocation2.observables.location; import android.app.PendingIntent; -import android.support.annotation.NonNull; +import androidx.annotation.NonNull; import com.google.android.gms.common.api.GoogleApiClient; import com.google.android.gms.common.api.ResultCallback; diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/location/MockLocationObservableOnSubscribe.java b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/location/MockLocationObservableOnSubscribe.java index f8c6cc76..cfd389ad 100644 --- a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/location/MockLocationObservableOnSubscribe.java +++ b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/location/MockLocationObservableOnSubscribe.java @@ -1,7 +1,7 @@ package pl.charmas.android.reactivelocation2.observables.location; import android.location.Location; -import android.support.annotation.NonNull; +import androidx.annotation.NonNull; import com.google.android.gms.common.api.GoogleApiClient; import com.google.android.gms.common.api.ResultCallback; diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/location/RemoveLocationIntentUpdatesObservableOnSubscribe.java b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/location/RemoveLocationIntentUpdatesObservableOnSubscribe.java index 881e35a5..ddf8351b 100644 --- a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/location/RemoveLocationIntentUpdatesObservableOnSubscribe.java +++ b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/location/RemoveLocationIntentUpdatesObservableOnSubscribe.java @@ -1,7 +1,7 @@ package pl.charmas.android.reactivelocation2.observables.location; import android.app.PendingIntent; -import android.support.annotation.NonNull; +import androidx.annotation.NonNull; import com.google.android.gms.common.api.GoogleApiClient; import com.google.android.gms.common.api.ResultCallback; diff --git a/build.gradle b/build.gradle index 1c6739a9..5ddc0441 100644 --- a/build.gradle +++ b/build.gradle @@ -1,21 +1,24 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { + ext.kotlin_version = '1.3.71' repositories { mavenCentral() jcenter() + google() } dependencies { - classpath 'com.android.tools.build:gradle:2.3.3' + classpath 'com.android.tools.build:gradle:3.6.1' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } // To avoid manually setting the same values in all Android modules, set the value on the root // project and then reference this from the modules ext { - compileSdkVersion = 25 - targetSdkVersion = 25 - buildToolsVersion = "25.0.3" + compileSdkVersion = 28 + targetSdkVersion = 26 + buildToolsVersion = "28.0.3" } def isReleaseBuild() { @@ -29,5 +32,6 @@ allprojects { repositories { mavenCentral() jcenter() + google() } } diff --git a/gradle.properties b/gradle.properties index a0a4da92..9ba32d77 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,3 +1,5 @@ +android.useAndroidX=true + VERSION_NAME=2.1 VERSION_CODE=102 GROUP=pl.charmas.android diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index cbb3db13..a8f002d2 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Fri Sep 29 10:11:52 CEST 2017 +#Tue Mar 24 17:39:00 MSK 2020 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip diff --git a/maven_push.gradle b/maven_push.gradle index 712abf68..9350cea1 100644 --- a/maven_push.gradle +++ b/maven_push.gradle @@ -92,6 +92,11 @@ afterEvaluate { project -> sign configurations.archives } + task androidSourcesJar(type: Jar) { + classifier = 'sources' + from android.sourceSets.main.java.sourceFiles + } + task androidJavadocs(type: Javadoc) { source = android.sourceSets.main.java.srcDirs classpath += project.files(android.getBootClasspath().join(File.pathSeparator)) @@ -103,11 +108,6 @@ afterEvaluate { project -> from androidJavadocs.destinationDir } - task androidSourcesJar(type: Jar) { - classifier = 'sources' - from android.sourceSets.main.java.sourceFiles - } - artifacts { archives androidSourcesJar archives androidJavadocsJar diff --git a/sample/build.gradle b/sample/build.gradle index ddb75b0e..563992e4 100755 --- a/sample/build.gradle +++ b/sample/build.gradle @@ -1,4 +1,6 @@ apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-android-extensions' def getGooglePlayServicesApiKey() { if (REACTIVE_LOCATION_GMS_API_KEY != null && !REACTIVE_LOCATION_GMS_API_KEY.isEmpty()) { @@ -12,7 +14,7 @@ android { buildToolsVersion rootProject.ext.buildToolsVersion defaultConfig { - minSdkVersion 14 + minSdkVersion 19 targetSdkVersion rootProject.ext.targetSdkVersion versionName project.VERSION_NAME versionCode Integer.parseInt(project.VERSION_CODE) @@ -20,8 +22,8 @@ android { } compileOptions { - sourceCompatibility JavaVersion.VERSION_1_7 - targetCompatibility JavaVersion.VERSION_1_7 + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 } buildTypes { release { @@ -35,8 +37,11 @@ android { } dependencies { - compile 'com.android.support:appcompat-v7:25.3.1' - compile 'io.reactivex.rxjava2:rxandroid:2.0.1' - compile 'com.tbruyelle.rxpermissions2:rxpermissions:0.9.1@aar' - compile project(':android-reactive-location') + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" + implementation "androidx.core:core-ktx:1.2.0" + + implementation 'androidx.appcompat:appcompat:1.1.0' + implementation 'io.reactivex.rxjava2:rxandroid:2.1.1' + implementation 'com.tbruyelle.rxpermissions2:rxpermissions:0.9.1@aar' + implementation project(':android-reactive-location') } diff --git a/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/BaseActivity.java b/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/BaseActivity.java index 70a45c5c..e95fc55b 100755 --- a/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/BaseActivity.java +++ b/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/BaseActivity.java @@ -1,28 +1,23 @@ package pl.charmas.android.reactivelocation2.sample; import android.Manifest; -import android.support.v7.app.AppCompatActivity; import android.widget.Toast; - +import androidx.appcompat.app.AppCompatActivity; import com.tbruyelle.rxpermissions2.RxPermissions; - -import io.reactivex.functions.Consumer; +import io.reactivex.disposables.Disposable; public abstract class BaseActivity extends AppCompatActivity { @Override protected void onStart() { super.onStart(); - new RxPermissions(this) + Disposable subscribe = new RxPermissions(this) .request(Manifest.permission.ACCESS_FINE_LOCATION) - .subscribe(new Consumer() { - @Override - public void accept(Boolean granted) throws Exception { - if (granted) { - onLocationPermissionGranted(); - } else { - Toast.makeText(BaseActivity.this, "Sorry, no demo without permission...", Toast.LENGTH_SHORT).show(); - } + .subscribe(granted -> { + if (granted) { + onLocationPermissionGranted(); + } else { + Toast.makeText(BaseActivity.this, "Sorry, no demo without permission...", Toast.LENGTH_SHORT).show(); } }); } diff --git a/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/GeofenceBroadcastReceiver.java b/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/GeofenceBroadcastReceiver.java index 8b1c5c44..931de375 100644 --- a/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/GeofenceBroadcastReceiver.java +++ b/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/GeofenceBroadcastReceiver.java @@ -5,8 +5,7 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; -import android.support.v4.app.NotificationCompat; - +import androidx.core.app.NotificationCompat; import com.google.android.gms.location.Geofence; import com.google.android.gms.location.GeofencingEvent; diff --git a/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/MainActivity.java b/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/MainActivity.java index 6ab3f2b9..9cda52ce 100755 --- a/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/MainActivity.java +++ b/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/MainActivity.java @@ -1,5 +1,6 @@ package pl.charmas.android.reactivelocation2.sample; +import android.annotation.SuppressLint; import android.content.Intent; import android.content.IntentSender; import android.location.Address; @@ -8,10 +9,8 @@ import android.text.TextUtils; import android.util.Log; import android.view.Menu; -import android.view.MenuItem; import android.widget.TextView; import android.widget.Toast; - import com.google.android.gms.common.api.Status; import com.google.android.gms.location.ActivityRecognitionResult; import com.google.android.gms.location.LocationRequest; @@ -19,9 +18,6 @@ import com.google.android.gms.location.LocationSettingsResult; import com.google.android.gms.location.LocationSettingsStates; import com.google.android.gms.location.LocationSettingsStatusCodes; - -import java.util.List; - import io.reactivex.Observable; import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.disposables.Disposable; @@ -36,6 +32,8 @@ import pl.charmas.android.reactivelocation2.sample.utils.LocationToStringFunc; import pl.charmas.android.reactivelocation2.sample.utils.ToMostProbableActivity; +import java.util.List; + import static pl.charmas.android.reactivelocation2.sample.utils.UnsubscribeIfPresent.dispose; public class MainActivity extends BaseActivity { @@ -58,15 +56,16 @@ public class MainActivity extends BaseActivity { private Disposable activityDisposable; private Observable addressObservable; + @SuppressLint("MissingPermission") @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); - lastKnownLocationView = (TextView) findViewById(R.id.last_known_location_view); - updatableLocationView = (TextView) findViewById(R.id.updated_location_view); - addressLocationView = (TextView) findViewById(R.id.address_for_location_view); - currentActivityView = (TextView) findViewById(R.id.activity_recent_view); + lastKnownLocationView = findViewById(R.id.last_known_location_view); + updatableLocationView = findViewById(R.id.updated_location_view); + addressLocationView = findViewById(R.id.address_for_location_view); + currentActivityView = findViewById(R.id.activity_recent_view); locationProvider = new ReactiveLocationProvider(getApplicationContext(), ReactiveLocationProviderConfiguration .builder() @@ -171,30 +170,21 @@ protected void onStop() { @Override public boolean onCreateOptionsMenu(Menu menu) { - menu.add("Geofencing").setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { - @Override - public boolean onMenuItemClick(MenuItem item) { - startActivity(new Intent(MainActivity.this, GeofenceActivity.class)); - return true; - } + menu.add("Geofencing").setOnMenuItemClickListener(item -> { + startActivity(new Intent(MainActivity.this, GeofenceActivity.class)); + return true; }); - menu.add("Places").setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { - @Override - public boolean onMenuItemClick(MenuItem item) { - if (TextUtils.isEmpty(getString(R.string.API_KEY))) { - Toast.makeText(MainActivity.this, "First you need to configure your API Key - see README.md", Toast.LENGTH_SHORT).show(); - } else { - startActivity(new Intent(MainActivity.this, PlacesActivity.class)); - } - return true; + menu.add("Places").setOnMenuItemClickListener(item -> { + if (TextUtils.isEmpty(getString(R.string.API_KEY))) { + Toast.makeText(MainActivity.this, "First you need to configure your API Key - see README.md", Toast.LENGTH_SHORT).show(); + } else { + startActivity(new Intent(MainActivity.this, PlacesActivity.class)); } + return true; }); - menu.add("Mock Locations").setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { - @Override - public boolean onMenuItemClick(MenuItem item) { - startActivity(new Intent(MainActivity.this, MockLocationsActivity.class)); - return true; - } + menu.add("Mock Locations").setOnMenuItemClickListener(item -> { + startActivity(new Intent(MainActivity.this, MockLocationsActivity.class)); + return true; }); return true; } @@ -209,6 +199,7 @@ public void accept(Throwable throwable) { @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); final LocationSettingsStates states = LocationSettingsStates.fromIntent(data);//intent); switch (requestCode) { case REQUEST_CHECK_SETTINGS: diff --git a/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/MockLocationsActivity.java b/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/MockLocationsActivity.java index c529304f..6cd45dc3 100755 --- a/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/MockLocationsActivity.java +++ b/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/MockLocationsActivity.java @@ -6,7 +6,6 @@ import android.os.Build; import android.os.Bundle; import android.os.SystemClock; -import android.support.v4.app.ActivityCompat; import android.util.Log; import android.view.View; import android.widget.Button; @@ -15,12 +14,9 @@ import android.widget.TextView; import android.widget.Toast; import android.widget.ToggleButton; - +import androidx.core.app.ActivityCompat; import com.google.android.gms.common.api.Status; import com.google.android.gms.location.LocationRequest; - -import java.util.Date; - import io.reactivex.Observable; import io.reactivex.disposables.Disposable; import io.reactivex.functions.BiFunction; @@ -31,6 +27,8 @@ import pl.charmas.android.reactivelocation2.sample.utils.DisplayTextOnViewAction; import pl.charmas.android.reactivelocation2.sample.utils.LocationToStringFunc; +import java.util.Date; + import static pl.charmas.android.reactivelocation2.sample.utils.UnsubscribeIfPresent.dispose; public class MockLocationsActivity extends BaseActivity { diff --git a/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/PlacesActivity.java b/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/PlacesActivity.java deleted file mode 100755 index 26966661..00000000 --- a/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/PlacesActivity.java +++ /dev/null @@ -1,165 +0,0 @@ -package pl.charmas.android.reactivelocation2.sample; - -import android.location.Location; -import android.os.Bundle; -import android.text.TextUtils; -import android.util.Log; -import android.view.View; -import android.widget.AdapterView; -import android.widget.ArrayAdapter; -import android.widget.EditText; -import android.widget.ListView; -import android.widget.TextView; - -import com.google.android.gms.location.places.AutocompletePrediction; -import com.google.android.gms.location.places.AutocompletePredictionBuffer; -import com.google.android.gms.location.places.PlaceLikelihood; -import com.google.android.gms.location.places.PlaceLikelihoodBuffer; -import com.google.android.gms.maps.model.LatLng; -import com.google.android.gms.maps.model.LatLngBounds; - -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.TimeUnit; - -import io.reactivex.Observable; -import io.reactivex.disposables.CompositeDisposable; -import io.reactivex.functions.BiFunction; -import io.reactivex.functions.Consumer; -import io.reactivex.functions.Function; -import io.reactivex.functions.Predicate; -import pl.charmas.android.reactivelocation2.ReactiveLocationProvider; -import pl.charmas.android.reactivelocation2.sample.utils.RxTextView; - -import static pl.charmas.android.reactivelocation2.sample.utils.UnsubscribeIfPresent.dispose; - -public class PlacesActivity extends BaseActivity { - - private TextView currentPlaceView; - private EditText queryView; - private ListView placeSuggestionsList; - private ReactiveLocationProvider reactiveLocationProvider; - private CompositeDisposable compositeDisposable; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_places); - currentPlaceView = (TextView) findViewById(R.id.current_place_view); - queryView = (EditText) findViewById(R.id.place_query_view); - placeSuggestionsList = (ListView) findViewById(R.id.place_suggestions_list); - placeSuggestionsList.setOnItemClickListener(new AdapterView.OnItemClickListener() { - @Override - public void onItemClick(AdapterView parent, View view, int position, long id) { - AutocompleteInfo info = (AutocompleteInfo) parent.getAdapter().getItem(position); - startActivity(PlacesResultActivity.getStartIntent(PlacesActivity.this, info.id)); - } - }); - - reactiveLocationProvider = new ReactiveLocationProvider(this); - } - - @Override - protected void onLocationPermissionGranted() { - compositeDisposable = new CompositeDisposable(); - compositeDisposable.add( - reactiveLocationProvider.getCurrentPlace(null) - .subscribe(new Consumer() { - @Override - public void accept(PlaceLikelihoodBuffer buffer) { - PlaceLikelihood likelihood = buffer.get(0); - if (likelihood != null) { - currentPlaceView.setText(likelihood.getPlace().getName()); - } - buffer.release(); - } - }, new Consumer() { - @Override - public void accept(Throwable throwable) throws Exception { - Log.e("PlacesActivity", "Error in observable", throwable); - } - }) - ); - - Observable queryObservable = RxTextView - .textChanges(queryView) - .map(new Function() { - @Override - public String apply(CharSequence charSequence) { - return charSequence.toString(); - } - }) - .debounce(1, TimeUnit.SECONDS) - .filter(new Predicate() { - @Override - public boolean test(String s) { - return !TextUtils.isEmpty(s); - } - }); - Observable lastKnownLocationObservable = reactiveLocationProvider.getLastKnownLocation(); - Observable suggestionsObservable = Observable - .combineLatest(queryObservable, lastKnownLocationObservable, - new BiFunction() { - @Override - public QueryWithCurrentLocation apply(String query, Location currentLocation) { - return new QueryWithCurrentLocation(query, currentLocation); - } - }).flatMap(new Function>() { - @Override - public Observable apply(QueryWithCurrentLocation q) { - if (q.location == null) return Observable.empty(); - - double latitude = q.location.getLatitude(); - double longitude = q.location.getLongitude(); - LatLngBounds bounds = new LatLngBounds( - new LatLng(latitude - 0.05, longitude - 0.05), - new LatLng(latitude + 0.05, longitude + 0.05) - ); - return reactiveLocationProvider.getPlaceAutocompletePredictions(q.query, bounds, null); - } - }); - - compositeDisposable.add(suggestionsObservable.subscribe(new Consumer() { - @Override - public void accept(AutocompletePredictionBuffer buffer) { - List infos = new ArrayList<>(); - for (AutocompletePrediction prediction : buffer) { - infos.add(new AutocompleteInfo(prediction.getFullText(null).toString(), prediction.getPlaceId())); - } - buffer.release(); - placeSuggestionsList.setAdapter(new ArrayAdapter<>(PlacesActivity.this, android.R.layout.simple_list_item_1, infos)); - } - })); - } - - @Override - protected void onStop() { - super.onStop(); - dispose(compositeDisposable); - } - - private static class QueryWithCurrentLocation { - final String query; - public final Location location; - - private QueryWithCurrentLocation(String query, Location location) { - this.query = query; - this.location = location; - } - } - - private static class AutocompleteInfo { - private final String description; - private final String id; - - private AutocompleteInfo(String description, String id) { - this.description = description; - this.id = id; - } - - @Override - public String toString() { - return description; - } - } -} diff --git a/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/PlacesActivity.kt b/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/PlacesActivity.kt new file mode 100755 index 00000000..ab674c85 --- /dev/null +++ b/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/PlacesActivity.kt @@ -0,0 +1,130 @@ +package pl.charmas.android.reactivelocation2.sample + +import android.annotation.SuppressLint +import android.location.Location +import android.os.Bundle +import android.util.Log +import android.widget.AdapterView.OnItemClickListener +import android.widget.ArrayAdapter +import android.widget.EditText +import android.widget.ListView +import android.widget.TextView +import com.google.android.gms.maps.model.LatLng +import com.google.android.gms.maps.model.LatLngBounds +import io.reactivex.Observable +import io.reactivex.disposables.CompositeDisposable +import io.reactivex.functions.BiFunction +import pl.charmas.android.reactivelocation2.ReactiveLocationProvider +import pl.charmas.android.reactivelocation2.sample.utils.RxTextView +import pl.charmas.android.reactivelocation2.sample.utils.UnsubscribeIfPresent +import java.util.concurrent.TimeUnit + +class PlacesActivity : BaseActivity() { + + private lateinit var currentPlaceView: TextView + private lateinit var queryView: EditText + private lateinit var placeSuggestionsList: ListView + private lateinit var reactiveLocationProvider: ReactiveLocationProvider + private var compositeDisposable: CompositeDisposable = CompositeDisposable() + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_places) + currentPlaceView = findViewById(R.id.current_place_view) + queryView = findViewById(R.id.place_query_view) + placeSuggestionsList = findViewById(R.id.place_suggestions_list) + placeSuggestionsList.onItemClickListener = + OnItemClickListener { parent, view, position, id -> + val info = + parent.adapter.getItem(position) as AutocompleteInfo + val placeId = info.id + if (placeId != null) { + startActivity(PlacesResultActivity.getStartIntent(this@PlacesActivity, placeId)) + } + } + reactiveLocationProvider = ReactiveLocationProvider(this) + } + + @SuppressLint("MissingPermission") + override fun onLocationPermissionGranted() { + compositeDisposable.add( + reactiveLocationProvider.getCurrentPlace(null) + .subscribe({ buffer -> + val likelihood = buffer.firstOrNull() + if (likelihood != null) { + currentPlaceView.text = likelihood.place.name + } + buffer.release() + }) { throwable -> + Log.e("PlacesActivity", "Error in observable", throwable) + } + ) + val queryObservable = RxTextView + .textChanges(queryView) + .map { charSequence -> charSequence.toString() } + .debounce(1, TimeUnit.SECONDS) + .filter { s -> s.isNotEmpty() } + + compositeDisposable.add( + Observable.combineLatest( + queryObservable, + reactiveLocationProvider.lastKnownLocation, + BiFunction { query, currentLocation -> + QueryWithCurrentLocation(query, currentLocation) + } + ) + .flatMap { q -> + val latitude = q.location.latitude + val longitude = q.location.longitude + val bounds = LatLngBounds( + LatLng(latitude - 50.05, longitude - 50.05), + LatLng(latitude + 50.05, longitude + 50.05) + ) + reactiveLocationProvider.getPlaceCompatAutocompletePredictions( + q.query, + bounds, + null + ) + } + .doOnError { Log.e(TAG, "onLocationPermissionGranted (line 80): ", it) } + .retry() + .subscribe { buffer -> + val infos = mutableListOf() + for (prediction in buffer) { + infos.add( + AutocompleteInfo( + prediction.getFullText(null).toString(), + prediction.placeId + ) + ) + } + buffer.release() + placeSuggestionsList.adapter = ArrayAdapter( + this@PlacesActivity, + android.R.layout.simple_list_item_1, + infos + ) + } + ) + } + + override fun onStop() { + super.onStop() + UnsubscribeIfPresent.dispose(compositeDisposable) + } + + data class QueryWithCurrentLocation(val query: String, val location: Location) + + data class AutocompleteInfo( + val description: String, + val id: String? + ) { + override fun toString(): String { + return description + } + } + + companion object { + const val TAG: String = "PlacesActivity" + } +} \ No newline at end of file diff --git a/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/PlacesResultActivity.java b/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/PlacesResultActivity.java deleted file mode 100755 index cabde5d7..00000000 --- a/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/PlacesResultActivity.java +++ /dev/null @@ -1,82 +0,0 @@ -package pl.charmas.android.reactivelocation2.sample; - -import android.content.Context; -import android.content.Intent; -import android.os.Bundle; -import android.support.annotation.NonNull; -import android.widget.TextView; - -import com.google.android.gms.location.places.Place; -import com.google.android.gms.location.places.PlaceBuffer; - -import io.reactivex.disposables.CompositeDisposable; -import io.reactivex.functions.Consumer; -import pl.charmas.android.reactivelocation2.ReactiveLocationProvider; - -import static pl.charmas.android.reactivelocation2.sample.utils.UnsubscribeIfPresent.dispose; - -public class PlacesResultActivity extends BaseActivity { - - private static final String EXTRA_PLACE_ID = "EXTRA_PLACE_ID"; - - private ReactiveLocationProvider reactiveLocationProvider; - private CompositeDisposable compositeSubscription; - private TextView placeNameView; - private TextView placeLocationView; - private TextView placeAddressView; - private String placeId; - - public static Intent getStartIntent(Context context, @NonNull String placeId) { - Intent startIntent = new Intent(context, PlacesResultActivity.class); - startIntent.putExtra(EXTRA_PLACE_ID, placeId); - - return startIntent; - } - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_places_result); - - placeNameView = (TextView) findViewById(R.id.place_name_view); - placeLocationView = (TextView) findViewById(R.id.place_location_view); - placeAddressView = (TextView) findViewById(R.id.place_address_view); - - reactiveLocationProvider = new ReactiveLocationProvider(this); - - getPlaceIdFromIntent(); - } - - private void getPlaceIdFromIntent() { - Intent loadedIntent = getIntent(); - placeId = loadedIntent.getStringExtra(EXTRA_PLACE_ID); - - if (placeId == null) { - throw new IllegalStateException("You must start SearchResultsActivity with a non-null place Id using getStartIntent(Context, String)"); - } - } - - @Override - protected void onLocationPermissionGranted() { - compositeSubscription = new CompositeDisposable(); - compositeSubscription.add(reactiveLocationProvider.getPlaceById(placeId) - .subscribe(new Consumer() { - @Override - public void accept(PlaceBuffer buffer) { - Place place = buffer.get(0); - if (place != null) { - placeNameView.setText(place.getName()); - placeLocationView.setText(place.getLatLng().latitude + ", " + place.getLatLng().longitude); - placeAddressView.setText(place.getAddress()); - } - buffer.release(); - } - })); - } - - @Override - protected void onStop() { - super.onStop(); - dispose(compositeSubscription); - } -} diff --git a/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/PlacesResultActivity.kt b/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/PlacesResultActivity.kt new file mode 100755 index 00000000..3c8dc102 --- /dev/null +++ b/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/PlacesResultActivity.kt @@ -0,0 +1,68 @@ +package pl.charmas.android.reactivelocation2.sample + +import android.content.Context +import android.content.Intent +import android.os.Bundle +import android.view.View +import android.widget.TextView +import io.reactivex.disposables.CompositeDisposable +import pl.charmas.android.reactivelocation2.ReactiveLocationProvider +import pl.charmas.android.reactivelocation2.sample.utils.UnsubscribeIfPresent + +class PlacesResultActivity : BaseActivity() { + private lateinit var reactiveLocationProvider: ReactiveLocationProvider + private lateinit var compositeSubscription: CompositeDisposable + private lateinit var placeNameView: TextView + private lateinit var placeLocationView: TextView + private lateinit var placeAddressView: TextView + private var placeId: String? = null + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_places_result) + placeNameView = findViewById(R.id.place_name_view) as TextView + placeLocationView = findViewById(R.id.place_location_view) as TextView + placeAddressView = findViewById(R.id.place_address_view) as TextView + reactiveLocationProvider = ReactiveLocationProvider(this) + placeIdFromIntent + } + + private val placeIdFromIntent: Unit + get() { + val loadedIntent = intent + placeId = loadedIntent.getStringExtra(EXTRA_PLACE_ID) + if (placeId == null) { + throw IllegalArgumentException("You must start SearchResultsActivity with a non-null place Id using getStartIntent(Context, String)") + } + } + + override fun onLocationPermissionGranted() { + compositeSubscription = CompositeDisposable() + compositeSubscription.add( + reactiveLocationProvider.getPlaceCompatById(placeId) + .subscribe { buffer -> + val place = buffer.firstOrNull() + if (place != null) { + placeNameView.text = place.name + val text = place.latLng.latitude.toString() + ", " + place.latLng.longitude + placeLocationView.text = text + placeAddressView.text = place.address + } + buffer.release() + } + ) + } + + override fun onStop() { + super.onStop() + UnsubscribeIfPresent.dispose(compositeSubscription) + } + + companion object { + private const val EXTRA_PLACE_ID = "EXTRA_PLACE_ID" + fun getStartIntent(context: Context?, placeId: String): Intent { + val startIntent = Intent(context, PlacesResultActivity::class.java) + startIntent.putExtra(EXTRA_PLACE_ID, placeId) + return startIntent + } + } +} \ No newline at end of file diff --git a/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/utils/RxTextView.java b/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/utils/RxTextView.java index 7c9ead44..2c2e5553 100644 --- a/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/utils/RxTextView.java +++ b/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/utils/RxTextView.java @@ -1,9 +1,8 @@ package pl.charmas.android.reactivelocation2.sample.utils; -import android.support.annotation.CheckResult; -import android.support.annotation.NonNull; import android.widget.TextView; - +import androidx.annotation.CheckResult; +import androidx.annotation.NonNull; import io.reactivex.Observable; From c7380589c08def36c86456eab2be08754515a495 Mon Sep 17 00:00:00 2001 From: Nikolay Unuchek Date: Wed, 25 Mar 2020 11:55:20 +0300 Subject: [PATCH 02/21] updated DataBufferObservable.kt --- .../DataBufferObservable.java | 32 ------------------- .../reactivelocation2/DataBufferObservable.kt | 29 +++++++++++++++++ 2 files changed, 29 insertions(+), 32 deletions(-) delete mode 100644 android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/DataBufferObservable.java create mode 100644 android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/DataBufferObservable.kt diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/DataBufferObservable.java b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/DataBufferObservable.java deleted file mode 100644 index 1bb44718..00000000 --- a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/DataBufferObservable.java +++ /dev/null @@ -1,32 +0,0 @@ -package pl.charmas.android.reactivelocation2; - -import com.google.android.gms.common.data.DataBuffer; -import io.reactivex.Observable; -import io.reactivex.disposables.Disposables; - - -/** - * Util class that creates observable from buffer. - */ -public final class DataBufferObservable { - - private DataBufferObservable() { - //no instance - } - - /** - * Creates observable from buffer. On unsubscribe buffer is automatically released. - * - * @param buffer source buffer - * @param item type - * @return observable that emits all items from buffer and on unsubscription releases it - */ - public static Observable from(final DataBuffer buffer) { - return Observable.create(emitter -> { - for (T item : buffer) { - emitter.onNext(item); - } - emitter.setDisposable(Disposables.fromAction(() -> buffer.release())); - }); - } -} diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/DataBufferObservable.kt b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/DataBufferObservable.kt new file mode 100644 index 00000000..aaf6592c --- /dev/null +++ b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/DataBufferObservable.kt @@ -0,0 +1,29 @@ +package pl.charmas.android.reactivelocation2 + +import com.google.android.gms.common.data.DataBuffer +import io.reactivex.Observable +import io.reactivex.ObservableEmitter +import io.reactivex.disposables.Disposables + +/** + * Util class that creates observable from buffer. + */ +object DataBufferObservable { + /** + * Creates observable from buffer. On unsubscribe buffer is automatically released. + * + * @param buffer source buffer + * @param item type + * @return observable that emits all items from buffer and on unsubscription releases it + */ + fun from(buffer: DataBuffer): Observable { + return Observable.create { emitter -> + for (item in buffer) { + emitter.onNext(item) + } + buffer.release() + emitter.onComplete() + emitter.setDisposable(Disposables.fromAction { buffer.release() }) + } + } +} \ No newline at end of file From 2b134e6634ebe47ee595bdbfd8c671499e72638a Mon Sep 17 00:00:00 2001 From: Nikolay Unuchek Date: Wed, 25 Mar 2020 15:34:34 +0300 Subject: [PATCH 03/21] updated to Maybe --- android-reactive-location/build.gradle | 6 +- .../src/main/AndroidManifest.xml | 5 +- .../reactivelocation2/DataBufferObservable.kt | 11 +- .../ReactiveLocationProvider.java | 81 +++++++----- .../observables/BaseMaybeOnSubscribe.java | 116 ++++++++++++++++++ .../GoogleAPIClientMaybeOnSubscribe.java | 25 ++++ .../observables/MaybeContext.java | 29 +++++ .../observables/MaybeFactory.java | 42 +++++++ .../observables/TaskResultMaybeOnSubscribe.kt | 31 +++++ 9 files changed, 301 insertions(+), 45 deletions(-) create mode 100644 android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/BaseMaybeOnSubscribe.java create mode 100644 android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/GoogleAPIClientMaybeOnSubscribe.java create mode 100644 android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/MaybeContext.java create mode 100644 android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/MaybeFactory.java create mode 100644 android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/TaskResultMaybeOnSubscribe.kt diff --git a/android-reactive-location/build.gradle b/android-reactive-location/build.gradle index 7f16b349..457d5b92 100644 --- a/android-reactive-location/build.gradle +++ b/android-reactive-location/build.gradle @@ -24,11 +24,11 @@ dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" implementation "androidx.core:core-ktx:1.2.0" - api ('com.google.android.gms:play-services-location:17.0.0'){ + implementation ('com.google.android.gms:play-services-location:17.0.0'){ exclude group: 'com.google.android.gms', module: 'play-services-places' } - api 'com.google.android.libraries.places:places-compat:2.2.0' - api 'io.reactivex.rxjava2:rxjava:2.2.19' + implementation 'com.google.android.libraries.places:places-compat:2.2.0' + implementation 'io.reactivex.rxjava2:rxjava:2.2.19' } // Comment this to deploy to local maven repository diff --git a/android-reactive-location/src/main/AndroidManifest.xml b/android-reactive-location/src/main/AndroidManifest.xml index cd701d43..489959df 100644 --- a/android-reactive-location/src/main/AndroidManifest.xml +++ b/android-reactive-location/src/main/AndroidManifest.xml @@ -1,5 +1,2 @@ - - - - + diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/DataBufferObservable.kt b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/DataBufferObservable.kt index aaf6592c..41a1142c 100644 --- a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/DataBufferObservable.kt +++ b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/DataBufferObservable.kt @@ -18,12 +18,15 @@ object DataBufferObservable { */ fun from(buffer: DataBuffer): Observable { return Observable.create { emitter -> + emitter.setDisposable(Disposables.fromAction { buffer.release() }) for (item in buffer) { - emitter.onNext(item) + if (!emitter.isDisposed) { + emitter.onNext(item) + } + } + if (!emitter.isDisposed) { + emitter.onComplete() } - buffer.release() - emitter.onComplete() - emitter.setDisposable(Disposables.fromAction { buffer.release() }) } } } \ No newline at end of file diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/ReactiveLocationProvider.java b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/ReactiveLocationProvider.java index 8c1db747..26fd9370 100644 --- a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/ReactiveLocationProvider.java +++ b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/ReactiveLocationProvider.java @@ -30,13 +30,17 @@ import com.google.android.gms.maps.model.LatLngBounds; import com.google.android.gms.tasks.Task; import com.google.android.libraries.places.compat.AutocompletePredictionBufferResponse; +import io.reactivex.Maybe; import io.reactivex.Observable; import io.reactivex.functions.Function; +import pl.charmas.android.reactivelocation2.observables.GoogleAPIClientMaybeOnSubscribe; import pl.charmas.android.reactivelocation2.observables.GoogleAPIClientObservableOnSubscribe; +import pl.charmas.android.reactivelocation2.observables.MaybeContext; +import pl.charmas.android.reactivelocation2.observables.MaybeFactory; import pl.charmas.android.reactivelocation2.observables.ObservableContext; import pl.charmas.android.reactivelocation2.observables.ObservableFactory; import pl.charmas.android.reactivelocation2.observables.PendingResultObservableOnSubscribe; -import pl.charmas.android.reactivelocation2.observables.TaskResultObservableOnSubscribe; +import pl.charmas.android.reactivelocation2.observables.TaskResultMaybeOnSubscribe; import pl.charmas.android.reactivelocation2.observables.activity.ActivityUpdatesObservableOnSubscribe; import pl.charmas.android.reactivelocation2.observables.geocode.GeocodeObservable; import pl.charmas.android.reactivelocation2.observables.geocode.ReverseGeocodeObservable; @@ -57,40 +61,44 @@ * delivered by Google Play Services. */ public class ReactiveLocationProvider { - private final ObservableContext ctx; - private final ObservableFactory factory; + private final ObservableContext ctxObservable; + private final MaybeContext ctxMaybe; + private final ObservableFactory factoryObservable; + private final MaybeFactory factoryMaybe; /** * Creates location provider instance with default configuration. * - * @param ctx preferably application context + * @param context preferably application context */ - public ReactiveLocationProvider(Context ctx) { - this(ctx, ReactiveLocationProviderConfiguration.builder().build()); + public ReactiveLocationProvider(Context context) { + this(context, ReactiveLocationProviderConfiguration.builder().build()); } /** * Create location provider with given {@link ReactiveLocationProviderConfiguration}. * - * @param ctx preferably application context + * @param context preferably application context * @param configuration configuration instance */ - public ReactiveLocationProvider(Context ctx, ReactiveLocationProviderConfiguration configuration) { - this.ctx = new ObservableContext(ctx, configuration); - this.factory = new ObservableFactory(this.ctx); + public ReactiveLocationProvider(Context context, ReactiveLocationProviderConfiguration configuration) { + this.ctxObservable = new ObservableContext(context, configuration); + this.ctxMaybe = new MaybeContext(context, configuration); + this.factoryObservable = new ObservableFactory(this.ctxObservable); + this.factoryMaybe = new MaybeFactory(this.ctxMaybe); } /** * Creates location provider with custom handler in which all GooglePlayServices callbacks are called. * - * @param ctx preferably application context + * @param ctxObservable preferably application context * @param handler on which all GooglePlayServices callbacks are called * @see com.google.android.gms.common.api.GoogleApiClient.Builder#setHandler(android.os.Handler) * @deprecated please use {@link ReactiveLocationProvider#ReactiveLocationProvider(Context, ReactiveLocationProviderConfiguration)} */ @Deprecated - public ReactiveLocationProvider(Context ctx, Handler handler) { - this(ctx, ReactiveLocationProviderConfiguration.builder().setCustomCallbackHandler(handler).build()); + public ReactiveLocationProvider(Context ctxObservable, Handler handler) { + this(ctxObservable, ReactiveLocationProviderConfiguration.builder().setCustomCallbackHandler(handler).build()); } /** @@ -109,7 +117,7 @@ public ReactiveLocationProvider(Context ctx, Handler handler) { anyOf = {"android.permission.ACCESS_COARSE_LOCATION", "android.permission.ACCESS_FINE_LOCATION"} ) public Observable getLastKnownLocation() { - return LastKnownLocationObservableOnSubscribe.createObservable(ctx, factory); + return LastKnownLocationObservableOnSubscribe.createObservable(ctxObservable, factoryObservable); } /** @@ -129,7 +137,7 @@ public Observable getLastKnownLocation() { anyOf = {"android.permission.ACCESS_COARSE_LOCATION", "android.permission.ACCESS_FINE_LOCATION"} ) public Observable getUpdatedLocation(LocationRequest locationRequest) { - return LocationUpdatesObservableOnSubscribe.createObservable(ctx, factory, locationRequest); + return LocationUpdatesObservableOnSubscribe.createObservable(ctxObservable, factoryObservable, locationRequest); } /** @@ -154,7 +162,7 @@ public Observable getUpdatedLocation(LocationRequest locationRequest) "android.permission.ACCESS_MOCK_LOCATION"} ) public Observable mockLocation(Observable sourceLocationObservable) { - return MockLocationObservableOnSubscribe.createObservable(ctx, factory, sourceLocationObservable); + return MockLocationObservableOnSubscribe.createObservable(ctxObservable, factoryObservable, sourceLocationObservable); } /** @@ -175,7 +183,7 @@ public Observable mockLocation(Observable sourceLocationObserv anyOf = {"android.permission.ACCESS_COARSE_LOCATION", "android.permission.ACCESS_FINE_LOCATION"} ) public Observable requestLocationUpdates(LocationRequest locationRequest, PendingIntent intent) { - return AddLocationIntentUpdatesObservableOnSubscribe.createObservable(ctx, factory, locationRequest, intent); + return AddLocationIntentUpdatesObservableOnSubscribe.createObservable(ctxObservable, factoryObservable, locationRequest, intent); } /** @@ -187,7 +195,7 @@ public Observable requestLocationUpdates(LocationRequest locationRequest * @return observable that removes the PendingIntent */ public Observable removeLocationUpdates(PendingIntent intent) { - return RemoveLocationIntentUpdatesObservableOnSubscribe.createObservable(ctx, factory, intent); + return RemoveLocationIntentUpdatesObservableOnSubscribe.createObservable(ctxObservable, factoryObservable, intent); } /** @@ -202,7 +210,7 @@ public Observable removeLocationUpdates(PendingIntent intent) { * @return observable that serves list of address based on location */ public Observable> getReverseGeocodeObservable(double lat, double lng, int maxResults) { - return ReverseGeocodeObservable.createObservable(ctx.getContext(), factory, Locale.getDefault(), lat, lng, maxResults); + return ReverseGeocodeObservable.createObservable(ctxObservable.getContext(), factoryObservable, Locale.getDefault(), lat, lng, maxResults); } /** @@ -218,7 +226,7 @@ public Observable> getReverseGeocodeObservable(double lat, double * @return observable that serves list of address based on location */ public Observable> getReverseGeocodeObservable(Locale locale, double lat, double lng, int maxResults) { - return ReverseGeocodeObservable.createObservable(ctx.getContext(), factory, locale, lat, lng, maxResults); + return ReverseGeocodeObservable.createObservable(ctxObservable.getContext(), factoryObservable, locale, lat, lng, maxResults); } /** @@ -259,7 +267,7 @@ public Observable> getGeocodeObservable(String locationName, int m * @return observable that serves list of address based on location name */ public Observable> getGeocodeObservable(String locationName, int maxResults, LatLngBounds bounds, Locale locale) { - return GeocodeObservable.createObservable(ctx.getContext(), factory, locationName, maxResults, bounds, locale); + return GeocodeObservable.createObservable(ctxObservable.getContext(), factoryObservable, locationName, maxResults, bounds, locale); } /** @@ -278,7 +286,7 @@ public Observable> getGeocodeObservable(String locationName, int m */ @RequiresPermission("android.permission.ACCESS_FINE_LOCATION") public Observable addGeofences(PendingIntent geofenceTransitionPendingIntent, GeofencingRequest request) { - return AddGeofenceObservableOnSubscribe.createObservable(ctx, factory, request, geofenceTransitionPendingIntent); + return AddGeofenceObservableOnSubscribe.createObservable(ctxObservable, factoryObservable, request, geofenceTransitionPendingIntent); } /** @@ -294,7 +302,7 @@ public Observable addGeofences(PendingIntent geofenceTransitionPendingIn * @return observable that removed geofences */ public Observable removeGeofences(PendingIntent pendingIntent) { - return RemoveGeofenceObservableOnSubscribe.createObservable(ctx, factory, pendingIntent); + return RemoveGeofenceObservableOnSubscribe.createObservable(ctxObservable, factoryObservable, pendingIntent); } /** @@ -310,7 +318,7 @@ public Observable removeGeofences(PendingIntent pendingIntent) { * @return observable that removed geofences */ public Observable removeGeofences(List requestIds) { - return RemoveGeofenceObservableOnSubscribe.createObservable(ctx, factory, requestIds); + return RemoveGeofenceObservableOnSubscribe.createObservable(ctxObservable, factoryObservable, requestIds); } @@ -321,7 +329,7 @@ public Observable removeGeofences(List requestIds) { * @return observable that provides activity recognition */ public Observable getDetectedActivity(int detectIntervalMiliseconds) { - return ActivityUpdatesObservableOnSubscribe.createObservable(ctx, factory, detectIntervalMiliseconds); + return ActivityUpdatesObservableOnSubscribe.createObservable(ctxObservable, factoryObservable, detectIntervalMiliseconds); } /** @@ -379,9 +387,9 @@ public Observable getPlaceById(@Nullable final String placeId) { * @param placeId id for place * @return observable that emits places buffer and completes */ - public Observable getPlaceCompatById(@Nullable final String placeId) { - return getGoogleApiClientObservable(Places.PLACE_DETECTION_API, Places.GEO_DATA_API) - .flatMap( api -> fromTaskResult(com.google.android.libraries.places.compat.Places.getGeoDataClient(this.ctx.getContext()).getPlaceById(placeId))); + public Maybe getPlaceCompatById(@Nullable final String placeId) { + return getGoogleApiClientMaybe(Places.PLACE_DETECTION_API, Places.GEO_DATA_API) + .flatMap( api -> maybeTaskResult(com.google.android.libraries.places.compat.Places.getGeoDataClient(this.ctxObservable.getContext()).getPlaceById(placeId))); } /** @@ -412,9 +420,9 @@ public Observable getPlaceAutocompletePredictions( * @param filter filter * @return observable with suggestions buffer and completes */ - public Observable getPlaceCompatAutocompletePredictions(final String query, final LatLngBounds bounds, final com.google.android.libraries.places.compat.AutocompleteFilter filter) { - return getGoogleApiClientObservable(Places.PLACE_DETECTION_API, Places.GEO_DATA_API) - .flatMap(api -> fromTaskResult(com.google.android.libraries.places.compat.Places.getGeoDataClient(this.ctx.getContext()).getAutocompletePredictions(query, bounds, filter))); + public Maybe getPlaceCompatAutocompletePredictions(final String query, final LatLngBounds bounds, final com.google.android.libraries.places.compat.AutocompleteFilter filter) { + return getGoogleApiClientMaybe(Places.PLACE_DETECTION_API, Places.GEO_DATA_API) + .flatMap(api -> maybeTaskResult(com.google.android.libraries.places.compat.Places.getGeoDataClient(this.ctxObservable.getContext()).getAutocompletePredictions(query, bounds, filter))); } /** @@ -462,7 +470,12 @@ public Observable apply(GoogleApiClient api) { */ public Observable getGoogleApiClientObservable(Api... apis) { //noinspection unchecked - return GoogleAPIClientObservableOnSubscribe.create(ctx, factory, apis); + return GoogleAPIClientObservableOnSubscribe.create(ctxObservable, factoryObservable, apis); + } + + public Maybe getGoogleApiClientMaybe(Api... apis) { + //noinspection unchecked + return GoogleAPIClientMaybeOnSubscribe.create(ctxMaybe, factoryMaybe, apis); } /** @@ -476,7 +489,7 @@ public static Observable fromPendingResult(PendingResult(result)); } - public static Observable fromTaskResult(Task result) { - return Observable.create(new TaskResultObservableOnSubscribe<>(result)); + public static Maybe maybeTaskResult(Task result) { + return Maybe.create(new TaskResultMaybeOnSubscribe<>(result)); } } diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/BaseMaybeOnSubscribe.java b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/BaseMaybeOnSubscribe.java new file mode 100644 index 00000000..dc807f10 --- /dev/null +++ b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/BaseMaybeOnSubscribe.java @@ -0,0 +1,116 @@ +package pl.charmas.android.reactivelocation2.observables; + +import android.content.Context; +import android.os.Bundle; +import android.os.Handler; +import androidx.annotation.NonNull; +import com.google.android.gms.common.ConnectionResult; +import com.google.android.gms.common.api.Api; +import com.google.android.gms.common.api.GoogleApiClient; +import io.reactivex.MaybeEmitter; +import io.reactivex.MaybeOnSubscribe; +import io.reactivex.disposables.Disposables; + +import java.util.Arrays; +import java.util.List; + + +public abstract class BaseMaybeOnSubscribe implements MaybeOnSubscribe { + private final Context ctx; + private final Handler handler; + private final List> services; + + @SafeVarargs + protected BaseMaybeOnSubscribe(MaybeContext ctx, Api... services) { + this.ctx = ctx.getContext(); + this.handler = ctx.getHandler(); + this.services = Arrays.asList(services); + } + + @Override + public void subscribe(MaybeEmitter emitter) { + final GoogleApiClient apiClient = createApiClient(emitter); + try { + apiClient.connect(); + } catch (Throwable ex) { + if (!emitter.isDisposed()) { + emitter.onError(ex); + } + } + + emitter.setDisposable(Disposables.fromAction(() -> { + onDisposed(apiClient); + apiClient.disconnect(); + })); + } + + + private GoogleApiClient createApiClient(MaybeEmitter emitter) { + ApiClientConnectionCallbacks apiClientConnectionCallbacks = new ApiClientConnectionCallbacks(emitter); + GoogleApiClient.Builder apiClientBuilder = new GoogleApiClient.Builder(ctx); + + for (Api service : services) { + apiClientBuilder.addApi(service); + } + + apiClientBuilder + .addConnectionCallbacks(apiClientConnectionCallbacks) + .addOnConnectionFailedListener(apiClientConnectionCallbacks); + + if (this.handler != null) { + apiClientBuilder = apiClientBuilder.setHandler(handler); + } + + GoogleApiClient apiClient = apiClientBuilder.build(); + apiClientConnectionCallbacks.setClient(apiClient); + return apiClient; + } + + protected void onDisposed(GoogleApiClient locationClient) { + } + + protected abstract void onGoogleApiClientReady(GoogleApiClient apiClient, MaybeEmitter emitter); + + private class ApiClientConnectionCallbacks implements + GoogleApiClient.ConnectionCallbacks, + GoogleApiClient.OnConnectionFailedListener { + + final private MaybeEmitter emitter; + + private GoogleApiClient apiClient; + + private ApiClientConnectionCallbacks(MaybeEmitter emitter) { + this.emitter = emitter; + } + + @Override + public void onConnected(Bundle bundle) { + try { + onGoogleApiClientReady(apiClient, emitter); + } catch (Throwable ex) { + if (!emitter.isDisposed()) { + emitter.onError(ex); + } + } + } + + @Override + public void onConnectionSuspended(int cause) { + if (!emitter.isDisposed()) { + emitter.onError(new GoogleAPIConnectionSuspendedException(cause)); + } + } + + @Override + public void onConnectionFailed(@NonNull ConnectionResult connectionResult) { + if (!emitter.isDisposed()) { + emitter.onError(new GoogleAPIConnectionException("Error connecting to GoogleApiClient.", + connectionResult)); + } + } + + void setClient(GoogleApiClient client) { + this.apiClient = client; + } + } +} diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/GoogleAPIClientMaybeOnSubscribe.java b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/GoogleAPIClientMaybeOnSubscribe.java new file mode 100644 index 00000000..e26f0f91 --- /dev/null +++ b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/GoogleAPIClientMaybeOnSubscribe.java @@ -0,0 +1,25 @@ +package pl.charmas.android.reactivelocation2.observables; + +import com.google.android.gms.common.api.Api; +import com.google.android.gms.common.api.GoogleApiClient; +import io.reactivex.Maybe; +import io.reactivex.MaybeEmitter; + +public class GoogleAPIClientMaybeOnSubscribe extends BaseMaybeOnSubscribe { + + @SafeVarargs + public static Maybe create(MaybeContext context, MaybeFactory factory, Api... apis) { + return factory.createMaybe(new GoogleAPIClientMaybeOnSubscribe(context, apis)); + } + + @SafeVarargs + private GoogleAPIClientMaybeOnSubscribe(MaybeContext ctx, Api... apis) { + super(ctx, apis); + } + + @Override + protected void onGoogleApiClientReady(GoogleApiClient apiClient, MaybeEmitter emitter) { + if (emitter.isDisposed()) return; + emitter.onSuccess(apiClient); + } +} diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/MaybeContext.java b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/MaybeContext.java new file mode 100644 index 00000000..2edb1ee2 --- /dev/null +++ b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/MaybeContext.java @@ -0,0 +1,29 @@ +package pl.charmas.android.reactivelocation2.observables; + +import android.content.Context; +import android.os.Handler; +import pl.charmas.android.reactivelocation2.ReactiveLocationProviderConfiguration; + +public class MaybeContext { + private final Context context; + private final Handler handler; + private final boolean retryOnConnectionSuspended; + + public MaybeContext(Context context, ReactiveLocationProviderConfiguration configuration) { + this.context = context; + this.handler = configuration.getCustomCallbackHandler(); + this.retryOnConnectionSuspended = configuration.isRetryOnConnectionSuspended(); + } + + public Context getContext() { + return context; + } + + Handler getHandler() { + return handler; + } + + boolean isRetryOnConnectionSuspended() { + return retryOnConnectionSuspended; + } +} diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/MaybeFactory.java b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/MaybeFactory.java new file mode 100644 index 00000000..fdb68d1b --- /dev/null +++ b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/MaybeFactory.java @@ -0,0 +1,42 @@ +package pl.charmas.android.reactivelocation2.observables; + +import io.reactivex.Maybe; +import io.reactivex.MaybeOnSubscribe; +import io.reactivex.MaybeSource; +import io.reactivex.MaybeTransformer; +import io.reactivex.functions.BiPredicate; + +public class MaybeFactory { + private final MaybeContext context; + + public MaybeFactory(MaybeContext context) { + this.context = context; + } + + public Maybe createMaybe(MaybeOnSubscribe source) { + return Maybe.create(source).compose(new RetryOnConnectionSuspension(context.isRetryOnConnectionSuspended())); + } + + private static class RetryOnConnectionSuspension implements MaybeTransformer { + private final boolean shouldRetry; + + RetryOnConnectionSuspension(boolean shouldRetry) { + this.shouldRetry = shouldRetry; + } + + @Override + public MaybeSource apply(Maybe upstream) { + if (shouldRetry) { + return upstream.retry(new IsConnectionSuspendedException()); + } + return upstream; + } + + private static class IsConnectionSuspendedException implements BiPredicate { + @Override + public boolean test(Integer integer, Throwable throwable) { + return throwable instanceof GoogleAPIConnectionSuspendedException; + } + } + } +} diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/TaskResultMaybeOnSubscribe.kt b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/TaskResultMaybeOnSubscribe.kt new file mode 100644 index 00000000..76c2f7fb --- /dev/null +++ b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/TaskResultMaybeOnSubscribe.kt @@ -0,0 +1,31 @@ +package pl.charmas.android.reactivelocation2.observables + +import com.google.android.gms.tasks.Task +import io.reactivex.MaybeEmitter +import io.reactivex.MaybeOnSubscribe + +class TaskResultMaybeOnSubscribe(private val result: Task) : + MaybeOnSubscribe { + override fun subscribe(emitter: MaybeEmitter) { + result.addOnSuccessListener { t: T -> + if (!emitter.isDisposed) { + emitter.onSuccess(t) + } + } + result.addOnCompleteListener { command -> + if (!emitter.isDisposed) { + val value = command.result + if (value != null) { + emitter.onSuccess(value) + }else{ + emitter.onComplete() + } + } + } + result.addOnFailureListener { exception -> + if (!emitter.isDisposed) { + emitter.onError(exception) + } + } + } +} \ No newline at end of file From e7ab9d8b5b02c9b1ed8c8c75baa1b8bee3d2660d Mon Sep 17 00:00:00 2001 From: Nikolay Unuchek Date: Wed, 25 Mar 2020 16:18:10 +0300 Subject: [PATCH 04/21] updated sample --- sample/build.gradle | 7 +++++++ .../android/reactivelocation2/sample/PlacesActivity.kt | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/sample/build.gradle b/sample/build.gradle index 563992e4..51b78606 100755 --- a/sample/build.gradle +++ b/sample/build.gradle @@ -41,7 +41,14 @@ dependencies { implementation "androidx.core:core-ktx:1.2.0" implementation 'androidx.appcompat:appcompat:1.1.0' + + implementation ('com.google.android.gms:play-services-location:17.0.0'){ + exclude group: 'com.google.android.gms', module: 'play-services-places' + } + implementation 'com.google.android.libraries.places:places-compat:2.2.0' + implementation 'io.reactivex.rxjava2:rxjava:2.2.19' implementation 'io.reactivex.rxjava2:rxandroid:2.1.1' implementation 'com.tbruyelle.rxpermissions2:rxpermissions:0.9.1@aar' + implementation project(':android-reactive-location') } diff --git a/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/PlacesActivity.kt b/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/PlacesActivity.kt index ab674c85..a0bcb155 100755 --- a/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/PlacesActivity.kt +++ b/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/PlacesActivity.kt @@ -73,7 +73,7 @@ class PlacesActivity : BaseActivity() { QueryWithCurrentLocation(query, currentLocation) } ) - .flatMap { q -> + .flatMapMaybe { q -> val latitude = q.location.latitude val longitude = q.location.longitude val bounds = LatLngBounds( From 48d2f377b3f28ae9c50e47cee655fff555a1679d Mon Sep 17 00:00:00 2001 From: Nikolay Unuchek Date: Thu, 26 Mar 2020 08:43:58 +0300 Subject: [PATCH 05/21] updated ReactiveLocationProvider.java --- .../android/reactivelocation2/ReactiveLocationProvider.java | 5 +++-- .../android/reactivelocation2/sample/PlacesActivity.kt | 1 + 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/ReactiveLocationProvider.java b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/ReactiveLocationProvider.java index 26fd9370..fbbd6291 100644 --- a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/ReactiveLocationProvider.java +++ b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/ReactiveLocationProvider.java @@ -5,6 +5,7 @@ import android.location.Address; import android.location.Location; import android.os.Handler; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.RequiresPermission; import com.google.android.gms.common.api.Api; @@ -376,7 +377,7 @@ public Observable apply(GoogleApiClient api) { * @deprecated use {@link ReactiveLocationProvider#getPlaceCompatById(java.lang.String)} */ @Deprecated - public Observable getPlaceById(@Nullable final String placeId) { + public Observable getPlaceById(@NonNull final String placeId) { return getGoogleApiClientObservable(Places.PLACE_DETECTION_API, Places.GEO_DATA_API) .flatMap( api -> fromPendingResult(Places.GeoDataApi.getPlaceById(api, placeId))); } @@ -387,7 +388,7 @@ public Observable getPlaceById(@Nullable final String placeId) { * @param placeId id for place * @return observable that emits places buffer and completes */ - public Maybe getPlaceCompatById(@Nullable final String placeId) { + public Maybe getPlaceCompatById(@NonNull final String placeId) { return getGoogleApiClientMaybe(Places.PLACE_DETECTION_API, Places.GEO_DATA_API) .flatMap( api -> maybeTaskResult(com.google.android.libraries.places.compat.Places.getGeoDataClient(this.ctxObservable.getContext()).getPlaceById(placeId))); } diff --git a/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/PlacesActivity.kt b/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/PlacesActivity.kt index a0bcb155..17272745 100755 --- a/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/PlacesActivity.kt +++ b/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/PlacesActivity.kt @@ -92,6 +92,7 @@ class PlacesActivity : BaseActivity() { val infos = mutableListOf() for (prediction in buffer) { infos.add( + AutocompleteInfo( prediction.getFullText(null).toString(), prediction.placeId From d8c3d2eb9babe1cefb3ce21f75500e6440404034 Mon Sep 17 00:00:00 2001 From: Nikolay Unuchek Date: Wed, 17 Jun 2020 16:53:51 +0300 Subject: [PATCH 06/21] updated libs --- android-reactive-location/build.gradle | 4 ++-- build.gradle | 4 ++-- gradle/wrapper/gradle-wrapper.properties | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/android-reactive-location/build.gradle b/android-reactive-location/build.gradle index 457d5b92..c3e26fa8 100644 --- a/android-reactive-location/build.gradle +++ b/android-reactive-location/build.gradle @@ -22,12 +22,12 @@ android { dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" - implementation "androidx.core:core-ktx:1.2.0" + implementation "androidx.core:core-ktx:1.3.0" implementation ('com.google.android.gms:play-services-location:17.0.0'){ exclude group: 'com.google.android.gms', module: 'play-services-places' } - implementation 'com.google.android.libraries.places:places-compat:2.2.0' + implementation 'com.google.android.libraries.places:places-compat:2.3.0' implementation 'io.reactivex.rxjava2:rxjava:2.2.19' } diff --git a/build.gradle b/build.gradle index 5ddc0441..db40e817 100644 --- a/build.gradle +++ b/build.gradle @@ -1,14 +1,14 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { - ext.kotlin_version = '1.3.71' + ext.kotlin_version = '1.3.72' repositories { mavenCentral() jcenter() google() } dependencies { - classpath 'com.android.tools.build:gradle:3.6.1' + classpath 'com.android.tools.build:gradle:4.0.0' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index a8f002d2..922ca983 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Tue Mar 24 17:39:00 MSK 2020 +#Wed Jun 17 16:51:28 MSK 2020 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip From 7dc8df38268fc3de3e969b4557525681d90269d4 Mon Sep 17 00:00:00 2001 From: Nikolay Unuchek Date: Mon, 20 Jul 2020 12:30:02 +0300 Subject: [PATCH 07/21] updated .gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 8a4e3d4c..e460a901 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,5 @@ build/ .DS_Store *.iml +/sample/keys/ +/sample/properties/ From ff2b65fdd7d6a0a54110fd30554ad0e2443d6c50 Mon Sep 17 00:00:00 2001 From: Nikolay Unuchek Date: Mon, 20 Jul 2020 15:45:22 +0300 Subject: [PATCH 08/21] updated google places-compat to places updated classes to kotlin style --- android-reactive-location/build.gradle | 2 +- .../ReactiveLocationProvider.java | 496 ------------------ .../ReactiveLocationProvider.kt | 466 ++++++++++++++++ ...ReactiveLocationProviderConfiguration.java | 69 --- .../ReactiveLocationProviderConfiguration.kt | 67 +++ .../android/reactivelocation2/ext/TasksExt.kt | 17 + .../BaseLocationObservableOnSubscribe.java | 9 - .../BaseLocationObservableOnSubscribe.kt | 6 + .../observables/BaseMaybeOnSubscribe.java | 116 ---- .../observables/BaseMaybeOnSubscribe.kt | 102 ++++ .../BaseObservableOnSubscribe.java | 120 ----- .../observables/BaseObservableOnSubscribe.kt | 109 ++++ .../GoogleAPIClientMaybeOnSubscribe.java | 25 - .../GoogleAPIClientMaybeOnSubscribe.kt | 34 ++ .../GoogleAPIClientObservableOnSubscribe.java | 26 - .../GoogleAPIConnectionException.java | 16 - ...GoogleAPIConnectionSuspendedException.java | 13 - .../observables/MaybeContext.java | 29 - .../observables/MaybeContext.kt | 13 + .../observables/MaybeFactory.java | 42 -- .../observables/MaybeFactory.kt | 34 ++ .../observables/ObservableContext.java | 30 -- .../observables/ObservableContext.kt | 13 + .../observables/ObservableEmitterWrapper.java | 38 -- .../observables/ObservableEmitterWrapper.kt | 27 + .../observables/ObservableFactory.java | 42 -- .../observables/ObservableFactory.kt | 35 ++ .../PendingResultObservableOnSubscribe.java | 33 -- .../PendingResultObservableOnSubscribe.kt | 29 + .../observables/StatusException.java | 16 - .../observables/StatusException.kt | 6 + .../observables/TaskResultMaybeOnSubscribe.kt | 14 +- .../TaskResultObservableOnSubscribe.kt | 32 -- .../GoogleAPIConnectionException.kt | 8 + .../GoogleAPIConnectionSuspendedException.kt | 4 + .../LocationUpdatesObservableOnSubscribe.java | 8 +- sample/build.gradle | 4 +- .../sample/GeofenceActivity.java | 2 - .../sample/MainActivity.java | 39 +- .../sample/PlacesActivity.kt | 24 +- .../sample/PlacesResultActivity.kt | 23 +- 41 files changed, 1019 insertions(+), 1219 deletions(-) delete mode 100644 android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/ReactiveLocationProvider.java create mode 100644 android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/ReactiveLocationProvider.kt delete mode 100644 android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/ReactiveLocationProviderConfiguration.java create mode 100644 android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/ReactiveLocationProviderConfiguration.kt create mode 100644 android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/ext/TasksExt.kt delete mode 100644 android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/BaseLocationObservableOnSubscribe.java create mode 100644 android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/BaseLocationObservableOnSubscribe.kt delete mode 100644 android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/BaseMaybeOnSubscribe.java create mode 100644 android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/BaseMaybeOnSubscribe.kt delete mode 100644 android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/BaseObservableOnSubscribe.java create mode 100644 android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/BaseObservableOnSubscribe.kt delete mode 100644 android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/GoogleAPIClientMaybeOnSubscribe.java create mode 100644 android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/GoogleAPIClientMaybeOnSubscribe.kt delete mode 100644 android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/GoogleAPIClientObservableOnSubscribe.java delete mode 100644 android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/GoogleAPIConnectionException.java delete mode 100644 android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/GoogleAPIConnectionSuspendedException.java delete mode 100644 android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/MaybeContext.java create mode 100644 android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/MaybeContext.kt delete mode 100644 android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/MaybeFactory.java create mode 100644 android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/MaybeFactory.kt delete mode 100644 android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/ObservableContext.java create mode 100644 android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/ObservableContext.kt delete mode 100644 android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/ObservableEmitterWrapper.java create mode 100644 android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/ObservableEmitterWrapper.kt delete mode 100644 android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/ObservableFactory.java create mode 100644 android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/ObservableFactory.kt delete mode 100644 android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/PendingResultObservableOnSubscribe.java create mode 100644 android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/PendingResultObservableOnSubscribe.kt delete mode 100644 android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/StatusException.java create mode 100644 android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/StatusException.kt delete mode 100644 android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/TaskResultObservableOnSubscribe.kt create mode 100644 android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/exceptions/GoogleAPIConnectionException.kt create mode 100644 android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/exceptions/GoogleAPIConnectionSuspendedException.kt diff --git a/android-reactive-location/build.gradle b/android-reactive-location/build.gradle index c3e26fa8..5b84c690 100644 --- a/android-reactive-location/build.gradle +++ b/android-reactive-location/build.gradle @@ -27,7 +27,7 @@ dependencies { implementation ('com.google.android.gms:play-services-location:17.0.0'){ exclude group: 'com.google.android.gms', module: 'play-services-places' } - implementation 'com.google.android.libraries.places:places-compat:2.3.0' + implementation 'com.google.android.libraries.places:places:2.3.0' implementation 'io.reactivex.rxjava2:rxjava:2.2.19' } diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/ReactiveLocationProvider.java b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/ReactiveLocationProvider.java deleted file mode 100644 index fbbd6291..00000000 --- a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/ReactiveLocationProvider.java +++ /dev/null @@ -1,496 +0,0 @@ -package pl.charmas.android.reactivelocation2; - -import android.app.PendingIntent; -import android.content.Context; -import android.location.Address; -import android.location.Location; -import android.os.Handler; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.annotation.RequiresPermission; -import com.google.android.gms.common.api.Api; -import com.google.android.gms.common.api.GoogleApiClient; -import com.google.android.gms.common.api.PendingResult; -import com.google.android.gms.common.api.Result; -import com.google.android.gms.common.api.Status; -import com.google.android.gms.location.ActivityRecognitionResult; -import com.google.android.gms.location.GeofencingRequest; -import com.google.android.gms.location.LocationRequest; -import com.google.android.gms.location.LocationServices; -import com.google.android.gms.location.LocationSettingsRequest; -import com.google.android.gms.location.LocationSettingsResult; -import com.google.android.gms.location.places.AutocompleteFilter; -import com.google.android.gms.location.places.AutocompletePredictionBuffer; -import com.google.android.gms.location.places.PlaceBuffer; -import com.google.android.gms.location.places.PlaceFilter; -import com.google.android.gms.location.places.PlaceLikelihoodBuffer; -import com.google.android.gms.location.places.PlacePhotoMetadata; -import com.google.android.gms.location.places.PlacePhotoMetadataResult; -import com.google.android.gms.location.places.PlacePhotoResult; -import com.google.android.gms.location.places.Places; -import com.google.android.gms.maps.model.LatLngBounds; -import com.google.android.gms.tasks.Task; -import com.google.android.libraries.places.compat.AutocompletePredictionBufferResponse; -import io.reactivex.Maybe; -import io.reactivex.Observable; -import io.reactivex.functions.Function; -import pl.charmas.android.reactivelocation2.observables.GoogleAPIClientMaybeOnSubscribe; -import pl.charmas.android.reactivelocation2.observables.GoogleAPIClientObservableOnSubscribe; -import pl.charmas.android.reactivelocation2.observables.MaybeContext; -import pl.charmas.android.reactivelocation2.observables.MaybeFactory; -import pl.charmas.android.reactivelocation2.observables.ObservableContext; -import pl.charmas.android.reactivelocation2.observables.ObservableFactory; -import pl.charmas.android.reactivelocation2.observables.PendingResultObservableOnSubscribe; -import pl.charmas.android.reactivelocation2.observables.TaskResultMaybeOnSubscribe; -import pl.charmas.android.reactivelocation2.observables.activity.ActivityUpdatesObservableOnSubscribe; -import pl.charmas.android.reactivelocation2.observables.geocode.GeocodeObservable; -import pl.charmas.android.reactivelocation2.observables.geocode.ReverseGeocodeObservable; -import pl.charmas.android.reactivelocation2.observables.geofence.AddGeofenceObservableOnSubscribe; -import pl.charmas.android.reactivelocation2.observables.geofence.RemoveGeofenceObservableOnSubscribe; -import pl.charmas.android.reactivelocation2.observables.location.AddLocationIntentUpdatesObservableOnSubscribe; -import pl.charmas.android.reactivelocation2.observables.location.LastKnownLocationObservableOnSubscribe; -import pl.charmas.android.reactivelocation2.observables.location.LocationUpdatesObservableOnSubscribe; -import pl.charmas.android.reactivelocation2.observables.location.MockLocationObservableOnSubscribe; -import pl.charmas.android.reactivelocation2.observables.location.RemoveLocationIntentUpdatesObservableOnSubscribe; - -import java.util.List; -import java.util.Locale; - - -/** - * Factory of observables that can manipulate location - * delivered by Google Play Services. - */ -public class ReactiveLocationProvider { - private final ObservableContext ctxObservable; - private final MaybeContext ctxMaybe; - private final ObservableFactory factoryObservable; - private final MaybeFactory factoryMaybe; - - /** - * Creates location provider instance with default configuration. - * - * @param context preferably application context - */ - public ReactiveLocationProvider(Context context) { - this(context, ReactiveLocationProviderConfiguration.builder().build()); - } - - /** - * Create location provider with given {@link ReactiveLocationProviderConfiguration}. - * - * @param context preferably application context - * @param configuration configuration instance - */ - public ReactiveLocationProvider(Context context, ReactiveLocationProviderConfiguration configuration) { - this.ctxObservable = new ObservableContext(context, configuration); - this.ctxMaybe = new MaybeContext(context, configuration); - this.factoryObservable = new ObservableFactory(this.ctxObservable); - this.factoryMaybe = new MaybeFactory(this.ctxMaybe); - } - - /** - * Creates location provider with custom handler in which all GooglePlayServices callbacks are called. - * - * @param ctxObservable preferably application context - * @param handler on which all GooglePlayServices callbacks are called - * @see com.google.android.gms.common.api.GoogleApiClient.Builder#setHandler(android.os.Handler) - * @deprecated please use {@link ReactiveLocationProvider#ReactiveLocationProvider(Context, ReactiveLocationProviderConfiguration)} - */ - @Deprecated - public ReactiveLocationProvider(Context ctxObservable, Handler handler) { - this(ctxObservable, ReactiveLocationProviderConfiguration.builder().setCustomCallbackHandler(handler).build()); - } - - /** - * Creates observable that obtains last known location and than completes. - * Delivered location is never null - when it is unavailable Observable completes without emitting - * any value. - *

- * Observable can report {@link pl.charmas.android.reactivelocation2.observables.GoogleAPIConnectionException} - * when there are trouble connecting with Google Play Services and other exceptions that - * can be thrown on {@link com.google.android.gms.location.FusedLocationProviderApi#getLastLocation(com.google.android.gms.common.api.GoogleApiClient)}. - * Everything is delivered by {@link io.reactivex.Observer#onError(Throwable)}. - * - * @return observable that serves last know location - */ - @RequiresPermission( - anyOf = {"android.permission.ACCESS_COARSE_LOCATION", "android.permission.ACCESS_FINE_LOCATION"} - ) - public Observable getLastKnownLocation() { - return LastKnownLocationObservableOnSubscribe.createObservable(ctxObservable, factoryObservable); - } - - /** - * Creates observable that allows to observe infinite stream of location updates. - * To stop the stream you have to unsubscribe from observable - location updates are - * then disconnected. - *

- * Observable can report {@link pl.charmas.android.reactivelocation2.observables.GoogleAPIConnectionException} - * when there are trouble connecting with Google Play Services and other exceptions that - * can be thrown on {@link com.google.android.gms.location.FusedLocationProviderApi#requestLocationUpdates(com.google.android.gms.common.api.GoogleApiClient, com.google.android.gms.location.LocationRequest, com.google.android.gms.location.LocationListener)}. - * Everything is delivered by {@link io.reactivex.Observer#onError(Throwable)}. - * - * @param locationRequest request object with info about what kind of location you need - * @return observable that serves infinite stream of location updates - */ - @RequiresPermission( - anyOf = {"android.permission.ACCESS_COARSE_LOCATION", "android.permission.ACCESS_FINE_LOCATION"} - ) - public Observable getUpdatedLocation(LocationRequest locationRequest) { - return LocationUpdatesObservableOnSubscribe.createObservable(ctxObservable, factoryObservable, locationRequest); - } - - /** - * Returns an observable which activates mock location mode when subscribed to, using the - * supplied observable as a source of mock locations. Mock locations will replace normal - * location information for all users of the FusedLocationProvider API on the device while this - * observable is subscribed to. - *

- * To use this method, mock locations must be enabled in developer options and your application - * must hold the android.permission.ACCESS_MOCK_LOCATION permission, or a {@link java.lang.SecurityException} - * will be thrown. - *

- * All statuses that are not successful will be reported as {@link pl.charmas.android.reactivelocation2.observables.StatusException}. - *

- * Every exception is delivered by {@link io.reactivex.Observer#onError(Throwable)}. - * - * @param sourceLocationObservable observable that emits {@link android.location.Location} instances suitable to use as mock locations - * @return observable that emits {@link com.google.android.gms.common.api.Status} - */ - @RequiresPermission( - allOf = {"android.permission.ACCESS_COARSE_LOCATION", - "android.permission.ACCESS_MOCK_LOCATION"} - ) - public Observable mockLocation(Observable sourceLocationObservable) { - return MockLocationObservableOnSubscribe.createObservable(ctxObservable, factoryObservable, sourceLocationObservable); - } - - /** - * Creates an observable that adds a {@link android.app.PendingIntent} as a location listener. - *

- * This invokes {@link com.google.android.gms.location.FusedLocationProviderApi#requestLocationUpdates(com.google.android.gms.common.api.GoogleApiClient, com.google.android.gms.location.LocationRequest, android.app.PendingIntent)}. - *

- * When location updates are no longer required, a call to {@link #removeLocationUpdates(android.app.PendingIntent)} - * should be made. - *

- * In case of unsuccessful status {@link pl.charmas.android.reactivelocation2.observables.StatusException} is delivered. - * - * @param locationRequest request object with info about what kind of location you need - * @param intent PendingIntent that will be called with location updates - * @return observable that adds the request and PendingIntent - */ - @RequiresPermission( - anyOf = {"android.permission.ACCESS_COARSE_LOCATION", "android.permission.ACCESS_FINE_LOCATION"} - ) - public Observable requestLocationUpdates(LocationRequest locationRequest, PendingIntent intent) { - return AddLocationIntentUpdatesObservableOnSubscribe.createObservable(ctxObservable, factoryObservable, locationRequest, intent); - } - - /** - * Observable that can be used to remove {@link android.app.PendingIntent} location updates. - *

- * In case of unsuccessful status {@link pl.charmas.android.reactivelocation2.observables.StatusException} is delivered. - * - * @param intent PendingIntent to remove location updates for - * @return observable that removes the PendingIntent - */ - public Observable removeLocationUpdates(PendingIntent intent) { - return RemoveLocationIntentUpdatesObservableOnSubscribe.createObservable(ctxObservable, factoryObservable, intent); - } - - /** - * Creates observable that translates latitude and longitude to list of possible addresses using - * included Geocoder class. In case geocoder fails with IOException("Service not Available") fallback - * decoder is used using google web api. You should subscribe for this observable on I/O thread. - * The stream finishes after address list is available. - * - * @param lat latitude - * @param lng longitude - * @param maxResults maximal number of results you are interested in - * @return observable that serves list of address based on location - */ - public Observable> getReverseGeocodeObservable(double lat, double lng, int maxResults) { - return ReverseGeocodeObservable.createObservable(ctxObservable.getContext(), factoryObservable, Locale.getDefault(), lat, lng, maxResults); - } - - /** - * Creates observable that translates latitude and longitude to list of possible addresses using - * included Geocoder class. In case geocoder fails with IOException("Service not Available") fallback - * decoder is used using google web api. You should subscribe for this observable on I/O thread. - * The stream finishes after address list is available. - * - * @param locale locale for address language - * @param lat latitude - * @param lng longitude - * @param maxResults maximal number of results you are interested in - * @return observable that serves list of address based on location - */ - public Observable> getReverseGeocodeObservable(Locale locale, double lat, double lng, int maxResults) { - return ReverseGeocodeObservable.createObservable(ctxObservable.getContext(), factoryObservable, locale, lat, lng, maxResults); - } - - /** - * Creates observable that translates a street address or other description into a list of - * possible addresses using included Geocoder class. You should subscribe for this - * observable on I/O thread. - * The stream finishes after address list is available. - * - * @param locationName a user-supplied description of a location - * @param maxResults max number of results you are interested in - * @return observable that serves list of address based on location name - */ - public Observable> getGeocodeObservable(String locationName, int maxResults) { - return getGeocodeObservable(locationName, maxResults, null); - } - - /** - * Creates geocoder with default Locale. - * - * @see ReactiveLocationProvider#getGeocodeObservable(String, int, LatLngBounds, Locale) - */ - public Observable> getGeocodeObservable(String locationName, int maxResults, LatLngBounds bounds) { - return getGeocodeObservable(locationName, maxResults, bounds, null); - } - - /** - * Creates observable that translates a street address or other description into a list of - * possible addresses using included Geocoder class. You should subscribe for this - * observable on I/O thread. - * The stream finishes after address list is available. - *

- * You may specify a bounding box for the search results. - * - * @param locationName a user-supplied description of a location - * @param maxResults max number of results you are interested in - * @param bounds restricts the results to geographical bounds. May be null - * @param locale locale passed to geocoder - * @return observable that serves list of address based on location name - */ - public Observable> getGeocodeObservable(String locationName, int maxResults, LatLngBounds bounds, Locale locale) { - return GeocodeObservable.createObservable(ctxObservable.getContext(), factoryObservable, locationName, maxResults, bounds, locale); - } - - /** - * Creates observable that adds request and completes when the action is done. - *

- * Observable can report {@link pl.charmas.android.reactivelocation2.observables.GoogleAPIConnectionException} - * when there are trouble connecting with Google Play Services. - *

- * In case of unsuccessful status {@link pl.charmas.android.reactivelocation2.observables.StatusException} is delivered. - *

- * Other exceptions will be reported that can be thrown on {@link com.google.android.gms.location.GeofencingApi#addGeofences(com.google.android.gms.common.api.GoogleApiClient, com.google.android.gms.location.GeofencingRequest, android.app.PendingIntent)} - * - * @param geofenceTransitionPendingIntent pending intent to register on geofence transition - * @param request list of request to add - * @return observable that adds request - */ - @RequiresPermission("android.permission.ACCESS_FINE_LOCATION") - public Observable addGeofences(PendingIntent geofenceTransitionPendingIntent, GeofencingRequest request) { - return AddGeofenceObservableOnSubscribe.createObservable(ctxObservable, factoryObservable, request, geofenceTransitionPendingIntent); - } - - /** - * Observable that can be used to remove geofences from LocationClient. - *

- * In case of unsuccessful status {@link pl.charmas.android.reactivelocation2.observables.StatusException} is delivered. - *

- * Other exceptions will be reported that can be thrown on {@link com.google.android.gms.location.GeofencingApi#removeGeofences(com.google.android.gms.common.api.GoogleApiClient, android.app.PendingIntent)}. - *

- * Every exception is delivered by {@link io.reactivex.Observer#onError(Throwable)}. - * - * @param pendingIntent key of registered geofences - * @return observable that removed geofences - */ - public Observable removeGeofences(PendingIntent pendingIntent) { - return RemoveGeofenceObservableOnSubscribe.createObservable(ctxObservable, factoryObservable, pendingIntent); - } - - /** - * Observable that can be used to remove geofences from LocationClient. - *

- * In case of unsuccessful status {@link pl.charmas.android.reactivelocation2.observables.StatusException} is delivered. - *

- * Other exceptions will be reported that can be thrown on {@link com.google.android.gms.location.GeofencingApi#removeGeofences(com.google.android.gms.common.api.GoogleApiClient, java.util.List)}. - *

- * Every exception is delivered by {@link io.reactivex.Observer#onError(Throwable)}. - * - * @param requestIds geofences to remove - * @return observable that removed geofences - */ - public Observable removeGeofences(List requestIds) { - return RemoveGeofenceObservableOnSubscribe.createObservable(ctxObservable, factoryObservable, requestIds); - } - - - /** - * Observable that can be used to observe activity provided by Actity Recognition mechanism. - * - * @param detectIntervalMiliseconds detecion interval - * @return observable that provides activity recognition - */ - public Observable getDetectedActivity(int detectIntervalMiliseconds) { - return ActivityUpdatesObservableOnSubscribe.createObservable(ctxObservable, factoryObservable, detectIntervalMiliseconds); - } - - /** - * Observable that can be used to check settings state for given location request. - * - * @param locationRequest location request - * @return observable that emits check result of location settings - * @see com.google.android.gms.location.SettingsApi - */ - public Observable checkLocationSettings(final LocationSettingsRequest locationRequest) { - return getGoogleApiClientObservable(LocationServices.API) - .flatMap(new Function>() { - @Override - public Observable apply(GoogleApiClient googleApiClient) { - return fromPendingResult(LocationServices.SettingsApi.checkLocationSettings(googleApiClient, locationRequest)); - } - }); - } - - /** - * Returns observable that fetches current place from Places API. To flatmap and auto release - * buffer to {@link com.google.android.gms.location.places.PlaceLikelihood} observable use - * {@link DataBufferObservable}. - * - * @param placeFilter filter - * @return observable that emits current places buffer and completes - */ - public Observable getCurrentPlace(@Nullable final PlaceFilter placeFilter) { - return getGoogleApiClientObservable(Places.PLACE_DETECTION_API, Places.GEO_DATA_API) - .flatMap(new Function>() { - @Override - public Observable apply(GoogleApiClient api) { - return fromPendingResult(Places.PlaceDetectionApi.getCurrentPlace(api, placeFilter)); - } - }); - } - - /** - * Returns observable that fetches a place from the Places API using the place ID. - * - * @param placeId id for place - * @return observable that emits places buffer and completes - * - * @deprecated use {@link ReactiveLocationProvider#getPlaceCompatById(java.lang.String)} - */ - @Deprecated - public Observable getPlaceById(@NonNull final String placeId) { - return getGoogleApiClientObservable(Places.PLACE_DETECTION_API, Places.GEO_DATA_API) - .flatMap( api -> fromPendingResult(Places.GeoDataApi.getPlaceById(api, placeId))); - } - - /** - * Returns observable that fetches a place from the Places API using the place ID. - * - * @param placeId id for place - * @return observable that emits places buffer and completes - */ - public Maybe getPlaceCompatById(@NonNull final String placeId) { - return getGoogleApiClientMaybe(Places.PLACE_DETECTION_API, Places.GEO_DATA_API) - .flatMap( api -> maybeTaskResult(com.google.android.libraries.places.compat.Places.getGeoDataClient(this.ctxObservable.getContext()).getPlaceById(placeId))); - } - - /** - * Returns observable that fetches autocomplete predictions from Places API. To flatmap and autorelease - * {@link com.google.android.gms.location.places.AutocompletePredictionBuffer} you can use - * {@link DataBufferObservable}. - * - * @param query search query - * @param bounds bounds where to fetch suggestions from - * @param filter filter - * @return observable with suggestions buffer and completes - * - * @deprecated use {@link ReactiveLocationProvider#getPlaceCompatAutocompletePredictions(java.lang.String, com.google.android.gms.maps.model.LatLngBounds, com.google.android.libraries.places.compat.AutocompleteFilter)} - */ - @Deprecated - public Observable getPlaceAutocompletePredictions(final String query, final LatLngBounds bounds, final AutocompleteFilter filter) { - return getGoogleApiClientObservable(Places.PLACE_DETECTION_API, Places.GEO_DATA_API) - .flatMap(api -> fromPendingResult(Places.GeoDataApi.getAutocompletePredictions(api, query, bounds, filter))); - } - - /** - * Returns observable that fetches autocomplete predictions from Places API. To flatmap and autorelease - * {@link com.google.android.libraries.places.compat.AutocompletePredictionBufferResponse} you can use - * {@link DataBufferObservable}. - * - * @param query search query - * @param bounds bounds where to fetch suggestions from - * @param filter filter - * @return observable with suggestions buffer and completes - */ - public Maybe getPlaceCompatAutocompletePredictions(final String query, final LatLngBounds bounds, final com.google.android.libraries.places.compat.AutocompleteFilter filter) { - return getGoogleApiClientMaybe(Places.PLACE_DETECTION_API, Places.GEO_DATA_API) - .flatMap(api -> maybeTaskResult(com.google.android.libraries.places.compat.Places.getGeoDataClient(this.ctxObservable.getContext()).getAutocompletePredictions(query, bounds, filter))); - } - - /** - * Returns observable that fetches photo metadata from the Places API using the place ID. - * - * @param placeId id for place - * @return observable that emits metadata buffer and completes - */ - public Observable getPhotoMetadataById(final String placeId) { - return getGoogleApiClientObservable(Places.PLACE_DETECTION_API, Places.GEO_DATA_API) - .flatMap(new Function>() { - @Override - public Observable apply(GoogleApiClient api) { - return fromPendingResult(Places.GeoDataApi.getPlacePhotos(api, placeId)); - } - }); - } - - /** - * Returns observable that fetches a placePhotoMetadata from the Places API using the place placePhotoMetadata metadata. - * Use after fetching the place placePhotoMetadata metadata with {@link ReactiveLocationProvider#getPhotoMetadataById(String)} - * - * @param placePhotoMetadata the place photo meta data - * @return observable that emits the photo result and completes - */ - public Observable getPhotoForMetadata(final PlacePhotoMetadata placePhotoMetadata) { - return getGoogleApiClientObservable(Places.PLACE_DETECTION_API, Places.GEO_DATA_API) - .flatMap(new Function>() { - @Override - public Observable apply(GoogleApiClient api) { - return fromPendingResult(placePhotoMetadata.getPhoto(api)); - } - }); - } - - /** - * Observable that emits {@link com.google.android.gms.common.api.GoogleApiClient} object after connection. - * In case of error {@link pl.charmas.android.reactivelocation2.observables.GoogleAPIConnectionException} is emmited. - * When connection to Google Play Services is suspended {@link pl.charmas.android.reactivelocation2.observables.GoogleAPIConnectionSuspendedException} - * is emitted as error. - * Do not disconnect from apis client manually - just unsubscribe. - * - * @param apis collection of apis to connect to - * @return observable that emits apis client after successful connection - */ - public Observable getGoogleApiClientObservable(Api... apis) { - //noinspection unchecked - return GoogleAPIClientObservableOnSubscribe.create(ctxObservable, factoryObservable, apis); - } - - public Maybe getGoogleApiClientMaybe(Api... apis) { - //noinspection unchecked - return GoogleAPIClientMaybeOnSubscribe.create(ctxMaybe, factoryMaybe, apis); - } - - /** - * Util method that wraps {@link com.google.android.gms.common.api.PendingResult} in Observable. - * - * @param result pending result to wrap - * @param parameter type of result - * @return observable that emits pending result and completes - */ - public static Observable fromPendingResult(PendingResult result) { - return Observable.create(new PendingResultObservableOnSubscribe<>(result)); - } - - public static Maybe maybeTaskResult(Task result) { - return Maybe.create(new TaskResultMaybeOnSubscribe<>(result)); - } -} diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/ReactiveLocationProvider.kt b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/ReactiveLocationProvider.kt new file mode 100644 index 00000000..679fb8b6 --- /dev/null +++ b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/ReactiveLocationProvider.kt @@ -0,0 +1,466 @@ +package pl.charmas.android.reactivelocation2 + +import android.app.PendingIntent +import android.content.Context +import android.graphics.Bitmap +import android.location.Address +import android.location.Location +import androidx.annotation.IntRange +import androidx.annotation.RequiresPermission +import com.google.android.gms.common.api.Api +import com.google.android.gms.common.api.GoogleApiClient +import com.google.android.gms.common.api.Status +import com.google.android.gms.location.ActivityRecognitionResult +import com.google.android.gms.location.GeofencingRequest +import com.google.android.gms.location.LocationRequest +import com.google.android.gms.location.LocationServices +import com.google.android.gms.location.LocationSettingsRequest +import com.google.android.gms.location.LocationSettingsResult +import com.google.android.gms.maps.model.LatLngBounds +import com.google.android.libraries.places.api.Places +import com.google.android.libraries.places.api.model.AutocompletePrediction +import com.google.android.libraries.places.api.model.PhotoMetadata +import com.google.android.libraries.places.api.model.Place +import com.google.android.libraries.places.api.net.FetchPhotoRequest +import com.google.android.libraries.places.api.net.FetchPlaceRequest +import com.google.android.libraries.places.api.net.FetchPlaceResponse +import com.google.android.libraries.places.api.net.FindAutocompletePredictionsRequest +import com.google.android.libraries.places.api.net.FindAutocompletePredictionsResponse +import com.google.android.libraries.places.api.net.FindCurrentPlaceRequest +import com.google.android.libraries.places.api.net.FindCurrentPlaceResponse +import io.reactivex.Maybe +import io.reactivex.Observable +import pl.charmas.android.reactivelocation2.ext.toMaybe +import pl.charmas.android.reactivelocation2.ext.toObservable +import pl.charmas.android.reactivelocation2.observables.GoogleAPIClientMaybeOnSubscribe +import pl.charmas.android.reactivelocation2.observables.MaybeContext +import pl.charmas.android.reactivelocation2.observables.MaybeFactory +import pl.charmas.android.reactivelocation2.observables.ObservableContext +import pl.charmas.android.reactivelocation2.observables.ObservableFactory +import pl.charmas.android.reactivelocation2.observables.activity.ActivityUpdatesObservableOnSubscribe +import pl.charmas.android.reactivelocation2.observables.geocode.GeocodeObservable +import pl.charmas.android.reactivelocation2.observables.geocode.ReverseGeocodeObservable +import pl.charmas.android.reactivelocation2.observables.geofence.AddGeofenceObservableOnSubscribe +import pl.charmas.android.reactivelocation2.observables.geofence.RemoveGeofenceObservableOnSubscribe +import pl.charmas.android.reactivelocation2.observables.location.AddLocationIntentUpdatesObservableOnSubscribe +import pl.charmas.android.reactivelocation2.observables.location.LastKnownLocationObservableOnSubscribe +import pl.charmas.android.reactivelocation2.observables.location.LocationUpdatesObservableOnSubscribe +import pl.charmas.android.reactivelocation2.observables.location.MockLocationObservableOnSubscribe +import pl.charmas.android.reactivelocation2.observables.location.RemoveLocationIntentUpdatesObservableOnSubscribe +import java.util.Locale + +/** + * Factory of observables that can manipulate location + * delivered by Google Play Services. + */ +class ReactiveLocationProvider +/** + * Create location provider with default configuration or with given [ReactiveLocationProviderConfiguration] + * + * @param context preferably application context + * @param configuration configuration instance + */ +@JvmOverloads +constructor( + context: Context, + configuration: ReactiveLocationProviderConfiguration = ReactiveLocationProviderConfiguration.builder() + .build() +) { + private val ctxObservable: ObservableContext = ObservableContext(context, configuration) + private val ctxMaybe: MaybeContext = MaybeContext(context, configuration) + private val factoryObservable: ObservableFactory = ObservableFactory(ctxObservable) + private val factoryMaybe: MaybeFactory = MaybeFactory(ctxMaybe) + + /** + * Creates observable that obtains last known location and than completes. + * Delivered location is never null - when it is unavailable Observable completes without emitting + * any value. + *

+ * Observable can report {@link pl.charmas.android.reactivelocation2.observables.exceptions.GoogleAPIConnectionException} + * when there are trouble connecting with Google Play Services and other exceptions that + * can be thrown on {@link com.google.android.gms.location.FusedLocationProviderApi#getLastLocation(com.google.android.gms.common.api.GoogleApiClient)}. + * Everything is delivered by {@link io.reactivex.Observer#onError(Throwable)}. + * + * @return observable that serves last know location + */ + @get:RequiresPermission(anyOf = ["android.permission.ACCESS_COARSE_LOCATION", "android.permission.ACCESS_FINE_LOCATION"]) + val lastKnownLocation: Observable + get() = LastKnownLocationObservableOnSubscribe.createObservable( + ctxObservable, + factoryObservable + ) + + /** + * Creates observable that allows to observe infinite stream of location updates. + * To stop the stream you have to unsubscribe from observable - location updates are + * then disconnected. + *

+ * Observable can report {@link pl.charmas.android.reactivelocation2.observables.exceptions.GoogleAPIConnectionException} + * when there are trouble connecting with Google Play Services and other exceptions that + * can be thrown on {@link com.google.android.gms.location.FusedLocationProviderApi#requestLocationUpdates(com.google.android.gms.common.api.GoogleApiClient, com.google.android.gms.location.LocationRequest, com.google.android.gms.location.LocationListener)}. + * Everything is delivered by {@link io.reactivex.Observer#onError(Throwable)}. + * + * @param locationRequest request object with info about what kind of location you need + * @return observable that serves infinite stream of location updates + */ + @RequiresPermission(anyOf = ["android.permission.ACCESS_COARSE_LOCATION", "android.permission.ACCESS_FINE_LOCATION"]) + fun getUpdatedLocation(locationRequest: LocationRequest?): Observable { + return LocationUpdatesObservableOnSubscribe.createObservable( + ctxObservable, + factoryObservable, + locationRequest + ) + } + + /** + * Returns an observable which activates mock location mode when subscribed to, using the + * supplied observable as a source of mock locations. Mock locations will replace normal + * location information for all users of the FusedLocationProvider API on the device while this + * observable is subscribed to. + *

+ * To use this method, mock locations must be enabled in developer options and your application + * must hold the android.permission.ACCESS_MOCK_LOCATION permission, or a {@link java.lang.SecurityException} + * will be thrown. + *

+ * All statuses that are not successful will be reported as {@link pl.charmas.android.reactivelocation2.observables.StatusException}. + *

+ * Every exception is delivered by {@link io.reactivex.Observer#onError(Throwable)}. + * + * @param sourceLocationObservable observable that emits {@link android.location.Location} instances suitable to use as mock locations + * @return observable that emits {@link com.google.android.gms.common.api.Status} + */ + @RequiresPermission(allOf = ["android.permission.ACCESS_COARSE_LOCATION", "android.permission.ACCESS_MOCK_LOCATION"]) + fun mockLocation(sourceLocationObservable: Observable?): Observable { + return MockLocationObservableOnSubscribe.createObservable( + ctxObservable, + factoryObservable, + sourceLocationObservable + ) + } + + /** + * Creates an observable that adds a {@link android.app.PendingIntent} as a location listener. + *

+ * This invokes {@link com.google.android.gms.location.FusedLocationProviderApi#requestLocationUpdates(com.google.android.gms.common.api.GoogleApiClient, com.google.android.gms.location.LocationRequest, android.app.PendingIntent)}. + *

+ * When location updates are no longer required, a call to {@link #removeLocationUpdates(android.app.PendingIntent)} + * should be made. + *

+ * In case of unsuccessful status {@link pl.charmas.android.reactivelocation2.observables.StatusException} is delivered. + * + * @param locationRequest request object with info about what kind of location you need + * @param intent PendingIntent that will be called with location updates + * @return observable that adds the request and PendingIntent + */ + @RequiresPermission(anyOf = ["android.permission.ACCESS_COARSE_LOCATION", "android.permission.ACCESS_FINE_LOCATION"]) + fun requestLocationUpdates( + locationRequest: LocationRequest?, + intent: PendingIntent? + ): Observable { + return AddLocationIntentUpdatesObservableOnSubscribe.createObservable( + ctxObservable, + factoryObservable, + locationRequest, + intent + ) + } + + /** + * Observable that can be used to remove {@link android.app.PendingIntent} location updates. + *

+ * In case of unsuccessful status {@link pl.charmas.android.reactivelocation2.observables.StatusException} is delivered. + * + * @param intent PendingIntent to remove location updates for + * @return observable that removes the PendingIntent + */ + fun removeLocationUpdates(intent: PendingIntent?): Observable { + return RemoveLocationIntentUpdatesObservableOnSubscribe.createObservable( + ctxObservable, + factoryObservable, + intent + ) + } + + /** + * Creates observable that translates latitude and longitude to list of possible addresses using + * included Geocoder class. In case geocoder fails with IOException("Service not Available") fallback + * decoder is used using google web api. You should subscribe for this observable on I/O thread. + * The stream finishes after address list is available. + * + * @param locale locale for address language + * @param lat latitude + * @param lng longitude + * @param maxResults maximal number of results you are interested in + * @return observable that serves list of address based on location + */ + fun getReverseGeocodeObservable( + locale: Locale = Locale.getDefault(), + lat: Double, + lng: Double, + maxResults: Int + ): Observable> { + return ReverseGeocodeObservable.createObservable( + ctxObservable.context, + factoryObservable, + locale, + lat, + lng, + maxResults + ) + } + + /** + * Creates observable that translates a street address or other description into a list of + * possible addresses using included Geocoder class. You should subscribe for this + * observable on I/O thread. + * The stream finishes after address list is available. + *

+ * You may specify a bounding box for the search results. + * + * @param locationName a user-supplied description of a location + * @param maxResults max number of results you are interested in + * @param bounds restricts the results to geographical bounds. May be null + * @param locale locale passed to geocoder + * @return observable that serves list of address based on location name + */ + fun getGeocodeObservable( + locationName: String?, + maxResults: Int, + bounds: LatLngBounds? = null, + locale: Locale? = Locale.getDefault() + ): Observable> { + return GeocodeObservable.createObservable( + ctxObservable.context, + factoryObservable, + locationName, + maxResults, + bounds, + locale + ) + } + + /** + * Creates observable that adds request and completes when the action is done. + *

+ * Observable can report {@link pl.charmas.android.reactivelocation2.observables.exceptions.GoogleAPIConnectionException} + * when there are trouble connecting with Google Play Services. + *

+ * In case of unsuccessful status {@link pl.charmas.android.reactivelocation2.observables.StatusException} is delivered. + *

+ * Other exceptions will be reported that can be thrown on {@link com.google.android.gms.location.GeofencingApi#addGeofences(com.google.android.gms.common.api.GoogleApiClient, com.google.android.gms.location.GeofencingRequest, android.app.PendingIntent)} + * + * @param geofenceTransitionPendingIntent pending intent to register on geofence transition + * @param request list of request to add + * @return observable that adds request + */ + @RequiresPermission("android.permission.ACCESS_FINE_LOCATION") + fun addGeofences( + geofenceTransitionPendingIntent: PendingIntent?, + request: GeofencingRequest? + ): Observable { + return AddGeofenceObservableOnSubscribe.createObservable( + ctxObservable, + factoryObservable, + request, + geofenceTransitionPendingIntent + ) + } + + /** + * Observable that can be used to remove geofences from LocationClient. + *

+ * In case of unsuccessful status {@link pl.charmas.android.reactivelocation2.observables.StatusException} is delivered. + *

+ * Other exceptions will be reported that can be thrown on {@link com.google.android.gms.location.GeofencingApi#removeGeofences(com.google.android.gms.common.api.GoogleApiClient, android.app.PendingIntent)}. + *

+ * Every exception is delivered by {@link io.reactivex.Observer#onError(Throwable)}. + * + * @param pendingIntent key of registered geofences + * @return observable that removed geofences + */ + fun removeGeofences(pendingIntent: PendingIntent?): Observable { + return RemoveGeofenceObservableOnSubscribe.createObservable( + ctxObservable, + factoryObservable, + pendingIntent + ) + } + + /** + * Observable that can be used to remove geofences from LocationClient. + *

+ * In case of unsuccessful status {@link pl.charmas.android.reactivelocation2.observables.StatusException} is delivered. + *

+ * Other exceptions will be reported that can be thrown on {@link com.google.android.gms.location.GeofencingApi#removeGeofences(com.google.android.gms.common.api.GoogleApiClient, java.util.List)}. + *

+ * Every exception is delivered by {@link io.reactivex.Observer#onError(Throwable)}. + * + * @param requestIds geofences to remove + * @return observable that removed geofences + */ + fun removeGeofences(requestIds: List?): Observable { + return RemoveGeofenceObservableOnSubscribe.createObservable( + ctxObservable, + factoryObservable, + requestIds + ) + } + + /** + * Observable that can be used to observe activity provided by Activity Recognition mechanism. + * + * @param detectIntervalMiliseconds detecion interval + * @return observable that provides activity recognition + */ + fun getDetectedActivity(detectIntervalMiliseconds: Int): Observable { + return ActivityUpdatesObservableOnSubscribe.createObservable( + ctxObservable, + factoryObservable, + detectIntervalMiliseconds + ) + } + + /** + * Observable that can be used to check settings state for given location request. + * + * @param locationRequest location request + * @return observable that emits check result of location settings + * @see com.google.android.gms.location.SettingsApi + */ + fun checkLocationSettings(locationRequest: LocationSettingsRequest?): Observable { + return getGoogleApiClientMaybe(LocationServices.API) + .flatMapObservable { googleApiClient -> + LocationServices.SettingsApi.checkLocationSettings( + googleApiClient, + locationRequest + ) + .toObservable() + } + } + + /** + * Returns observable that fetches current place from Places API. To flatmap and auto release + * buffer to {@link com.google.android.gms.location.places.PlaceLikelihood} observable use + * {@link DataBufferObservable}. + * + * @param placeFilter filter + * @return observable that emits current places buffer and completes + */ + @RequiresPermission(allOf = ["android.permission.ACCESS_FINE_LOCATION", "android.permission.ACCESS_WIFI_STATE"]) + fun getCurrentPlace(placeFilter: FindCurrentPlaceRequest): Maybe { + return Places.createClient( + ctxObservable.context + ).findCurrentPlace(placeFilter) + .toMaybe() + } + + /** + * Returns observable that fetches a place from the Places API using the place ID. + * + * @param placeId id for place + * @return observable that emits places buffer and completes + * + * @deprecated use {@link ReactiveLocationProvider#getPlaceCompatById(java.lang.String)} + */ + fun getPlaceById(placeId: String): Maybe { + return Places.createClient(ctxObservable.context) + .fetchPlace( + FetchPlaceRequest.builder( + placeId, + listOf(Place.Field.ID) + ).build() + ) + .toMaybe() + } + + /** + * Returns observable that fetches autocomplete predictions from Places API. To flatmap and autorelease + * {@link com.google.android.gms.location.places.AutocompletePredictionBuffer} you can use + * {@link DataBufferObservable}. + * + * @param query search query + * @param bounds bounds where to fetch suggestions from + * @param filter filter + * @return observable with suggestions buffer and completes + * + * @deprecated use {@link ReactiveLocationProvider#getPlaceCompatAutocompletePredictions(java.lang.String, com.google.android.gms.maps.model.LatLngBounds, com.google.android.libraries.places.compat.AutocompleteFilter)} + */ + fun getPlaceAutocompletePredictions(result: FindAutocompletePredictionsRequest): Maybe> { + return Places.createClient(ctxObservable.context) + .findAutocompletePredictions(result) + .toMaybe() + .map { obj: FindAutocompletePredictionsResponse -> obj.autocompletePredictions } + } + + /** + * Returns observable that fetches photo metadata from the Places API using the place ID. + * + * @param placeId id for place + * @return observable that emits metadata buffer and completes + */ + fun getPhotoMetadataById( + placeId: String, + @IntRange(from = 0L) height: Int, + @IntRange(from = 0L) width: Int + ): Maybe { + return Places.createClient(ctxObservable.context) + .fetchPlace( + FetchPlaceRequest.builder( + placeId, + listOf(Place.Field.PHOTO_METADATAS) + ).build() + ) + .toMaybe() + .flatMap { res -> + val photoMetadata = res.place.photoMetadatas?.firstOrNull() + if (photoMetadata == null) { + Maybe.empty() + } else { + Places.createClient(ctxObservable.context) + .fetchPhoto( + FetchPhotoRequest.builder( + PhotoMetadata.builder(placeId) + .setHeight(height) + .setWidth(width) + .setAttributions(photoMetadata.attributions) + .build() + ) + .build() + ) + .toMaybe() + .map { it.bitmap } + } + } + } + + /** + * Returns observable that fetches a placePhotoMetadata from the Places API using the place placePhotoMetadata metadata. + * Use after fetching the place placePhotoMetadata metadata with [ReactiveLocationProvider.getPhotoMetadataById] + * + * @param placePhotoMetadata the place photo meta data + * @return observable that emits the photo result and completes + */ + fun getPhotoForMetadata(placePhotoMetadata: PhotoMetadata): Maybe { + return Places.createClient(ctxObservable.context) + .fetchPhoto( + FetchPhotoRequest.builder(placePhotoMetadata) + .build() + ) + .toMaybe() + .map { it.bitmap } + } + + /** + * Observable that emits {@link com.google.android.gms.common.api.GoogleApiClient} object after connection. + * In case of error {@link pl.charmas.android.reactivelocation2.observables.exceptions.GoogleAPIConnectionException} is emmited. + * When connection to Google Play Services is suspended {@link pl.charmas.android.reactivelocation2.observables.exceptions.GoogleAPIConnectionSuspendedException} + * is emitted as error. + * Do not disconnect from apis client manually - just unsubscribe. + * + * @param apis collection of apis to connect to + * @return observable that emits apis client after successful connection + */ + fun getGoogleApiClientMaybe(vararg apis: Api): Maybe { + return GoogleAPIClientMaybeOnSubscribe.create(ctxMaybe, factoryMaybe, *apis) + } +} diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/ReactiveLocationProviderConfiguration.java b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/ReactiveLocationProviderConfiguration.java deleted file mode 100644 index dc72428c..00000000 --- a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/ReactiveLocationProviderConfiguration.java +++ /dev/null @@ -1,69 +0,0 @@ -package pl.charmas.android.reactivelocation2; - -import android.os.Handler; -import androidx.annotation.Nullable; - -/** - * Configuration for location provider. Pleas use builder to create an instance. - */ -public class ReactiveLocationProviderConfiguration { - private final Handler customCallbackHandler; - private final boolean retryOnConnectionSuspended; - - private ReactiveLocationProviderConfiguration(Builder builder) { - this.customCallbackHandler = builder.customCallbackHandler; - this.retryOnConnectionSuspended = builder.retryOnConnectionSuspended; - } - - public Handler getCustomCallbackHandler() { - return customCallbackHandler; - } - - public boolean isRetryOnConnectionSuspended() { - return retryOnConnectionSuspended; - } - - public static Builder builder() { - return new Builder(); - } - - public static class Builder { - private Handler customCallbackHandler = null; - private boolean retryOnConnectionSuspended = false; - - /** - * Allows to set custom handler on which all Google Play Services callbacks are called. - *

- * Default: null - * - * @param customCallbackHandler handler instance - * @return builder instance - */ - public Builder setCustomCallbackHandler(@Nullable Handler customCallbackHandler) { - this.customCallbackHandler = customCallbackHandler; - return this; - } - - /** - * Property that allows automatic retries of connection to Google Play Services when it has bean suspended. - *

- * Default: false - * - * @param retryOnConnectionSuspended if should we retry on connection failure - * @return builder instance - */ - public Builder setRetryOnConnectionSuspended(boolean retryOnConnectionSuspended) { - this.retryOnConnectionSuspended = retryOnConnectionSuspended; - return this; - } - - /** - * Builds configuration instance - * - * @return configuration instance - */ - public ReactiveLocationProviderConfiguration build() { - return new ReactiveLocationProviderConfiguration(this); - } - } -} diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/ReactiveLocationProviderConfiguration.kt b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/ReactiveLocationProviderConfiguration.kt new file mode 100644 index 00000000..0af88f26 --- /dev/null +++ b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/ReactiveLocationProviderConfiguration.kt @@ -0,0 +1,67 @@ +package pl.charmas.android.reactivelocation2 + +import android.os.Handler + +/** + * Configuration for location provider. Pleas use builder to create an instance. + */ +class ReactiveLocationProviderConfiguration private constructor(builder: Builder) { + + val customCallbackHandler: Handler? = builder.customCallbackHandler + val isRetryOnConnectionSuspended: Boolean = builder.retryOnConnectionSuspended + + class Builder { + var customCallbackHandler: Handler? = null + private set(value) { + field = value + } + var retryOnConnectionSuspended = false + private set(value) { + field = value + } + + /** + * Allows to set custom handler on which all Google Play Services callbacks are called. + * + * + * Default: null + * + * @param customCallbackHandler handler instance + * @return builder instance + */ + fun setCustomCallbackHandler(customCallbackHandler: Handler?): Builder { + this.customCallbackHandler = customCallbackHandler + return this + } + + /** + * Property that allows automatic retries of connection to Google Play Services when it has bean suspended. + * + * + * Default: false + * + * @param retryOnConnectionSuspended if should we retry on connection failure + * @return builder instance + */ + fun setRetryOnConnectionSuspended(retryOnConnectionSuspended: Boolean): Builder { + this.retryOnConnectionSuspended = retryOnConnectionSuspended + return this + } + + /** + * Builds configuration instance + * + * @return configuration instance + */ + fun build(): ReactiveLocationProviderConfiguration { + return ReactiveLocationProviderConfiguration(this) + } + } + + companion object { + @JvmStatic + fun builder(): Builder { + return Builder() + } + } +} \ No newline at end of file diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/ext/TasksExt.kt b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/ext/TasksExt.kt new file mode 100644 index 00000000..26053ba8 --- /dev/null +++ b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/ext/TasksExt.kt @@ -0,0 +1,17 @@ +package pl.charmas.android.reactivelocation2.ext + +import com.google.android.gms.common.api.PendingResult +import com.google.android.gms.common.api.Result +import com.google.android.gms.tasks.Task +import io.reactivex.Maybe +import io.reactivex.Observable +import pl.charmas.android.reactivelocation2.observables.PendingResultObservableOnSubscribe +import pl.charmas.android.reactivelocation2.observables.TaskResultMaybeOnSubscribe + +fun Task.toMaybe(): Maybe { + return Maybe.create(TaskResultMaybeOnSubscribe(this)) +} + +fun PendingResult.toObservable(): Observable { + return Observable.create(PendingResultObservableOnSubscribe(this)) +} diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/BaseLocationObservableOnSubscribe.java b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/BaseLocationObservableOnSubscribe.java deleted file mode 100644 index 86278213..00000000 --- a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/BaseLocationObservableOnSubscribe.java +++ /dev/null @@ -1,9 +0,0 @@ -package pl.charmas.android.reactivelocation2.observables; - -import com.google.android.gms.location.LocationServices; - -public abstract class BaseLocationObservableOnSubscribe extends BaseObservableOnSubscribe { - protected BaseLocationObservableOnSubscribe(ObservableContext ctx) { - super(ctx, LocationServices.API); - } -} diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/BaseLocationObservableOnSubscribe.kt b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/BaseLocationObservableOnSubscribe.kt new file mode 100644 index 00000000..6a36916d --- /dev/null +++ b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/BaseLocationObservableOnSubscribe.kt @@ -0,0 +1,6 @@ +package pl.charmas.android.reactivelocation2.observables + +import com.google.android.gms.location.LocationServices + +abstract class BaseLocationObservableOnSubscribe protected constructor(ctx: ObservableContext) : + BaseObservableOnSubscribe(ctx, LocationServices.API) \ No newline at end of file diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/BaseMaybeOnSubscribe.java b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/BaseMaybeOnSubscribe.java deleted file mode 100644 index dc807f10..00000000 --- a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/BaseMaybeOnSubscribe.java +++ /dev/null @@ -1,116 +0,0 @@ -package pl.charmas.android.reactivelocation2.observables; - -import android.content.Context; -import android.os.Bundle; -import android.os.Handler; -import androidx.annotation.NonNull; -import com.google.android.gms.common.ConnectionResult; -import com.google.android.gms.common.api.Api; -import com.google.android.gms.common.api.GoogleApiClient; -import io.reactivex.MaybeEmitter; -import io.reactivex.MaybeOnSubscribe; -import io.reactivex.disposables.Disposables; - -import java.util.Arrays; -import java.util.List; - - -public abstract class BaseMaybeOnSubscribe implements MaybeOnSubscribe { - private final Context ctx; - private final Handler handler; - private final List> services; - - @SafeVarargs - protected BaseMaybeOnSubscribe(MaybeContext ctx, Api... services) { - this.ctx = ctx.getContext(); - this.handler = ctx.getHandler(); - this.services = Arrays.asList(services); - } - - @Override - public void subscribe(MaybeEmitter emitter) { - final GoogleApiClient apiClient = createApiClient(emitter); - try { - apiClient.connect(); - } catch (Throwable ex) { - if (!emitter.isDisposed()) { - emitter.onError(ex); - } - } - - emitter.setDisposable(Disposables.fromAction(() -> { - onDisposed(apiClient); - apiClient.disconnect(); - })); - } - - - private GoogleApiClient createApiClient(MaybeEmitter emitter) { - ApiClientConnectionCallbacks apiClientConnectionCallbacks = new ApiClientConnectionCallbacks(emitter); - GoogleApiClient.Builder apiClientBuilder = new GoogleApiClient.Builder(ctx); - - for (Api service : services) { - apiClientBuilder.addApi(service); - } - - apiClientBuilder - .addConnectionCallbacks(apiClientConnectionCallbacks) - .addOnConnectionFailedListener(apiClientConnectionCallbacks); - - if (this.handler != null) { - apiClientBuilder = apiClientBuilder.setHandler(handler); - } - - GoogleApiClient apiClient = apiClientBuilder.build(); - apiClientConnectionCallbacks.setClient(apiClient); - return apiClient; - } - - protected void onDisposed(GoogleApiClient locationClient) { - } - - protected abstract void onGoogleApiClientReady(GoogleApiClient apiClient, MaybeEmitter emitter); - - private class ApiClientConnectionCallbacks implements - GoogleApiClient.ConnectionCallbacks, - GoogleApiClient.OnConnectionFailedListener { - - final private MaybeEmitter emitter; - - private GoogleApiClient apiClient; - - private ApiClientConnectionCallbacks(MaybeEmitter emitter) { - this.emitter = emitter; - } - - @Override - public void onConnected(Bundle bundle) { - try { - onGoogleApiClientReady(apiClient, emitter); - } catch (Throwable ex) { - if (!emitter.isDisposed()) { - emitter.onError(ex); - } - } - } - - @Override - public void onConnectionSuspended(int cause) { - if (!emitter.isDisposed()) { - emitter.onError(new GoogleAPIConnectionSuspendedException(cause)); - } - } - - @Override - public void onConnectionFailed(@NonNull ConnectionResult connectionResult) { - if (!emitter.isDisposed()) { - emitter.onError(new GoogleAPIConnectionException("Error connecting to GoogleApiClient.", - connectionResult)); - } - } - - void setClient(GoogleApiClient client) { - this.apiClient = client; - } - } -} diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/BaseMaybeOnSubscribe.kt b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/BaseMaybeOnSubscribe.kt new file mode 100644 index 00000000..66b0e3fc --- /dev/null +++ b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/BaseMaybeOnSubscribe.kt @@ -0,0 +1,102 @@ +package pl.charmas.android.reactivelocation2.observables + +import android.content.Context +import android.os.Bundle +import android.os.Handler +import com.google.android.gms.common.ConnectionResult +import com.google.android.gms.common.api.Api +import com.google.android.gms.common.api.Api.ApiOptions.NotRequiredOptions +import com.google.android.gms.common.api.GoogleApiClient +import com.google.android.gms.common.api.GoogleApiClient.ConnectionCallbacks +import com.google.android.gms.common.api.GoogleApiClient.OnConnectionFailedListener +import io.reactivex.MaybeEmitter +import io.reactivex.MaybeOnSubscribe +import io.reactivex.disposables.Disposables +import pl.charmas.android.reactivelocation2.observables.exceptions.GoogleAPIConnectionException +import pl.charmas.android.reactivelocation2.observables.exceptions.GoogleAPIConnectionSuspendedException + +abstract class BaseMaybeOnSubscribe @SafeVarargs protected constructor( + ctx: MaybeContext, + vararg services: Api +) : MaybeOnSubscribe { + + private val ctx: Context = ctx.context + private val handler: Handler? = ctx.handler + private val services: List> = listOf(*services) + + override fun subscribe(emitter: MaybeEmitter) { + val apiClient = createApiClient(emitter) + try { + apiClient.connect() + } catch (ex: Throwable) { + if (!emitter.isDisposed) { + emitter.onError(ex) + } + } + emitter.setDisposable(Disposables.fromAction { + onDisposed(apiClient) + apiClient.disconnect() + }) + } + + private fun createApiClient(emitter: MaybeEmitter): GoogleApiClient { + val apiClientConnectionCallbacks = ApiClientConnectionCallbacks(emitter) + + val builder = GoogleApiClient.Builder(ctx) + for (service in services) { + builder.addApi(service) + } + builder + .addConnectionCallbacks(apiClientConnectionCallbacks) + .addOnConnectionFailedListener(apiClientConnectionCallbacks) + if (handler != null) { + builder.setHandler(handler) + } + val apiClient = builder.build() + apiClientConnectionCallbacks.setClient(apiClient) + return apiClient + } + + private fun onDisposed(locationClient: GoogleApiClient?) {} + + protected abstract fun onGoogleApiClientReady( + apiClient: GoogleApiClient, + emitter: MaybeEmitter + ) + + private inner class ApiClientConnectionCallbacks constructor(private val emitter: MaybeEmitter) : + ConnectionCallbacks, OnConnectionFailedListener { + private lateinit var apiClient: GoogleApiClient + + override fun onConnected(bundle: Bundle?) { + try { + onGoogleApiClientReady(apiClient, emitter) + } catch (ex: Throwable) { + if (!emitter.isDisposed) { + emitter.onError(ex) + } + } + } + + override fun onConnectionSuspended(cause: Int) { + if (!emitter.isDisposed) { + emitter.onError(GoogleAPIConnectionSuspendedException(cause)) + } + } + + override fun onConnectionFailed(connectionResult: ConnectionResult) { + if (!emitter.isDisposed) { + emitter.onError( + GoogleAPIConnectionException( + "Error connecting to GoogleApiClient.", + connectionResult + ) + ) + } + } + + fun setClient(client: GoogleApiClient) { + apiClient = client + } + } +} \ No newline at end of file diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/BaseObservableOnSubscribe.java b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/BaseObservableOnSubscribe.java deleted file mode 100644 index e6853a6e..00000000 --- a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/BaseObservableOnSubscribe.java +++ /dev/null @@ -1,120 +0,0 @@ -package pl.charmas.android.reactivelocation2.observables; - -import android.content.Context; -import android.os.Bundle; -import android.os.Handler; -import androidx.annotation.NonNull; -import com.google.android.gms.common.ConnectionResult; -import com.google.android.gms.common.api.Api; -import com.google.android.gms.common.api.GoogleApiClient; -import io.reactivex.ObservableEmitter; -import io.reactivex.ObservableOnSubscribe; -import io.reactivex.disposables.Disposables; -import io.reactivex.functions.Action; - -import java.util.Arrays; -import java.util.List; - - -public abstract class BaseObservableOnSubscribe implements ObservableOnSubscribe { - private final Context ctx; - private final Handler handler; - private final List> services; - - @SafeVarargs - protected BaseObservableOnSubscribe(ObservableContext ctx, Api... services) { - this.ctx = ctx.getContext(); - this.handler = ctx.getHandler(); - this.services = Arrays.asList(services); - } - - @Override - public void subscribe(ObservableEmitter emitter) throws Exception { - final GoogleApiClient apiClient = createApiClient(emitter); - try { - apiClient.connect(); - } catch (Throwable ex) { - if (!emitter.isDisposed()) { - emitter.onError(ex); - } - } - - emitter.setDisposable(Disposables.fromAction(new Action() { - @Override - public void run() throws Exception { - onDisposed(apiClient); - apiClient.disconnect(); - } - })); - } - - - private GoogleApiClient createApiClient(ObservableEmitter emitter) { - ApiClientConnectionCallbacks apiClientConnectionCallbacks = new ApiClientConnectionCallbacks(emitter); - GoogleApiClient.Builder apiClientBuilder = new GoogleApiClient.Builder(ctx); - - for (Api service : services) { - apiClientBuilder = apiClientBuilder.addApi(service); - } - - apiClientBuilder = apiClientBuilder - .addConnectionCallbacks(apiClientConnectionCallbacks) - .addOnConnectionFailedListener(apiClientConnectionCallbacks); - - if (this.handler != null) { - apiClientBuilder = apiClientBuilder.setHandler(handler); - } - - GoogleApiClient apiClient = apiClientBuilder.build(); - apiClientConnectionCallbacks.setClient(apiClient); - return apiClient; - } - - protected void onDisposed(GoogleApiClient locationClient) { - } - - protected abstract void onGoogleApiClientReady(GoogleApiClient apiClient, ObservableEmitter emitter); - - private class ApiClientConnectionCallbacks implements - GoogleApiClient.ConnectionCallbacks, - GoogleApiClient.OnConnectionFailedListener { - - final private ObservableEmitter emitter; - - private GoogleApiClient apiClient; - - private ApiClientConnectionCallbacks(ObservableEmitter emitter) { - this.emitter = emitter; - } - - @Override - public void onConnected(Bundle bundle) { - try { - onGoogleApiClientReady(apiClient, emitter); - } catch (Throwable ex) { - if (!emitter.isDisposed()) { - emitter.onError(ex); - } - } - } - - @Override - public void onConnectionSuspended(int cause) { - if (!emitter.isDisposed()) { - emitter.onError(new GoogleAPIConnectionSuspendedException(cause)); - } - } - - @Override - public void onConnectionFailed(@NonNull ConnectionResult connectionResult) { - if (!emitter.isDisposed()) { - emitter.onError(new GoogleAPIConnectionException("Error connecting to GoogleApiClient.", - connectionResult)); - } - } - - void setClient(GoogleApiClient client) { - this.apiClient = client; - } - } -} diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/BaseObservableOnSubscribe.kt b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/BaseObservableOnSubscribe.kt new file mode 100644 index 00000000..55547e03 --- /dev/null +++ b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/BaseObservableOnSubscribe.kt @@ -0,0 +1,109 @@ +package pl.charmas.android.reactivelocation2.observables + +import android.content.Context +import android.os.Bundle +import android.os.Handler +import com.google.android.gms.common.ConnectionResult +import com.google.android.gms.common.api.Api +import com.google.android.gms.common.api.Api.ApiOptions.NotRequiredOptions +import com.google.android.gms.common.api.GoogleApiClient +import com.google.android.gms.common.api.GoogleApiClient.ConnectionCallbacks +import com.google.android.gms.common.api.GoogleApiClient.OnConnectionFailedListener +import io.reactivex.ObservableEmitter +import io.reactivex.ObservableOnSubscribe +import io.reactivex.disposables.Disposables +import pl.charmas.android.reactivelocation2.observables.exceptions.GoogleAPIConnectionException +import pl.charmas.android.reactivelocation2.observables.exceptions.GoogleAPIConnectionSuspendedException +import java.util.Arrays + +abstract class BaseObservableOnSubscribe @SafeVarargs protected constructor( + ctx: ObservableContext, + vararg services: Api +) : ObservableOnSubscribe { + + private val ctx: Context= ctx.context + private val handler: Handler? = ctx.handler + private val services: List> = listOf(*services) + + override fun subscribe(emitter: ObservableEmitter) { + val apiClient = createApiClient(emitter) + try { + apiClient.connect() + } catch (ex: Throwable) { + if (!emitter.isDisposed) { + emitter.onError(ex) + } + } + emitter.setDisposable(Disposables.fromAction { + onDisposed(apiClient) + apiClient.disconnect() + }) + } + + private fun createApiClient(emitter: ObservableEmitter): GoogleApiClient { + val apiClientConnectionCallbacks = + ApiClientConnectionCallbacks( + emitter + ) + var apiClientBuilder = GoogleApiClient.Builder(ctx) + for (service in services) { + apiClientBuilder = apiClientBuilder.addApi(service) + } + apiClientBuilder = apiClientBuilder + .addConnectionCallbacks(apiClientConnectionCallbacks) + .addOnConnectionFailedListener(apiClientConnectionCallbacks) + if (handler != null) { + apiClientBuilder = apiClientBuilder.setHandler(handler) + } + val apiClient = apiClientBuilder.build() + apiClientConnectionCallbacks.setClient(apiClient) + return apiClient + } + + protected open fun onDisposed(locationClient: GoogleApiClient?) {} + protected abstract fun onGoogleApiClientReady( + apiClient: GoogleApiClient?, + emitter: ObservableEmitter? + ) + + private inner class ApiClientConnectionCallbacks constructor(private val emitter: ObservableEmitter) : + ConnectionCallbacks, OnConnectionFailedListener { + private var apiClient: GoogleApiClient? = null + override fun onConnected(bundle: Bundle?) { + try { + onGoogleApiClientReady(apiClient, emitter) + } catch (ex: Throwable) { + if (!emitter.isDisposed) { + emitter.onError(ex) + } + } + } + + override fun onConnectionSuspended(cause: Int) { + if (!emitter.isDisposed) { + emitter.onError(GoogleAPIConnectionSuspendedException(cause)) + } + } + + override fun onConnectionFailed(connectionResult: ConnectionResult) { + if (!emitter.isDisposed) { + emitter.onError( + GoogleAPIConnectionException( + "Error connecting to GoogleApiClient.", + connectionResult + ) + ) + } + } + + fun setClient(client: GoogleApiClient?) { + apiClient = client + } + } + + init { + this.ctx + handler + this.services + } +} \ No newline at end of file diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/GoogleAPIClientMaybeOnSubscribe.java b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/GoogleAPIClientMaybeOnSubscribe.java deleted file mode 100644 index e26f0f91..00000000 --- a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/GoogleAPIClientMaybeOnSubscribe.java +++ /dev/null @@ -1,25 +0,0 @@ -package pl.charmas.android.reactivelocation2.observables; - -import com.google.android.gms.common.api.Api; -import com.google.android.gms.common.api.GoogleApiClient; -import io.reactivex.Maybe; -import io.reactivex.MaybeEmitter; - -public class GoogleAPIClientMaybeOnSubscribe extends BaseMaybeOnSubscribe { - - @SafeVarargs - public static Maybe create(MaybeContext context, MaybeFactory factory, Api... apis) { - return factory.createMaybe(new GoogleAPIClientMaybeOnSubscribe(context, apis)); - } - - @SafeVarargs - private GoogleAPIClientMaybeOnSubscribe(MaybeContext ctx, Api... apis) { - super(ctx, apis); - } - - @Override - protected void onGoogleApiClientReady(GoogleApiClient apiClient, MaybeEmitter emitter) { - if (emitter.isDisposed()) return; - emitter.onSuccess(apiClient); - } -} diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/GoogleAPIClientMaybeOnSubscribe.kt b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/GoogleAPIClientMaybeOnSubscribe.kt new file mode 100644 index 00000000..b9260e40 --- /dev/null +++ b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/GoogleAPIClientMaybeOnSubscribe.kt @@ -0,0 +1,34 @@ +package pl.charmas.android.reactivelocation2.observables + +import com.google.android.gms.common.api.Api +import com.google.android.gms.common.api.Api.ApiOptions.NotRequiredOptions +import com.google.android.gms.common.api.GoogleApiClient +import io.reactivex.Maybe +import io.reactivex.MaybeEmitter + +class GoogleAPIClientMaybeOnSubscribe @SafeVarargs private constructor( + ctx: MaybeContext, + vararg apis: Api +) : BaseMaybeOnSubscribe(ctx, *apis) { + + override fun onGoogleApiClientReady( + apiClient: GoogleApiClient, + emitter: MaybeEmitter + ) { + if (emitter.isDisposed) return + emitter.onSuccess(apiClient) + } + + companion object { + @SafeVarargs + fun create( + context: MaybeContext, + factory: MaybeFactory, + vararg apis: Api + ): Maybe { + return factory.createMaybe( + GoogleAPIClientMaybeOnSubscribe(context, *apis) + ) + } + } +} \ No newline at end of file diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/GoogleAPIClientObservableOnSubscribe.java b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/GoogleAPIClientObservableOnSubscribe.java deleted file mode 100644 index 2a09d33e..00000000 --- a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/GoogleAPIClientObservableOnSubscribe.java +++ /dev/null @@ -1,26 +0,0 @@ -package pl.charmas.android.reactivelocation2.observables; - -import com.google.android.gms.common.api.Api; -import com.google.android.gms.common.api.GoogleApiClient; - -import io.reactivex.Observable; -import io.reactivex.ObservableEmitter; - -public class GoogleAPIClientObservableOnSubscribe extends BaseObservableOnSubscribe { - - @SafeVarargs - public static Observable create(ObservableContext context, ObservableFactory factory, Api... apis) { - return factory.createObservable(new GoogleAPIClientObservableOnSubscribe(context, apis)); - } - - @SafeVarargs - private GoogleAPIClientObservableOnSubscribe(ObservableContext ctx, Api... apis) { - super(ctx, apis); - } - - @Override - protected void onGoogleApiClientReady(GoogleApiClient apiClient, ObservableEmitter emitter) { - if (emitter.isDisposed()) return; - emitter.onNext(apiClient); - } -} diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/GoogleAPIConnectionException.java b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/GoogleAPIConnectionException.java deleted file mode 100644 index 014070e5..00000000 --- a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/GoogleAPIConnectionException.java +++ /dev/null @@ -1,16 +0,0 @@ -package pl.charmas.android.reactivelocation2.observables; - -import com.google.android.gms.common.ConnectionResult; - -public class GoogleAPIConnectionException extends RuntimeException { - private final ConnectionResult connectionResult; - - GoogleAPIConnectionException(String detailMessage, ConnectionResult connectionResult) { - super(detailMessage); - this.connectionResult = connectionResult; - } - - public ConnectionResult getConnectionResult() { - return connectionResult; - } -} diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/GoogleAPIConnectionSuspendedException.java b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/GoogleAPIConnectionSuspendedException.java deleted file mode 100644 index eeed3412..00000000 --- a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/GoogleAPIConnectionSuspendedException.java +++ /dev/null @@ -1,13 +0,0 @@ -package pl.charmas.android.reactivelocation2.observables; - -public class GoogleAPIConnectionSuspendedException extends RuntimeException { - private final int cause; - - GoogleAPIConnectionSuspendedException(int cause) { - this.cause = cause; - } - - public int getErrorCause() { - return cause; - } -} diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/MaybeContext.java b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/MaybeContext.java deleted file mode 100644 index 2edb1ee2..00000000 --- a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/MaybeContext.java +++ /dev/null @@ -1,29 +0,0 @@ -package pl.charmas.android.reactivelocation2.observables; - -import android.content.Context; -import android.os.Handler; -import pl.charmas.android.reactivelocation2.ReactiveLocationProviderConfiguration; - -public class MaybeContext { - private final Context context; - private final Handler handler; - private final boolean retryOnConnectionSuspended; - - public MaybeContext(Context context, ReactiveLocationProviderConfiguration configuration) { - this.context = context; - this.handler = configuration.getCustomCallbackHandler(); - this.retryOnConnectionSuspended = configuration.isRetryOnConnectionSuspended(); - } - - public Context getContext() { - return context; - } - - Handler getHandler() { - return handler; - } - - boolean isRetryOnConnectionSuspended() { - return retryOnConnectionSuspended; - } -} diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/MaybeContext.kt b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/MaybeContext.kt new file mode 100644 index 00000000..4e7337dc --- /dev/null +++ b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/MaybeContext.kt @@ -0,0 +1,13 @@ +package pl.charmas.android.reactivelocation2.observables + +import android.content.Context +import android.os.Handler +import pl.charmas.android.reactivelocation2.ReactiveLocationProviderConfiguration + +class MaybeContext constructor( + val context: Context, + configuration: ReactiveLocationProviderConfiguration +) { + val handler: Handler? = configuration.customCallbackHandler + val isRetryOnConnectionSuspended: Boolean = configuration.isRetryOnConnectionSuspended +} \ No newline at end of file diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/MaybeFactory.java b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/MaybeFactory.java deleted file mode 100644 index fdb68d1b..00000000 --- a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/MaybeFactory.java +++ /dev/null @@ -1,42 +0,0 @@ -package pl.charmas.android.reactivelocation2.observables; - -import io.reactivex.Maybe; -import io.reactivex.MaybeOnSubscribe; -import io.reactivex.MaybeSource; -import io.reactivex.MaybeTransformer; -import io.reactivex.functions.BiPredicate; - -public class MaybeFactory { - private final MaybeContext context; - - public MaybeFactory(MaybeContext context) { - this.context = context; - } - - public Maybe createMaybe(MaybeOnSubscribe source) { - return Maybe.create(source).compose(new RetryOnConnectionSuspension(context.isRetryOnConnectionSuspended())); - } - - private static class RetryOnConnectionSuspension implements MaybeTransformer { - private final boolean shouldRetry; - - RetryOnConnectionSuspension(boolean shouldRetry) { - this.shouldRetry = shouldRetry; - } - - @Override - public MaybeSource apply(Maybe upstream) { - if (shouldRetry) { - return upstream.retry(new IsConnectionSuspendedException()); - } - return upstream; - } - - private static class IsConnectionSuspendedException implements BiPredicate { - @Override - public boolean test(Integer integer, Throwable throwable) { - return throwable instanceof GoogleAPIConnectionSuspendedException; - } - } - } -} diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/MaybeFactory.kt b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/MaybeFactory.kt new file mode 100644 index 00000000..35bee742 --- /dev/null +++ b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/MaybeFactory.kt @@ -0,0 +1,34 @@ +package pl.charmas.android.reactivelocation2.observables + +import io.reactivex.Maybe +import io.reactivex.MaybeOnSubscribe +import io.reactivex.MaybeSource +import io.reactivex.MaybeTransformer +import io.reactivex.functions.BiPredicate +import pl.charmas.android.reactivelocation2.observables.exceptions.GoogleAPIConnectionSuspendedException + +class MaybeFactory(private val context: MaybeContext) { + fun createMaybe(source: MaybeOnSubscribe?): Maybe { + return Maybe.create(source).compose( + RetryOnConnectionSuspension( + context.isRetryOnConnectionSuspended + ) + ) + } + + private class RetryOnConnectionSuspension internal constructor(private val shouldRetry: Boolean) : + MaybeTransformer { + override fun apply(upstream: Maybe): MaybeSource { + return if (shouldRetry) { + upstream.retry(IsConnectionSuspendedException()) + } else upstream + } + + private class IsConnectionSuspendedException : + BiPredicate { + override fun test(integer: Int, throwable: Throwable): Boolean { + return throwable is GoogleAPIConnectionSuspendedException + } + } + } +} \ No newline at end of file diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/ObservableContext.java b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/ObservableContext.java deleted file mode 100644 index 4b66d820..00000000 --- a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/ObservableContext.java +++ /dev/null @@ -1,30 +0,0 @@ -package pl.charmas.android.reactivelocation2.observables; - -import android.content.Context; -import android.os.Handler; - -import pl.charmas.android.reactivelocation2.ReactiveLocationProviderConfiguration; - -public class ObservableContext { - private final Context context; - private final Handler handler; - private final boolean retryOnConnectionSuspended; - - public ObservableContext(Context context, ReactiveLocationProviderConfiguration configuration) { - this.context = context; - this.handler = configuration.getCustomCallbackHandler(); - this.retryOnConnectionSuspended = configuration.isRetryOnConnectionSuspended(); - } - - public Context getContext() { - return context; - } - - Handler getHandler() { - return handler; - } - - boolean isRetryOnConnectionSuspended() { - return retryOnConnectionSuspended; - } -} diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/ObservableContext.kt b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/ObservableContext.kt new file mode 100644 index 00000000..ac90a86b --- /dev/null +++ b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/ObservableContext.kt @@ -0,0 +1,13 @@ +package pl.charmas.android.reactivelocation2.observables + +import android.content.Context +import android.os.Handler +import pl.charmas.android.reactivelocation2.ReactiveLocationProviderConfiguration + +class ObservableContext constructor( + val context: Context, + configuration: ReactiveLocationProviderConfiguration +) { + val handler: Handler? = configuration.customCallbackHandler + val isRetryOnConnectionSuspended: Boolean = configuration.isRetryOnConnectionSuspended +} \ No newline at end of file diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/ObservableEmitterWrapper.java b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/ObservableEmitterWrapper.java deleted file mode 100644 index b7c9b8d1..00000000 --- a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/ObservableEmitterWrapper.java +++ /dev/null @@ -1,38 +0,0 @@ -package pl.charmas.android.reactivelocation2.observables; - -import io.reactivex.ObservableEmitter; -import io.reactivex.Observer; -import io.reactivex.disposables.Disposable; - -public class ObservableEmitterWrapper implements Observer { - private final ObservableEmitter emitter; - - public ObservableEmitterWrapper(ObservableEmitter emitter) { - this.emitter = emitter; - } - - @Override - public void onSubscribe(Disposable d) { - } - - @Override - public void onNext(T t) { - if (!emitter.isDisposed()){ - emitter.onNext(t); - } - } - - @Override - public void onError(Throwable e) { - if (!emitter.isDisposed()) { - emitter.onError(e); - } - } - - @Override - public void onComplete() { - if (!emitter.isDisposed()) { - emitter.onComplete(); - } - } -} diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/ObservableEmitterWrapper.kt b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/ObservableEmitterWrapper.kt new file mode 100644 index 00000000..c7c54970 --- /dev/null +++ b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/ObservableEmitterWrapper.kt @@ -0,0 +1,27 @@ +package pl.charmas.android.reactivelocation2.observables + +import io.reactivex.ObservableEmitter +import io.reactivex.Observer +import io.reactivex.disposables.Disposable + +class ObservableEmitterWrapper(private val emitter: ObservableEmitter) : + Observer { + override fun onSubscribe(d: Disposable) {} + override fun onNext(value: T) { + if (!emitter.isDisposed) { + emitter.onNext(value) + } + } + + override fun onError(e: Throwable) { + if (!emitter.isDisposed) { + emitter.onError(e) + } + } + + override fun onComplete() { + if (!emitter.isDisposed) { + emitter.onComplete() + } + } +} \ No newline at end of file diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/ObservableFactory.java b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/ObservableFactory.java deleted file mode 100644 index 3b1b734c..00000000 --- a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/ObservableFactory.java +++ /dev/null @@ -1,42 +0,0 @@ -package pl.charmas.android.reactivelocation2.observables; - -import io.reactivex.Observable; -import io.reactivex.ObservableOnSubscribe; -import io.reactivex.ObservableSource; -import io.reactivex.ObservableTransformer; -import io.reactivex.functions.BiPredicate; - -public class ObservableFactory { - private final ObservableContext context; - - public ObservableFactory(ObservableContext context) { - this.context = context; - } - - public Observable createObservable(ObservableOnSubscribe source) { - return Observable.create(source).compose(new RetryOnConnectionSuspension(context.isRetryOnConnectionSuspended())); - } - - private static class RetryOnConnectionSuspension implements ObservableTransformer { - private final boolean shouldRetry; - - RetryOnConnectionSuspension(boolean shouldRetry) { - this.shouldRetry = shouldRetry; - } - - @Override - public ObservableSource apply(Observable upstream) { - if (shouldRetry) { - return upstream.retry(new IsConnectionSuspendedException()); - } - return upstream; - } - - private static class IsConnectionSuspendedException implements BiPredicate { - @Override - public boolean test(Integer integer, Throwable throwable) throws Exception { - return throwable instanceof GoogleAPIConnectionSuspendedException; - } - } - } -} diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/ObservableFactory.kt b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/ObservableFactory.kt new file mode 100644 index 00000000..df6c6c06 --- /dev/null +++ b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/ObservableFactory.kt @@ -0,0 +1,35 @@ +package pl.charmas.android.reactivelocation2.observables + +import io.reactivex.Observable +import io.reactivex.ObservableOnSubscribe +import io.reactivex.ObservableSource +import io.reactivex.ObservableTransformer +import io.reactivex.functions.BiPredicate +import pl.charmas.android.reactivelocation2.observables.exceptions.GoogleAPIConnectionSuspendedException + +class ObservableFactory constructor(private val context: ObservableContext) { + fun createObservable(source: ObservableOnSubscribe): Observable { + return Observable.create(source).compose( + RetryOnConnectionSuspension( + context.isRetryOnConnectionSuspended + ) + ) + } + + private class RetryOnConnectionSuspension internal constructor(private val shouldRetry: Boolean) : + ObservableTransformer { + override fun apply(upstream: Observable): ObservableSource { + return if (shouldRetry) { + upstream.retry(IsConnectionSuspendedException()) + } else upstream + } + + private class IsConnectionSuspendedException : + BiPredicate { + @Throws(Exception::class) + override fun test(integer: Int, throwable: Throwable): Boolean { + return throwable is GoogleAPIConnectionSuspendedException + } + } + } +} \ No newline at end of file diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/PendingResultObservableOnSubscribe.java b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/PendingResultObservableOnSubscribe.java deleted file mode 100644 index 5bc2e3ef..00000000 --- a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/PendingResultObservableOnSubscribe.java +++ /dev/null @@ -1,33 +0,0 @@ -package pl.charmas.android.reactivelocation2.observables; - -import com.google.android.gms.common.api.PendingResult; -import com.google.android.gms.common.api.Result; -import io.reactivex.ObservableEmitter; -import io.reactivex.ObservableOnSubscribe; -import io.reactivex.disposables.Disposables; - -public class PendingResultObservableOnSubscribe implements ObservableOnSubscribe { - private final PendingResult result; - private boolean complete = false; - - public PendingResultObservableOnSubscribe(PendingResult result) { - this.result = result; - } - - @Override - public void subscribe(final ObservableEmitter emitter) { - result.setResultCallback(t -> { - if (!emitter.isDisposed()) { - emitter.onNext(t); - emitter.onComplete(); - } - complete = true; - }); - - emitter.setDisposable(Disposables.fromAction(() -> { - if (!complete) { - result.cancel(); - } - })); - } -} diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/PendingResultObservableOnSubscribe.kt b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/PendingResultObservableOnSubscribe.kt new file mode 100644 index 00000000..963096cc --- /dev/null +++ b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/PendingResultObservableOnSubscribe.kt @@ -0,0 +1,29 @@ +package pl.charmas.android.reactivelocation2.observables + +import com.google.android.gms.common.api.PendingResult +import com.google.android.gms.common.api.Result +import io.reactivex.ObservableEmitter +import io.reactivex.ObservableOnSubscribe +import io.reactivex.disposables.Disposables + +class PendingResultObservableOnSubscribe( + private val pendingResult: PendingResult +) : ObservableOnSubscribe { + + private var complete = false + + override fun subscribe(emitter: ObservableEmitter) { + pendingResult.setResultCallback { result: T -> + if (!emitter.isDisposed) { + emitter.onNext(result) + emitter.onComplete() + } + complete = true + } + emitter.setDisposable(Disposables.fromAction { + if (!complete) { + pendingResult.cancel() + } + }) + } +} \ No newline at end of file diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/StatusException.java b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/StatusException.java deleted file mode 100644 index 9a0ff78c..00000000 --- a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/StatusException.java +++ /dev/null @@ -1,16 +0,0 @@ -package pl.charmas.android.reactivelocation2.observables; - -import com.google.android.gms.common.api.Status; - -public class StatusException extends Throwable { - private final Status status; - - public StatusException(Status status) { - super(status.getStatusCode() + ": " + status.getStatusMessage()); - this.status = status; - } - - public Status getStatus() { - return status; - } -} diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/StatusException.kt b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/StatusException.kt new file mode 100644 index 00000000..5d2b3c6f --- /dev/null +++ b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/StatusException.kt @@ -0,0 +1,6 @@ +package pl.charmas.android.reactivelocation2.observables + +import com.google.android.gms.common.api.Status + +class StatusException(val status: Status) : + Throwable(status.statusCode.toString() + ": " + status.statusMessage) \ No newline at end of file diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/TaskResultMaybeOnSubscribe.kt b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/TaskResultMaybeOnSubscribe.kt index 76c2f7fb..1ae2d11f 100644 --- a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/TaskResultMaybeOnSubscribe.kt +++ b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/TaskResultMaybeOnSubscribe.kt @@ -4,25 +4,25 @@ import com.google.android.gms.tasks.Task import io.reactivex.MaybeEmitter import io.reactivex.MaybeOnSubscribe -class TaskResultMaybeOnSubscribe(private val result: Task) : +class TaskResultMaybeOnSubscribe(private val task: Task) : MaybeOnSubscribe { override fun subscribe(emitter: MaybeEmitter) { - result.addOnSuccessListener { t: T -> + task.addOnSuccessListener { t: T -> if (!emitter.isDisposed) { emitter.onSuccess(t) } } - result.addOnCompleteListener { command -> + task.addOnCompleteListener { command -> if (!emitter.isDisposed) { - val value = command.result - if (value != null) { - emitter.onSuccess(value) + val result = command.result + if (result != null) { + emitter.onSuccess(result) }else{ emitter.onComplete() } } } - result.addOnFailureListener { exception -> + task.addOnFailureListener { exception -> if (!emitter.isDisposed) { emitter.onError(exception) } diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/TaskResultObservableOnSubscribe.kt b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/TaskResultObservableOnSubscribe.kt deleted file mode 100644 index 6a923678..00000000 --- a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/TaskResultObservableOnSubscribe.kt +++ /dev/null @@ -1,32 +0,0 @@ -package pl.charmas.android.reactivelocation2.observables - -import com.google.android.gms.tasks.Task -import io.reactivex.ObservableEmitter -import io.reactivex.ObservableOnSubscribe - -class TaskResultObservableOnSubscribe(private val result: Task) : - ObservableOnSubscribe { - override fun subscribe(emitter: ObservableEmitter) { - result.addOnSuccessListener { t: T -> - if (!emitter.isDisposed) { - emitter.onNext(t) - emitter.onComplete() - } - } - result.addOnCompleteListener { command -> - if (!emitter.isDisposed) { - val value = command.result - if (value != null) { - emitter.onNext(value) - }else{ - emitter.onComplete() - } - } - } - result.addOnFailureListener { exception -> - if (!emitter.isDisposed) { - emitter.onError(exception) - } - } - } -} \ No newline at end of file diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/exceptions/GoogleAPIConnectionException.kt b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/exceptions/GoogleAPIConnectionException.kt new file mode 100644 index 00000000..26f43287 --- /dev/null +++ b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/exceptions/GoogleAPIConnectionException.kt @@ -0,0 +1,8 @@ +package pl.charmas.android.reactivelocation2.observables.exceptions + +import com.google.android.gms.common.ConnectionResult + +class GoogleAPIConnectionException internal constructor( + detailMessage: String?, + val connectionResult: ConnectionResult +) : RuntimeException(detailMessage) \ No newline at end of file diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/exceptions/GoogleAPIConnectionSuspendedException.kt b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/exceptions/GoogleAPIConnectionSuspendedException.kt new file mode 100644 index 00000000..51fe6b31 --- /dev/null +++ b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/exceptions/GoogleAPIConnectionSuspendedException.kt @@ -0,0 +1,4 @@ +package pl.charmas.android.reactivelocation2.observables.exceptions + +class GoogleAPIConnectionSuspendedException internal constructor(val errorCause: Int) : + RuntimeException() \ No newline at end of file diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/location/LocationUpdatesObservableOnSubscribe.java b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/location/LocationUpdatesObservableOnSubscribe.java index 5b0c0254..2a4ab975 100644 --- a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/location/LocationUpdatesObservableOnSubscribe.java +++ b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/location/LocationUpdatesObservableOnSubscribe.java @@ -1,20 +1,18 @@ package pl.charmas.android.reactivelocation2.observables.location; import android.location.Location; - import com.google.android.gms.common.api.GoogleApiClient; import com.google.android.gms.location.LocationListener; import com.google.android.gms.location.LocationRequest; import com.google.android.gms.location.LocationServices; - -import java.lang.ref.WeakReference; - import io.reactivex.Observable; import io.reactivex.ObservableEmitter; import pl.charmas.android.reactivelocation2.observables.BaseLocationObservableOnSubscribe; import pl.charmas.android.reactivelocation2.observables.ObservableContext; import pl.charmas.android.reactivelocation2.observables.ObservableFactory; +import java.lang.ref.WeakReference; + @SuppressWarnings("MissingPermission") public class LocationUpdatesObservableOnSubscribe extends BaseLocationObservableOnSubscribe { @@ -52,7 +50,7 @@ private static class LocationUpdatesLocationListener implements LocationListener private final WeakReference> weakRef; LocationUpdatesLocationListener(ObservableEmitter emitter) { - this.weakRef = new WeakReference>(emitter); + this.weakRef = new WeakReference<>(emitter); } @Override diff --git a/sample/build.gradle b/sample/build.gradle index 51b78606..3c7dfe59 100755 --- a/sample/build.gradle +++ b/sample/build.gradle @@ -38,14 +38,14 @@ android { dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" - implementation "androidx.core:core-ktx:1.2.0" + implementation "androidx.core:core-ktx:1.3.0" implementation 'androidx.appcompat:appcompat:1.1.0' implementation ('com.google.android.gms:play-services-location:17.0.0'){ exclude group: 'com.google.android.gms', module: 'play-services-places' } - implementation 'com.google.android.libraries.places:places-compat:2.2.0' + implementation 'com.google.android.libraries.places:places:2.3.0' implementation 'io.reactivex.rxjava2:rxjava:2.2.19' implementation 'io.reactivex.rxjava2:rxandroid:2.1.1' implementation 'com.tbruyelle.rxpermissions2:rxpermissions:0.9.1@aar' diff --git a/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/GeofenceActivity.java b/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/GeofenceActivity.java index 51d498bb..3031a4e7 100755 --- a/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/GeofenceActivity.java +++ b/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/GeofenceActivity.java @@ -8,11 +8,9 @@ import android.widget.EditText; import android.widget.TextView; import android.widget.Toast; - import com.google.android.gms.common.api.Status; import com.google.android.gms.location.Geofence; import com.google.android.gms.location.GeofencingRequest; - import io.reactivex.Observable; import io.reactivex.disposables.Disposable; import io.reactivex.functions.Consumer; diff --git a/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/MainActivity.java b/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/MainActivity.java index 9cda52ce..db690418 100755 --- a/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/MainActivity.java +++ b/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/MainActivity.java @@ -33,6 +33,7 @@ import pl.charmas.android.reactivelocation2.sample.utils.ToMostProbableActivity; import java.util.List; +import java.util.Locale; import static pl.charmas.android.reactivelocation2.sample.utils.UnsubscribeIfPresent.dispose; @@ -88,40 +89,22 @@ protected void onCreate(Bundle savedInstanceState) { .setAlwaysShow(true) //Refrence: http://stackoverflow.com/questions/29824408/google-play-services-locationservices-api-new-option-never .build() ) - .doOnNext(new Consumer() { - @Override - public void accept(LocationSettingsResult locationSettingsResult) { - Status status = locationSettingsResult.getStatus(); - if (status.getStatusCode() == LocationSettingsStatusCodes.RESOLUTION_REQUIRED) { - try { - status.startResolutionForResult(MainActivity.this, REQUEST_CHECK_SETTINGS); - } catch (IntentSender.SendIntentException th) { - Log.e("MainActivity", "Error opening settings activity.", th); - } + .doOnNext(locationSettingsResult -> { + Status status = locationSettingsResult.getStatus(); + if (status.getStatusCode() == LocationSettingsStatusCodes.RESOLUTION_REQUIRED) { + try { + status.startResolutionForResult(MainActivity.this, REQUEST_CHECK_SETTINGS); + } catch (IntentSender.SendIntentException th) { + Log.e("MainActivity", "Error opening settings activity.", th); } } }) - .flatMap(new Function>() { - @Override - public Observable apply(LocationSettingsResult locationSettingsResult) { - return locationProvider.getUpdatedLocation(locationRequest); - } - }) + .flatMap((Function>) locationSettingsResult -> locationProvider.getUpdatedLocation(locationRequest)) .observeOn(AndroidSchedulers.mainThread()); addressObservable = locationProvider.getUpdatedLocation(locationRequest) - .flatMap(new Function>>() { - @Override - public Observable> apply(Location location) { - return locationProvider.getReverseGeocodeObservable(location.getLatitude(), location.getLongitude(), 1); - } - }) - .map(new Function, Address>() { - @Override - public Address apply(List

addresses) { - return addresses != null && !addresses.isEmpty() ? addresses.get(0) : null; - } - }) + .flatMap((Function>>) location -> locationProvider.getReverseGeocodeObservable(Locale.getDefault(), location.getLatitude(), location.getLongitude(), 1)) + .map(addresses -> addresses != null && !addresses.isEmpty() ? addresses.get(0) : null) .map(new AddressToStringFunc()) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()); diff --git a/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/PlacesActivity.kt b/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/PlacesActivity.kt index 17272745..29b29a03 100755 --- a/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/PlacesActivity.kt +++ b/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/PlacesActivity.kt @@ -11,6 +11,10 @@ import android.widget.ListView import android.widget.TextView import com.google.android.gms.maps.model.LatLng import com.google.android.gms.maps.model.LatLngBounds +import com.google.android.libraries.places.api.Places +import com.google.android.libraries.places.api.model.RectangularBounds +import com.google.android.libraries.places.api.net.FindAutocompletePredictionsRequest +import com.google.android.libraries.places.api.net.FindCurrentPlaceRequest import io.reactivex.Observable import io.reactivex.disposables.CompositeDisposable import io.reactivex.functions.BiFunction @@ -30,6 +34,7 @@ class PlacesActivity : BaseActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_places) + Places.initialize(this.applicationContext, getString(R.string.API_KEY)) currentPlaceView = findViewById(R.id.current_place_view) queryView = findViewById(R.id.place_query_view) placeSuggestionsList = findViewById(R.id.place_suggestions_list) @@ -48,13 +53,16 @@ class PlacesActivity : BaseActivity() { @SuppressLint("MissingPermission") override fun onLocationPermissionGranted() { compositeDisposable.add( - reactiveLocationProvider.getCurrentPlace(null) - .subscribe({ buffer -> + reactiveLocationProvider.getCurrentPlace( + FindCurrentPlaceRequest.builder(listOf(com.google.android.libraries.places.api.model.Place.Field.ID)) + .build() + ) + .subscribe({ response -> + val buffer = response.placeLikelihoods val likelihood = buffer.firstOrNull() if (likelihood != null) { currentPlaceView.text = likelihood.place.name } - buffer.release() }) { throwable -> Log.e("PlacesActivity", "Error in observable", throwable) } @@ -80,10 +88,11 @@ class PlacesActivity : BaseActivity() { LatLng(latitude - 50.05, longitude - 50.05), LatLng(latitude + 50.05, longitude + 50.05) ) - reactiveLocationProvider.getPlaceCompatAutocompletePredictions( - q.query, - bounds, - null + reactiveLocationProvider.getPlaceAutocompletePredictions( + FindAutocompletePredictionsRequest.builder() + .setQuery(q.query) + .setLocationRestriction(RectangularBounds.newInstance(bounds)) + .build() ) } .doOnError { Log.e(TAG, "onLocationPermissionGranted (line 80): ", it) } @@ -99,7 +108,6 @@ class PlacesActivity : BaseActivity() { ) ) } - buffer.release() placeSuggestionsList.adapter = ArrayAdapter( this@PlacesActivity, android.R.layout.simple_list_item_1, diff --git a/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/PlacesResultActivity.kt b/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/PlacesResultActivity.kt index 3c8dc102..cfdc9cfe 100755 --- a/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/PlacesResultActivity.kt +++ b/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/PlacesResultActivity.kt @@ -15,7 +15,7 @@ class PlacesResultActivity : BaseActivity() { private lateinit var placeNameView: TextView private lateinit var placeLocationView: TextView private lateinit var placeAddressView: TextView - private var placeId: String? = null + private lateinit var placeId: String override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_places_result) @@ -30,24 +30,19 @@ class PlacesResultActivity : BaseActivity() { get() { val loadedIntent = intent placeId = loadedIntent.getStringExtra(EXTRA_PLACE_ID) - if (placeId == null) { - throw IllegalArgumentException("You must start SearchResultsActivity with a non-null place Id using getStartIntent(Context, String)") - } + ?: throw IllegalArgumentException("You must start SearchResultsActivity with a non-null place Id using getStartIntent(Context, String)") } override fun onLocationPermissionGranted() { compositeSubscription = CompositeDisposable() compositeSubscription.add( - reactiveLocationProvider.getPlaceCompatById(placeId) - .subscribe { buffer -> - val place = buffer.firstOrNull() - if (place != null) { - placeNameView.text = place.name - val text = place.latLng.latitude.toString() + ", " + place.latLng.longitude - placeLocationView.text = text - placeAddressView.text = place.address - } - buffer.release() + reactiveLocationProvider.getPlaceById(placeId) + .subscribe { res -> + val place = res.place + placeNameView.text = place.name + val text = place.latLng?.latitude.toString() + ", " + place.latLng?.longitude + placeLocationView.text = text + placeAddressView.text = place.address } ) } From 4eab302041e951d36cc75085372164ced33f3633 Mon Sep 17 00:00:00 2001 From: Nikolay Unuchek Date: Wed, 29 Jul 2020 15:24:13 +0300 Subject: [PATCH 09/21] updated call from LocationServices.FusedLocationApi to LocationServices.getFusedLocationProviderClient(context) updated classes to kotlin style --- .../ReactiveLocationProvider.kt | 123 +++++++++--------- .../android/reactivelocation2/ext/TasksExt.kt | 5 + .../BaseLocationMaybeOnSubscribe.kt | 6 + .../observables/BaseMaybeOnSubscribe.kt | 2 +- .../observables/BaseObservableOnSubscribe.kt | 14 +- .../GoogleAPIClientMaybeOnSubscribe.kt | 2 +- .../observables/MaybeFactory.kt | 2 +- .../observables/ObservableFactory.kt | 2 +- .../observables/TaskResultMaybeOnSubscribe.kt | 10 +- .../TaskSuccessFailureMaybeOnSubscribe.kt | 21 +++ .../ActivityUpdatesObservableOnSubscribe.java | 2 +- .../BaseActivityObservableOnSubscribe.java | 12 -- .../BaseActivityObservableOnSubscribe.kt | 8 ++ .../observables/geocode/GeocodeMaybe.kt | 73 +++++++++++ .../geocode/GeocodeObservable.java | 67 ---------- .../geocode/ReverseGeocodeObservable.java | 2 +- .../geofence/AddGeofenceMaybeOnSubscribe.kt | 55 ++++++++ .../AddGeofenceObservableOnSubscribe.java | 53 -------- ...GeofenceByPendingIntentMaybeOnSubscribe.kt | 29 +++++ ...eByPendingIntentObservableOnSubscribe.java | 40 ------ .../RemoveGeofenceMaybeOnSubscribe.kt | 58 +++++++++ .../RemoveGeofenceObservableOnSubscribe.java | 38 ------ ...emoveGeofenceRequestIdsMaybeOnSubscribe.kt | 28 ++++ ...ofenceRequestIdsObservableOnSubscribe.java | 39 ------ ...ddLocationIntentUpdatesMaybeOnSubscribe.kt | 55 ++++++++ ...ionIntentUpdatesObservableOnSubscribe.java | 52 -------- ...astKnownLocationObservableOnSubscribe.java | 33 ----- .../LocationUpdatesObservableOnSubscribe.java | 64 --------- .../LocationUpdatesObservableOnSubscribe.kt | 85 ++++++++++++ .../MockLocationObservableOnSubscribe.java | 100 -------------- .../MockLocationObservableOnSubscribe.kt | 94 +++++++++++++ ...ionIntentUpdatesObservableOnSubscribe.java | 47 ------- ...ationIntentUpdatesObservableOnSubscribe.kt | 48 +++++++ build.gradle | 2 +- sample/build.gradle | 8 +- .../sample/GeofenceActivity.java | 76 ++++------- .../sample/MainActivity.java | 39 ++++-- 37 files changed, 699 insertions(+), 695 deletions(-) create mode 100644 android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/BaseLocationMaybeOnSubscribe.kt create mode 100644 android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/TaskSuccessFailureMaybeOnSubscribe.kt delete mode 100644 android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/activity/BaseActivityObservableOnSubscribe.java create mode 100644 android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/activity/BaseActivityObservableOnSubscribe.kt create mode 100644 android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/geocode/GeocodeMaybe.kt delete mode 100644 android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/geocode/GeocodeObservable.java create mode 100644 android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/geofence/AddGeofenceMaybeOnSubscribe.kt delete mode 100644 android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/geofence/AddGeofenceObservableOnSubscribe.java create mode 100644 android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/geofence/RemoveGeofenceByPendingIntentMaybeOnSubscribe.kt delete mode 100644 android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/geofence/RemoveGeofenceByPendingIntentObservableOnSubscribe.java create mode 100644 android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/geofence/RemoveGeofenceMaybeOnSubscribe.kt delete mode 100644 android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/geofence/RemoveGeofenceObservableOnSubscribe.java create mode 100644 android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/geofence/RemoveGeofenceRequestIdsMaybeOnSubscribe.kt delete mode 100644 android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/geofence/RemoveGeofenceRequestIdsObservableOnSubscribe.java create mode 100644 android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/location/AddLocationIntentUpdatesMaybeOnSubscribe.kt delete mode 100644 android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/location/AddLocationIntentUpdatesObservableOnSubscribe.java delete mode 100644 android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/location/LastKnownLocationObservableOnSubscribe.java delete mode 100644 android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/location/LocationUpdatesObservableOnSubscribe.java create mode 100644 android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/location/LocationUpdatesObservableOnSubscribe.kt delete mode 100644 android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/location/MockLocationObservableOnSubscribe.java create mode 100644 android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/location/MockLocationObservableOnSubscribe.kt delete mode 100644 android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/location/RemoveLocationIntentUpdatesObservableOnSubscribe.java create mode 100644 android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/location/RemoveLocationIntentUpdatesObservableOnSubscribe.kt diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/ReactiveLocationProvider.kt b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/ReactiveLocationProvider.kt index 679fb8b6..47afb958 100644 --- a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/ReactiveLocationProvider.kt +++ b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/ReactiveLocationProvider.kt @@ -9,13 +9,12 @@ import androidx.annotation.IntRange import androidx.annotation.RequiresPermission import com.google.android.gms.common.api.Api import com.google.android.gms.common.api.GoogleApiClient -import com.google.android.gms.common.api.Status import com.google.android.gms.location.ActivityRecognitionResult import com.google.android.gms.location.GeofencingRequest import com.google.android.gms.location.LocationRequest import com.google.android.gms.location.LocationServices import com.google.android.gms.location.LocationSettingsRequest -import com.google.android.gms.location.LocationSettingsResult +import com.google.android.gms.location.LocationSettingsResponse import com.google.android.gms.maps.model.LatLngBounds import com.google.android.libraries.places.api.Places import com.google.android.libraries.places.api.model.AutocompletePrediction @@ -30,20 +29,19 @@ import com.google.android.libraries.places.api.net.FindCurrentPlaceRequest import com.google.android.libraries.places.api.net.FindCurrentPlaceResponse import io.reactivex.Maybe import io.reactivex.Observable +import pl.charmas.android.reactivelocation2.ext.fromSuccessFailureToMaybe import pl.charmas.android.reactivelocation2.ext.toMaybe -import pl.charmas.android.reactivelocation2.ext.toObservable import pl.charmas.android.reactivelocation2.observables.GoogleAPIClientMaybeOnSubscribe import pl.charmas.android.reactivelocation2.observables.MaybeContext import pl.charmas.android.reactivelocation2.observables.MaybeFactory import pl.charmas.android.reactivelocation2.observables.ObservableContext import pl.charmas.android.reactivelocation2.observables.ObservableFactory import pl.charmas.android.reactivelocation2.observables.activity.ActivityUpdatesObservableOnSubscribe -import pl.charmas.android.reactivelocation2.observables.geocode.GeocodeObservable +import pl.charmas.android.reactivelocation2.observables.geocode.GeocodeMaybe import pl.charmas.android.reactivelocation2.observables.geocode.ReverseGeocodeObservable -import pl.charmas.android.reactivelocation2.observables.geofence.AddGeofenceObservableOnSubscribe -import pl.charmas.android.reactivelocation2.observables.geofence.RemoveGeofenceObservableOnSubscribe -import pl.charmas.android.reactivelocation2.observables.location.AddLocationIntentUpdatesObservableOnSubscribe -import pl.charmas.android.reactivelocation2.observables.location.LastKnownLocationObservableOnSubscribe +import pl.charmas.android.reactivelocation2.observables.geofence.AddGeofenceMaybeOnSubscribe +import pl.charmas.android.reactivelocation2.observables.geofence.RemoveGeofenceMaybeOnSubscribe +import pl.charmas.android.reactivelocation2.observables.location.AddLocationIntentUpdatesMaybeOnSubscribe import pl.charmas.android.reactivelocation2.observables.location.LocationUpdatesObservableOnSubscribe import pl.charmas.android.reactivelocation2.observables.location.MockLocationObservableOnSubscribe import pl.charmas.android.reactivelocation2.observables.location.RemoveLocationIntentUpdatesObservableOnSubscribe @@ -62,7 +60,7 @@ class ReactiveLocationProvider */ @JvmOverloads constructor( - context: Context, + val context: Context, configuration: ReactiveLocationProviderConfiguration = ReactiveLocationProviderConfiguration.builder() .build() ) { @@ -71,6 +69,11 @@ constructor( private val factoryObservable: ObservableFactory = ObservableFactory(ctxObservable) private val factoryMaybe: MaybeFactory = MaybeFactory(ctxMaybe) + private val settingsClient = LocationServices.getSettingsClient(context) + private val geofencingClient = LocationServices.getGeofencingClient(context) + private val fusedLocationProviderClient = + LocationServices.getFusedLocationProviderClient(context) + /** * Creates observable that obtains last known location and than completes. * Delivered location is never null - when it is unavailable Observable completes without emitting @@ -84,11 +87,12 @@ constructor( * @return observable that serves last know location */ @get:RequiresPermission(anyOf = ["android.permission.ACCESS_COARSE_LOCATION", "android.permission.ACCESS_FINE_LOCATION"]) - val lastKnownLocation: Observable - get() = LastKnownLocationObservableOnSubscribe.createObservable( - ctxObservable, - factoryObservable - ) + val lastKnownLocation: Maybe + get() { + return fusedLocationProviderClient + .lastLocation + .toMaybe() + } /** * Creates observable that allows to observe infinite stream of location updates. @@ -104,8 +108,9 @@ constructor( * @return observable that serves infinite stream of location updates */ @RequiresPermission(anyOf = ["android.permission.ACCESS_COARSE_LOCATION", "android.permission.ACCESS_FINE_LOCATION"]) - fun getUpdatedLocation(locationRequest: LocationRequest?): Observable { + fun getUpdatedLocation(locationRequest: LocationRequest): Observable { return LocationUpdatesObservableOnSubscribe.createObservable( + fusedLocationProviderClient, ctxObservable, factoryObservable, locationRequest @@ -130,8 +135,9 @@ constructor( * @return observable that emits {@link com.google.android.gms.common.api.Status} */ @RequiresPermission(allOf = ["android.permission.ACCESS_COARSE_LOCATION", "android.permission.ACCESS_MOCK_LOCATION"]) - fun mockLocation(sourceLocationObservable: Observable?): Observable { - return MockLocationObservableOnSubscribe.createObservable( + fun mockLocation(sourceLocationObservable: Observable): Observable { + return MockLocationObservableOnSubscribe.create( + fusedLocationProviderClient, ctxObservable, factoryObservable, sourceLocationObservable @@ -154,12 +160,13 @@ constructor( */ @RequiresPermission(anyOf = ["android.permission.ACCESS_COARSE_LOCATION", "android.permission.ACCESS_FINE_LOCATION"]) fun requestLocationUpdates( - locationRequest: LocationRequest?, - intent: PendingIntent? - ): Observable { - return AddLocationIntentUpdatesObservableOnSubscribe.createObservable( - ctxObservable, - factoryObservable, + locationRequest: LocationRequest, + intent: PendingIntent + ): Maybe { + return AddLocationIntentUpdatesMaybeOnSubscribe.create( + fusedLocationProviderClient, + ctxMaybe, + factoryMaybe, locationRequest, intent ) @@ -173,10 +180,11 @@ constructor( * @param intent PendingIntent to remove location updates for * @return observable that removes the PendingIntent */ - fun removeLocationUpdates(intent: PendingIntent?): Observable { - return RemoveLocationIntentUpdatesObservableOnSubscribe.createObservable( - ctxObservable, - factoryObservable, + fun removeLocationUpdates(intent: PendingIntent): Maybe { + return RemoveLocationIntentUpdatesObservableOnSubscribe.create( + fusedLocationProviderClient, + ctxMaybe, + factoryMaybe, intent ) } @@ -224,18 +232,18 @@ constructor( * @return observable that serves list of address based on location name */ fun getGeocodeObservable( - locationName: String?, + locationName: String, maxResults: Int, bounds: LatLngBounds? = null, - locale: Locale? = Locale.getDefault() - ): Observable> { - return GeocodeObservable.createObservable( + locale: Locale? = null + ): Maybe> { + return GeocodeMaybe.create( ctxObservable.context, - factoryObservable, + factoryMaybe, locationName, maxResults, bounds, - locale + locale ?: Locale.getDefault() ) } @@ -255,12 +263,13 @@ constructor( */ @RequiresPermission("android.permission.ACCESS_FINE_LOCATION") fun addGeofences( - geofenceTransitionPendingIntent: PendingIntent?, - request: GeofencingRequest? - ): Observable { - return AddGeofenceObservableOnSubscribe.createObservable( - ctxObservable, - factoryObservable, + geofenceTransitionPendingIntent: PendingIntent, + request: GeofencingRequest + ): Maybe { + return AddGeofenceMaybeOnSubscribe.createMaybe( + geofencingClient, + ctxMaybe, + factoryMaybe, request, geofenceTransitionPendingIntent ) @@ -278,11 +287,11 @@ constructor( * @param pendingIntent key of registered geofences * @return observable that removed geofences */ - fun removeGeofences(pendingIntent: PendingIntent?): Observable { - return RemoveGeofenceObservableOnSubscribe.createObservable( - ctxObservable, - factoryObservable, - pendingIntent + fun removeGeofences(pendingIntent: PendingIntent): Maybe { + return RemoveGeofenceMaybeOnSubscribe.createMaybe( + ctxMaybe, + factoryMaybe, + pendingIntent, geofencingClient ) } @@ -298,11 +307,12 @@ constructor( * @param requestIds geofences to remove * @return observable that removed geofences */ - fun removeGeofences(requestIds: List?): Observable { - return RemoveGeofenceObservableOnSubscribe.createObservable( - ctxObservable, - factoryObservable, - requestIds + fun removeGeofences(requestIds: List): Maybe { + return RemoveGeofenceMaybeOnSubscribe.createMaybe( + ctxMaybe, + factoryMaybe, + requestIds, + geofencingClient ) } @@ -327,15 +337,12 @@ constructor( * @return observable that emits check result of location settings * @see com.google.android.gms.location.SettingsApi */ - fun checkLocationSettings(locationRequest: LocationSettingsRequest?): Observable { - return getGoogleApiClientMaybe(LocationServices.API) - .flatMapObservable { googleApiClient -> - LocationServices.SettingsApi.checkLocationSettings( - googleApiClient, - locationRequest - ) - .toObservable() - } + fun checkLocationSettings( + locationRequest: LocationSettingsRequest + ): Maybe { + return settingsClient + .checkLocationSettings(locationRequest) + .fromSuccessFailureToMaybe() } /** diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/ext/TasksExt.kt b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/ext/TasksExt.kt index 26053ba8..9f8f7436 100644 --- a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/ext/TasksExt.kt +++ b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/ext/TasksExt.kt @@ -7,11 +7,16 @@ import io.reactivex.Maybe import io.reactivex.Observable import pl.charmas.android.reactivelocation2.observables.PendingResultObservableOnSubscribe import pl.charmas.android.reactivelocation2.observables.TaskResultMaybeOnSubscribe +import pl.charmas.android.reactivelocation2.observables.TaskSuccessFailureMaybeOnSubscribe fun Task.toMaybe(): Maybe { return Maybe.create(TaskResultMaybeOnSubscribe(this)) } +fun Task.fromSuccessFailureToMaybe(): Maybe { + return Maybe.create(TaskSuccessFailureMaybeOnSubscribe(this)) +} + fun PendingResult.toObservable(): Observable { return Observable.create(PendingResultObservableOnSubscribe(this)) } diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/BaseLocationMaybeOnSubscribe.kt b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/BaseLocationMaybeOnSubscribe.kt new file mode 100644 index 00000000..e933a25b --- /dev/null +++ b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/BaseLocationMaybeOnSubscribe.kt @@ -0,0 +1,6 @@ +package pl.charmas.android.reactivelocation2.observables + +import com.google.android.gms.location.LocationServices + +abstract class BaseLocationMaybeOnSubscribe protected constructor(ctx: MaybeContext) : + BaseMaybeOnSubscribe(ctx, LocationServices.API) \ No newline at end of file diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/BaseMaybeOnSubscribe.kt b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/BaseMaybeOnSubscribe.kt index 66b0e3fc..1d8eb9b5 100644 --- a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/BaseMaybeOnSubscribe.kt +++ b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/BaseMaybeOnSubscribe.kt @@ -57,7 +57,7 @@ abstract class BaseMaybeOnSubscribe @SafeVarargs protected constructor( return apiClient } - private fun onDisposed(locationClient: GoogleApiClient?) {} + private fun onDisposed(locationClient: GoogleApiClient) {} protected abstract fun onGoogleApiClientReady( apiClient: GoogleApiClient, diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/BaseObservableOnSubscribe.kt b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/BaseObservableOnSubscribe.kt index 55547e03..24802709 100644 --- a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/BaseObservableOnSubscribe.kt +++ b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/BaseObservableOnSubscribe.kt @@ -42,9 +42,7 @@ abstract class BaseObservableOnSubscribe @SafeVarargs protected constructor( private fun createApiClient(emitter: ObservableEmitter): GoogleApiClient { val apiClientConnectionCallbacks = - ApiClientConnectionCallbacks( - emitter - ) + ApiClientConnectionCallbacks(emitter) var apiClientBuilder = GoogleApiClient.Builder(ctx) for (service in services) { apiClientBuilder = apiClientBuilder.addApi(service) @@ -60,15 +58,15 @@ abstract class BaseObservableOnSubscribe @SafeVarargs protected constructor( return apiClient } - protected open fun onDisposed(locationClient: GoogleApiClient?) {} + protected open fun onDisposed(locationClient: GoogleApiClient) {} protected abstract fun onGoogleApiClientReady( - apiClient: GoogleApiClient?, - emitter: ObservableEmitter? + apiClient: GoogleApiClient, + emitter: ObservableEmitter ) private inner class ApiClientConnectionCallbacks constructor(private val emitter: ObservableEmitter) : ConnectionCallbacks, OnConnectionFailedListener { - private var apiClient: GoogleApiClient? = null + private lateinit var apiClient: GoogleApiClient override fun onConnected(bundle: Bundle?) { try { onGoogleApiClientReady(apiClient, emitter) @@ -96,7 +94,7 @@ abstract class BaseObservableOnSubscribe @SafeVarargs protected constructor( } } - fun setClient(client: GoogleApiClient?) { + fun setClient(client: GoogleApiClient) { apiClient = client } } diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/GoogleAPIClientMaybeOnSubscribe.kt b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/GoogleAPIClientMaybeOnSubscribe.kt index b9260e40..fb22408a 100644 --- a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/GoogleAPIClientMaybeOnSubscribe.kt +++ b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/GoogleAPIClientMaybeOnSubscribe.kt @@ -26,7 +26,7 @@ class GoogleAPIClientMaybeOnSubscribe @SafeVarargs private constructor( factory: MaybeFactory, vararg apis: Api ): Maybe { - return factory.createMaybe( + return factory.create( GoogleAPIClientMaybeOnSubscribe(context, *apis) ) } diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/MaybeFactory.kt b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/MaybeFactory.kt index 35bee742..ea5ba87d 100644 --- a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/MaybeFactory.kt +++ b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/MaybeFactory.kt @@ -8,7 +8,7 @@ import io.reactivex.functions.BiPredicate import pl.charmas.android.reactivelocation2.observables.exceptions.GoogleAPIConnectionSuspendedException class MaybeFactory(private val context: MaybeContext) { - fun createMaybe(source: MaybeOnSubscribe?): Maybe { + fun create(source: MaybeOnSubscribe?): Maybe { return Maybe.create(source).compose( RetryOnConnectionSuspension( context.isRetryOnConnectionSuspended diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/ObservableFactory.kt b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/ObservableFactory.kt index df6c6c06..fb8d9649 100644 --- a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/ObservableFactory.kt +++ b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/ObservableFactory.kt @@ -8,7 +8,7 @@ import io.reactivex.functions.BiPredicate import pl.charmas.android.reactivelocation2.observables.exceptions.GoogleAPIConnectionSuspendedException class ObservableFactory constructor(private val context: ObservableContext) { - fun createObservable(source: ObservableOnSubscribe): Observable { + fun create(source: ObservableOnSubscribe): Observable { return Observable.create(source).compose( RetryOnConnectionSuspension( context.isRetryOnConnectionSuspended diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/TaskResultMaybeOnSubscribe.kt b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/TaskResultMaybeOnSubscribe.kt index 1ae2d11f..0cddc9b1 100644 --- a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/TaskResultMaybeOnSubscribe.kt +++ b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/TaskResultMaybeOnSubscribe.kt @@ -7,19 +7,15 @@ import io.reactivex.MaybeOnSubscribe class TaskResultMaybeOnSubscribe(private val task: Task) : MaybeOnSubscribe { override fun subscribe(emitter: MaybeEmitter) { - task.addOnSuccessListener { t: T -> + task.addOnSuccessListener { result: T? -> if (!emitter.isDisposed) { - emitter.onSuccess(t) + result?.let { emitter.onSuccess(it) } ?: emitter.onComplete() } } task.addOnCompleteListener { command -> if (!emitter.isDisposed) { val result = command.result - if (result != null) { - emitter.onSuccess(result) - }else{ - emitter.onComplete() - } + result?.let { emitter.onSuccess(it) } ?: emitter.onComplete() } } task.addOnFailureListener { exception -> diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/TaskSuccessFailureMaybeOnSubscribe.kt b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/TaskSuccessFailureMaybeOnSubscribe.kt new file mode 100644 index 00000000..9e5915b5 --- /dev/null +++ b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/TaskSuccessFailureMaybeOnSubscribe.kt @@ -0,0 +1,21 @@ +package pl.charmas.android.reactivelocation2.observables + +import com.google.android.gms.tasks.Task +import io.reactivex.MaybeEmitter +import io.reactivex.MaybeOnSubscribe + +class TaskSuccessFailureMaybeOnSubscribe(private val task: Task) : + MaybeOnSubscribe { + override fun subscribe(emitter: MaybeEmitter) { + task.addOnSuccessListener { t: T -> + if (!emitter.isDisposed) { + emitter.onSuccess(t) + } + } + task.addOnFailureListener { exception -> + if (!emitter.isDisposed) { + emitter.onError(exception) + } + } + } +} \ No newline at end of file diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/activity/ActivityUpdatesObservableOnSubscribe.java b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/activity/ActivityUpdatesObservableOnSubscribe.java index 60abc354..4fd2e55c 100644 --- a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/activity/ActivityUpdatesObservableOnSubscribe.java +++ b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/activity/ActivityUpdatesObservableOnSubscribe.java @@ -25,7 +25,7 @@ public class ActivityUpdatesObservableOnSubscribe extends BaseActivityObservable private ActivityUpdatesBroadcastReceiver receiver; public static Observable createObservable(ObservableContext ctx, ObservableFactory factory, int detectionIntervalMiliseconds) { - return factory.createObservable(new ActivityUpdatesObservableOnSubscribe(ctx, detectionIntervalMiliseconds)); + return factory.create(new ActivityUpdatesObservableOnSubscribe(ctx, detectionIntervalMiliseconds)); } private ActivityUpdatesObservableOnSubscribe(ObservableContext context, int detectionIntervalMilliseconds) { diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/activity/BaseActivityObservableOnSubscribe.java b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/activity/BaseActivityObservableOnSubscribe.java deleted file mode 100644 index 3101ffbd..00000000 --- a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/activity/BaseActivityObservableOnSubscribe.java +++ /dev/null @@ -1,12 +0,0 @@ -package pl.charmas.android.reactivelocation2.observables.activity; - -import com.google.android.gms.location.ActivityRecognition; - -import pl.charmas.android.reactivelocation2.observables.BaseObservableOnSubscribe; -import pl.charmas.android.reactivelocation2.observables.ObservableContext; - -abstract class BaseActivityObservableOnSubscribe extends BaseObservableOnSubscribe { - BaseActivityObservableOnSubscribe(ObservableContext ctx) { - super(ctx, ActivityRecognition.API); - } -} diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/activity/BaseActivityObservableOnSubscribe.kt b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/activity/BaseActivityObservableOnSubscribe.kt new file mode 100644 index 00000000..bb045002 --- /dev/null +++ b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/activity/BaseActivityObservableOnSubscribe.kt @@ -0,0 +1,8 @@ +package pl.charmas.android.reactivelocation2.observables.activity + +import com.google.android.gms.location.ActivityRecognition +import pl.charmas.android.reactivelocation2.observables.BaseObservableOnSubscribe +import pl.charmas.android.reactivelocation2.observables.ObservableContext + +internal abstract class BaseActivityObservableOnSubscribe constructor(ctx: ObservableContext) : + BaseObservableOnSubscribe(ctx, ActivityRecognition.API) \ No newline at end of file diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/geocode/GeocodeMaybe.kt b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/geocode/GeocodeMaybe.kt new file mode 100644 index 00000000..f5e68eff --- /dev/null +++ b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/geocode/GeocodeMaybe.kt @@ -0,0 +1,73 @@ +package pl.charmas.android.reactivelocation2.observables.geocode + +import android.content.Context +import android.location.Address +import android.location.Geocoder +import com.google.android.gms.maps.model.LatLngBounds +import io.reactivex.Maybe +import io.reactivex.MaybeEmitter +import io.reactivex.MaybeOnSubscribe +import pl.charmas.android.reactivelocation2.observables.MaybeFactory +import pl.charmas.android.reactivelocation2.observables.ObservableFactory +import java.io.IOException +import java.util.Locale + +class GeocodeMaybe private constructor( + private val ctx: Context, + private val locationName: String, + private val maxResults: Int, + private val bounds: LatLngBounds? = null, + private val locale: Locale +) : MaybeOnSubscribe> { + + @Throws(Exception::class) + override fun subscribe(emitter: MaybeEmitter>) { + val geoCoder = createGeocoder() + try { + val result = getAddresses(geoCoder) + if (!emitter.isDisposed) { + emitter.onSuccess(result) + } + } catch (e: IOException) { + if (!emitter.isDisposed) { + emitter.onError(e) + } + } + } + + @Throws(IOException::class) + private fun getAddresses(geocoder: Geocoder): List
{ + return if (bounds != null) { + geocoder.getFromLocationName( + locationName, + maxResults, + bounds.southwest.latitude, + bounds.southwest.longitude, + bounds.northeast.latitude, + bounds.northeast.longitude + ) + } else { + geocoder.getFromLocationName(locationName, maxResults) + } + } + + private fun createGeocoder(): Geocoder { + return Geocoder(ctx, locale) + } + + companion object { + fun create( + ctx: Context, + factory: MaybeFactory, + locationName: String, + maxResults: Int, + bounds: LatLngBounds? = null, + locale: Locale + ): Maybe> { + return factory.create( + GeocodeMaybe(ctx, locationName, maxResults, bounds, locale) + ) + } + } + +} \ No newline at end of file diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/geocode/GeocodeObservable.java b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/geocode/GeocodeObservable.java deleted file mode 100644 index 6d037e92..00000000 --- a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/geocode/GeocodeObservable.java +++ /dev/null @@ -1,67 +0,0 @@ -package pl.charmas.android.reactivelocation2.observables.geocode; - -import android.content.Context; -import android.location.Address; -import android.location.Geocoder; -import androidx.annotation.NonNull; -import com.google.android.gms.maps.model.LatLngBounds; -import io.reactivex.Observable; -import io.reactivex.ObservableEmitter; -import io.reactivex.ObservableOnSubscribe; -import pl.charmas.android.reactivelocation2.observables.ObservableFactory; - -import java.io.IOException; -import java.util.List; -import java.util.Locale; - -public class GeocodeObservable implements ObservableOnSubscribe> { - private final Context ctx; - private final String locationName; - private final int maxResults; - private final LatLngBounds bounds; - private final Locale locale; - - public static Observable> createObservable(Context ctx, ObservableFactory factory, String locationName, int maxResults, LatLngBounds bounds, Locale locale) { - return factory.createObservable(new GeocodeObservable(ctx, locationName, maxResults, bounds, locale)); - } - - private GeocodeObservable(Context ctx, String locationName, int maxResults, LatLngBounds bounds, Locale locale) { - this.ctx = ctx; - this.locationName = locationName; - this.maxResults = maxResults; - this.bounds = bounds; - this.locale = locale; - } - - @Override - public void subscribe(ObservableEmitter> emitter) throws Exception { - Geocoder geocoder = createGeocoder(); - try { - List
result = getAddresses(geocoder); - if (!emitter.isDisposed()) { - emitter.onNext(result); - emitter.onComplete(); - } - } catch (IOException e) { - if (!emitter.isDisposed()) { - emitter.onError(e); - } - } - } - - private List
getAddresses(Geocoder geocoder) throws IOException { - List
result; - if (bounds != null) { - result = geocoder.getFromLocationName(locationName, maxResults, bounds.southwest.latitude, bounds.southwest.longitude, bounds.northeast.latitude, bounds.northeast.longitude); - } else { - result = geocoder.getFromLocationName(locationName, maxResults); - } - return result; - } - - @NonNull - private Geocoder createGeocoder() { - if (locale != null) return new Geocoder(ctx, locale); - return new Geocoder(ctx); - } -} diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/geocode/ReverseGeocodeObservable.java b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/geocode/ReverseGeocodeObservable.java index bbcccf5f..1945854f 100644 --- a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/geocode/ReverseGeocodeObservable.java +++ b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/geocode/ReverseGeocodeObservable.java @@ -24,7 +24,7 @@ public class ReverseGeocodeObservable implements ObservableOnSubscribe> createObservable(Context ctx, ObservableFactory factory, Locale locale, double latitude, double longitude, int maxResults) { - return factory.createObservable(new ReverseGeocodeObservable(ctx, locale, latitude, longitude, maxResults)); + return factory.create(new ReverseGeocodeObservable(ctx, locale, latitude, longitude, maxResults)); } private ReverseGeocodeObservable(Context ctx, Locale locale, double latitude, double longitude, int maxResults) { diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/geofence/AddGeofenceMaybeOnSubscribe.kt b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/geofence/AddGeofenceMaybeOnSubscribe.kt new file mode 100644 index 00000000..f086f299 --- /dev/null +++ b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/geofence/AddGeofenceMaybeOnSubscribe.kt @@ -0,0 +1,55 @@ +package pl.charmas.android.reactivelocation2.observables.geofence + +import android.app.PendingIntent +import androidx.annotation.RequiresPermission +import com.google.android.gms.common.api.GoogleApiClient +import com.google.android.gms.location.GeofencingClient +import com.google.android.gms.location.GeofencingRequest +import io.reactivex.Maybe +import io.reactivex.MaybeEmitter +import pl.charmas.android.reactivelocation2.observables.BaseLocationMaybeOnSubscribe +import pl.charmas.android.reactivelocation2.observables.MaybeContext +import pl.charmas.android.reactivelocation2.observables.MaybeFactory + +class AddGeofenceMaybeOnSubscribe private constructor( + ctx: MaybeContext, + private val request: GeofencingRequest, + private val geofenceTransitionPendingIntent: PendingIntent, + private val geofencingClient: GeofencingClient +) : BaseLocationMaybeOnSubscribe(ctx) { + + @RequiresPermission("android.permission.ACCESS_FINE_LOCATION") + override fun onGoogleApiClientReady( + apiClient: GoogleApiClient, + emitter: MaybeEmitter + ) { + geofencingClient.addGeofences(request, geofenceTransitionPendingIntent) + .addOnSuccessListener { sd: Void? -> + if (emitter.isDisposed) return@addOnSuccessListener + emitter.onSuccess(true) + } + .addOnFailureListener { error: Exception? -> + if (emitter.isDisposed) return@addOnFailureListener + emitter.onError(error!!) + } + } + + companion object { + fun createMaybe( + geofencingClient: GeofencingClient, + ctx: MaybeContext, + factory: MaybeFactory, + request: GeofencingRequest, + geofenceTransitionPendingIntent: PendingIntent + ): Maybe { + return factory.create( + AddGeofenceMaybeOnSubscribe( + ctx, + request, + geofenceTransitionPendingIntent, + geofencingClient + ) + ) + } + } +} \ No newline at end of file diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/geofence/AddGeofenceObservableOnSubscribe.java b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/geofence/AddGeofenceObservableOnSubscribe.java deleted file mode 100644 index 2a5e69ca..00000000 --- a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/geofence/AddGeofenceObservableOnSubscribe.java +++ /dev/null @@ -1,53 +0,0 @@ -package pl.charmas.android.reactivelocation2.observables.geofence; - -import android.app.PendingIntent; -import androidx.annotation.NonNull; - -import com.google.android.gms.common.api.GoogleApiClient; -import com.google.android.gms.common.api.ResultCallback; -import com.google.android.gms.common.api.Status; -import com.google.android.gms.location.GeofencingRequest; -import com.google.android.gms.location.LocationServices; - -import io.reactivex.Observable; -import io.reactivex.ObservableEmitter; -import pl.charmas.android.reactivelocation2.observables.BaseLocationObservableOnSubscribe; -import pl.charmas.android.reactivelocation2.observables.ObservableContext; -import pl.charmas.android.reactivelocation2.observables.ObservableFactory; -import pl.charmas.android.reactivelocation2.observables.StatusException; - - -@SuppressWarnings("MissingPermission") -public class AddGeofenceObservableOnSubscribe extends BaseLocationObservableOnSubscribe { - private final GeofencingRequest request; - private final PendingIntent geofenceTransitionPendingIntent; - - public static Observable createObservable(ObservableContext ctx, ObservableFactory factory, GeofencingRequest request, PendingIntent geofenceTransitionPendingIntent) { - return factory.createObservable(new AddGeofenceObservableOnSubscribe(ctx, request, geofenceTransitionPendingIntent)); - } - - private AddGeofenceObservableOnSubscribe(ObservableContext ctx, GeofencingRequest request, PendingIntent geofenceTransitionPendingIntent) { - super(ctx); - this.request = request; - this.geofenceTransitionPendingIntent = geofenceTransitionPendingIntent; - } - - @Override - protected void onGoogleApiClientReady(GoogleApiClient apiClient, final ObservableEmitter emitter) { - LocationServices.GeofencingApi.addGeofences(apiClient, request, geofenceTransitionPendingIntent) - .setResultCallback(new ResultCallback() { - @Override - public void onResult(@NonNull Status status) { - if (emitter.isDisposed()) return; - if (status.isSuccess()) { - emitter.onNext(status); - emitter.onComplete(); - - } else { - emitter.onError(new StatusException(status)); - } - } - }); - } - -} diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/geofence/RemoveGeofenceByPendingIntentMaybeOnSubscribe.kt b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/geofence/RemoveGeofenceByPendingIntentMaybeOnSubscribe.kt new file mode 100644 index 00000000..1b8b591a --- /dev/null +++ b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/geofence/RemoveGeofenceByPendingIntentMaybeOnSubscribe.kt @@ -0,0 +1,29 @@ +package pl.charmas.android.reactivelocation2.observables.geofence + +import android.app.PendingIntent +import com.google.android.gms.common.api.GoogleApiClient +import com.google.android.gms.location.GeofencingClient +import io.reactivex.MaybeEmitter +import pl.charmas.android.reactivelocation2.observables.MaybeContext + +class RemoveGeofenceByPendingIntentMaybeOnSubscribe( + ctx: MaybeContext, + private val pendingIntent: PendingIntent, + private val geofencingClient: GeofencingClient +) : RemoveGeofenceMaybeOnSubscribe(ctx) { + + override fun removeGeofences( + locationClient: GoogleApiClient, + emitter: MaybeEmitter + ) { + geofencingClient.removeGeofences(pendingIntent) + .addOnSuccessListener { + if (emitter.isDisposed) return@addOnSuccessListener + emitter.onSuccess(true) + } + .addOnFailureListener { error -> + if (emitter.isDisposed) return@addOnFailureListener + emitter.onError(error) + } + } +} \ No newline at end of file diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/geofence/RemoveGeofenceByPendingIntentObservableOnSubscribe.java b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/geofence/RemoveGeofenceByPendingIntentObservableOnSubscribe.java deleted file mode 100644 index 0e814d44..00000000 --- a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/geofence/RemoveGeofenceByPendingIntentObservableOnSubscribe.java +++ /dev/null @@ -1,40 +0,0 @@ -package pl.charmas.android.reactivelocation2.observables.geofence; - -import android.app.PendingIntent; -import androidx.annotation.NonNull; - -import com.google.android.gms.common.api.GoogleApiClient; -import com.google.android.gms.common.api.ResultCallback; -import com.google.android.gms.common.api.Status; -import com.google.android.gms.location.LocationServices; - -import io.reactivex.ObservableEmitter; -import pl.charmas.android.reactivelocation2.observables.ObservableContext; -import pl.charmas.android.reactivelocation2.observables.StatusException; - - -class RemoveGeofenceByPendingIntentObservableOnSubscribe extends RemoveGeofenceObservableOnSubscribe { - private final PendingIntent pendingIntent; - - RemoveGeofenceByPendingIntentObservableOnSubscribe(ObservableContext ctx, PendingIntent pendingIntent) { - super(ctx); - this.pendingIntent = pendingIntent; - } - - @Override - protected void removeGeofences(GoogleApiClient locationClient, final ObservableEmitter emitter) { - LocationServices.GeofencingApi.removeGeofences(locationClient, pendingIntent) - .setResultCallback(new ResultCallback() { - @Override - public void onResult(@NonNull Status status) { - if (emitter.isDisposed()) return; - if (status.isSuccess()) { - emitter.onNext(status); - emitter.onComplete(); - } else { - emitter.onError(new StatusException(status)); - } - } - }); - } -} diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/geofence/RemoveGeofenceMaybeOnSubscribe.kt b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/geofence/RemoveGeofenceMaybeOnSubscribe.kt new file mode 100644 index 00000000..0f01dbdd --- /dev/null +++ b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/geofence/RemoveGeofenceMaybeOnSubscribe.kt @@ -0,0 +1,58 @@ +package pl.charmas.android.reactivelocation2.observables.geofence + +import android.app.PendingIntent +import com.google.android.gms.common.api.GoogleApiClient +import com.google.android.gms.location.GeofencingClient +import io.reactivex.Maybe +import io.reactivex.MaybeEmitter +import pl.charmas.android.reactivelocation2.observables.BaseLocationMaybeOnSubscribe +import pl.charmas.android.reactivelocation2.observables.MaybeContext +import pl.charmas.android.reactivelocation2.observables.MaybeFactory + +abstract class RemoveGeofenceMaybeOnSubscribe internal constructor(ctx: MaybeContext) : + BaseLocationMaybeOnSubscribe(ctx) { + + override fun onGoogleApiClientReady( + apiClient: GoogleApiClient, + emitter: MaybeEmitter + ) { + removeGeofences(apiClient, emitter) + } + + protected abstract fun removeGeofences( + locationClient: GoogleApiClient, + emitter: MaybeEmitter + ) + + companion object { + fun createMaybe( + ctx: MaybeContext, + factory: MaybeFactory, + pendingIntent: PendingIntent, + geofencingClient: GeofencingClient + ): Maybe { + return factory.create( + RemoveGeofenceByPendingIntentMaybeOnSubscribe( + ctx, + pendingIntent, + geofencingClient + ) + ) + } + + fun createMaybe( + ctx: MaybeContext, + factory: MaybeFactory, + requestIds: List, + geofencingClient: GeofencingClient + ): Maybe { + return factory.create( + RemoveGeofenceRequestIdsMaybeOnSubscribe( + ctx, + requestIds, + geofencingClient + ) + ) + } + } +} \ No newline at end of file diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/geofence/RemoveGeofenceObservableOnSubscribe.java b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/geofence/RemoveGeofenceObservableOnSubscribe.java deleted file mode 100644 index a5c4db0d..00000000 --- a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/geofence/RemoveGeofenceObservableOnSubscribe.java +++ /dev/null @@ -1,38 +0,0 @@ -package pl.charmas.android.reactivelocation2.observables.geofence; - -import android.app.PendingIntent; - -import com.google.android.gms.common.api.GoogleApiClient; -import com.google.android.gms.common.api.Status; - -import java.util.List; - -import io.reactivex.Observable; -import io.reactivex.ObservableEmitter; -import pl.charmas.android.reactivelocation2.observables.BaseLocationObservableOnSubscribe; -import pl.charmas.android.reactivelocation2.observables.ObservableContext; -import pl.charmas.android.reactivelocation2.observables.ObservableFactory; - - -public abstract class RemoveGeofenceObservableOnSubscribe extends BaseLocationObservableOnSubscribe { - - public static Observable createObservable(ObservableContext ctx, ObservableFactory factory, PendingIntent pendingIntent) { - return factory.createObservable(new RemoveGeofenceByPendingIntentObservableOnSubscribe(ctx, pendingIntent)); - } - - public static Observable createObservable(ObservableContext ctx, ObservableFactory factory, List requestIds) { - return factory.createObservable(new RemoveGeofenceRequestIdsObservableOnSubscribe(ctx, requestIds)); - } - - RemoveGeofenceObservableOnSubscribe(ObservableContext ctx) { - super(ctx); - } - - @Override - protected void onGoogleApiClientReady(GoogleApiClient apiClient, final ObservableEmitter emitter) { - removeGeofences(apiClient, emitter); - } - - protected abstract void removeGeofences(GoogleApiClient locationClient, ObservableEmitter emitter); - -} diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/geofence/RemoveGeofenceRequestIdsMaybeOnSubscribe.kt b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/geofence/RemoveGeofenceRequestIdsMaybeOnSubscribe.kt new file mode 100644 index 00000000..8b7f2b2b --- /dev/null +++ b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/geofence/RemoveGeofenceRequestIdsMaybeOnSubscribe.kt @@ -0,0 +1,28 @@ +package pl.charmas.android.reactivelocation2.observables.geofence + +import com.google.android.gms.common.api.GoogleApiClient +import com.google.android.gms.location.GeofencingClient +import io.reactivex.MaybeEmitter +import pl.charmas.android.reactivelocation2.observables.MaybeContext + +internal class RemoveGeofenceRequestIdsMaybeOnSubscribe constructor( + ctx: MaybeContext, + private val geofenceRequestIds: List, + private val geofencingClient: GeofencingClient +) : RemoveGeofenceMaybeOnSubscribe(ctx) { + + override fun removeGeofences( + locationClient: GoogleApiClient, + emitter: MaybeEmitter + ) { + geofencingClient.removeGeofences(geofenceRequestIds) + .addOnSuccessListener { + if (emitter.isDisposed) return@addOnSuccessListener + emitter.onSuccess(true) + } + .addOnFailureListener { error -> + if (emitter.isDisposed) return@addOnFailureListener + emitter.onError(error) + } + } +} \ No newline at end of file diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/geofence/RemoveGeofenceRequestIdsObservableOnSubscribe.java b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/geofence/RemoveGeofenceRequestIdsObservableOnSubscribe.java deleted file mode 100644 index 6f84035a..00000000 --- a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/geofence/RemoveGeofenceRequestIdsObservableOnSubscribe.java +++ /dev/null @@ -1,39 +0,0 @@ -package pl.charmas.android.reactivelocation2.observables.geofence; - -import androidx.annotation.NonNull; -import com.google.android.gms.common.api.GoogleApiClient; -import com.google.android.gms.common.api.ResultCallback; -import com.google.android.gms.common.api.Status; -import com.google.android.gms.location.LocationServices; -import io.reactivex.ObservableEmitter; -import pl.charmas.android.reactivelocation2.observables.ObservableContext; -import pl.charmas.android.reactivelocation2.observables.StatusException; - -import java.util.List; - - -class RemoveGeofenceRequestIdsObservableOnSubscribe extends RemoveGeofenceObservableOnSubscribe { - private final List geofenceRequestIds; - - RemoveGeofenceRequestIdsObservableOnSubscribe(ObservableContext ctx, List geofenceRequestIds) { - super(ctx); - this.geofenceRequestIds = geofenceRequestIds; - } - - @Override - protected void removeGeofences(GoogleApiClient locationClient, final ObservableEmitter emitter) { - LocationServices.GeofencingApi.removeGeofences(locationClient, geofenceRequestIds) - .setResultCallback(new ResultCallback() { - @Override - public void onResult(@NonNull Status status) { - if (emitter.isDisposed()) return; - if (status.isSuccess()) { - emitter.onNext(status); - emitter.onComplete(); - } else { - emitter.onError(new StatusException(status)); - } - } - }); - } -} diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/location/AddLocationIntentUpdatesMaybeOnSubscribe.kt b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/location/AddLocationIntentUpdatesMaybeOnSubscribe.kt new file mode 100644 index 00000000..43cca943 --- /dev/null +++ b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/location/AddLocationIntentUpdatesMaybeOnSubscribe.kt @@ -0,0 +1,55 @@ +package pl.charmas.android.reactivelocation2.observables.location + +import android.app.PendingIntent +import androidx.annotation.RequiresPermission +import com.google.android.gms.common.api.GoogleApiClient +import com.google.android.gms.location.FusedLocationProviderClient +import com.google.android.gms.location.LocationRequest +import io.reactivex.Maybe +import io.reactivex.MaybeEmitter +import pl.charmas.android.reactivelocation2.observables.BaseLocationMaybeOnSubscribe +import pl.charmas.android.reactivelocation2.observables.MaybeContext +import pl.charmas.android.reactivelocation2.observables.MaybeFactory + +class AddLocationIntentUpdatesMaybeOnSubscribe private constructor( + ctx: MaybeContext, + private val fusedLocationProviderClient: FusedLocationProviderClient, + private val locationRequest: LocationRequest, + private val intent: PendingIntent +) : BaseLocationMaybeOnSubscribe(ctx) { + + @RequiresPermission("android.permission.ACCESS_FINE_LOCATION") + override fun onGoogleApiClientReady( + apiClient: GoogleApiClient, + emitter: MaybeEmitter + ) { + fusedLocationProviderClient.requestLocationUpdates(locationRequest, intent) + .addOnSuccessListener { + if (emitter.isDisposed) return@addOnSuccessListener + emitter.onSuccess(true) + } + .addOnFailureListener { error -> + if (emitter.isDisposed) return@addOnFailureListener + emitter.onError(error) + } + } + + companion object { + fun create( + fusedLocationProviderClient: FusedLocationProviderClient, + ctx: MaybeContext, + factory: MaybeFactory, + locationRequest: LocationRequest, + intent: PendingIntent + ): Maybe { + return factory.create( + AddLocationIntentUpdatesMaybeOnSubscribe( + ctx, + fusedLocationProviderClient, + locationRequest, + intent + ) + ) + } + } +} \ No newline at end of file diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/location/AddLocationIntentUpdatesObservableOnSubscribe.java b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/location/AddLocationIntentUpdatesObservableOnSubscribe.java deleted file mode 100644 index 2f4d60e5..00000000 --- a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/location/AddLocationIntentUpdatesObservableOnSubscribe.java +++ /dev/null @@ -1,52 +0,0 @@ -package pl.charmas.android.reactivelocation2.observables.location; - -import android.app.PendingIntent; -import androidx.annotation.NonNull; - -import com.google.android.gms.common.api.GoogleApiClient; -import com.google.android.gms.common.api.ResultCallback; -import com.google.android.gms.common.api.Status; -import com.google.android.gms.location.LocationRequest; -import com.google.android.gms.location.LocationServices; - -import io.reactivex.Observable; -import io.reactivex.ObservableEmitter; -import pl.charmas.android.reactivelocation2.observables.BaseLocationObservableOnSubscribe; -import pl.charmas.android.reactivelocation2.observables.ObservableContext; -import pl.charmas.android.reactivelocation2.observables.ObservableFactory; -import pl.charmas.android.reactivelocation2.observables.StatusException; - - -@SuppressWarnings("MissingPermission") -public class AddLocationIntentUpdatesObservableOnSubscribe extends BaseLocationObservableOnSubscribe { - private final LocationRequest locationRequest; - private final PendingIntent intent; - - public static Observable createObservable(ObservableContext ctx, ObservableFactory factory, LocationRequest locationRequest, PendingIntent intent) { - return factory.createObservable(new AddLocationIntentUpdatesObservableOnSubscribe(ctx, locationRequest, intent)); - } - - private AddLocationIntentUpdatesObservableOnSubscribe(ObservableContext ctx, LocationRequest locationRequest, PendingIntent intent) { - super(ctx); - this.locationRequest = locationRequest; - this.intent = intent; - } - - @Override - protected void onGoogleApiClientReady(GoogleApiClient apiClient, final ObservableEmitter emitter) { - LocationServices.FusedLocationApi.requestLocationUpdates(apiClient, locationRequest, intent) - .setResultCallback(new ResultCallback() { - @Override - public void onResult(@NonNull Status status) { - if (emitter.isDisposed()) return; - if (!status.isSuccess()) { - emitter.onError(new StatusException(status)); - } else { - emitter.onNext(status); - emitter.onComplete(); - } - } - }); - - } -} diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/location/LastKnownLocationObservableOnSubscribe.java b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/location/LastKnownLocationObservableOnSubscribe.java deleted file mode 100644 index 3a6fef6a..00000000 --- a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/location/LastKnownLocationObservableOnSubscribe.java +++ /dev/null @@ -1,33 +0,0 @@ -package pl.charmas.android.reactivelocation2.observables.location; - -import android.location.Location; - -import com.google.android.gms.common.api.GoogleApiClient; -import com.google.android.gms.location.LocationServices; - -import io.reactivex.Observable; -import io.reactivex.ObservableEmitter; -import pl.charmas.android.reactivelocation2.observables.BaseLocationObservableOnSubscribe; -import pl.charmas.android.reactivelocation2.observables.ObservableContext; -import pl.charmas.android.reactivelocation2.observables.ObservableFactory; - -@SuppressWarnings("MissingPermission") -public class LastKnownLocationObservableOnSubscribe extends BaseLocationObservableOnSubscribe { - public static Observable createObservable(ObservableContext ctx, ObservableFactory factory) { - return factory.createObservable(new LastKnownLocationObservableOnSubscribe(ctx)); - } - - private LastKnownLocationObservableOnSubscribe(ObservableContext ctx) { - super(ctx); - } - - @Override - protected void onGoogleApiClientReady(GoogleApiClient apiClient, ObservableEmitter emitter) { - Location location = LocationServices.FusedLocationApi.getLastLocation(apiClient); - if (emitter.isDisposed()) return; - if (location != null) { - emitter.onNext(location); - } - emitter.onComplete(); - } -} diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/location/LocationUpdatesObservableOnSubscribe.java b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/location/LocationUpdatesObservableOnSubscribe.java deleted file mode 100644 index 2a4ab975..00000000 --- a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/location/LocationUpdatesObservableOnSubscribe.java +++ /dev/null @@ -1,64 +0,0 @@ -package pl.charmas.android.reactivelocation2.observables.location; - -import android.location.Location; -import com.google.android.gms.common.api.GoogleApiClient; -import com.google.android.gms.location.LocationListener; -import com.google.android.gms.location.LocationRequest; -import com.google.android.gms.location.LocationServices; -import io.reactivex.Observable; -import io.reactivex.ObservableEmitter; -import pl.charmas.android.reactivelocation2.observables.BaseLocationObservableOnSubscribe; -import pl.charmas.android.reactivelocation2.observables.ObservableContext; -import pl.charmas.android.reactivelocation2.observables.ObservableFactory; - -import java.lang.ref.WeakReference; - - -@SuppressWarnings("MissingPermission") -public class LocationUpdatesObservableOnSubscribe extends BaseLocationObservableOnSubscribe { - public static Observable createObservable(ObservableContext ctx, ObservableFactory factory, LocationRequest locationRequest) { - Observable observable = factory.createObservable(new LocationUpdatesObservableOnSubscribe(ctx, locationRequest)); - int requestedNumberOfUpdates = locationRequest.getNumUpdates(); - if (requestedNumberOfUpdates > 0 && requestedNumberOfUpdates < Integer.MAX_VALUE) { - observable = observable.take(requestedNumberOfUpdates); - } - return observable; - } - - private final LocationRequest locationRequest; - private LocationListener listener; - - private LocationUpdatesObservableOnSubscribe(ObservableContext ctx, LocationRequest locationRequest) { - super(ctx); - this.locationRequest = locationRequest; - } - - @Override - protected void onGoogleApiClientReady(GoogleApiClient apiClient, final ObservableEmitter emitter) { - listener = new LocationUpdatesLocationListener(emitter); - LocationServices.FusedLocationApi.requestLocationUpdates(apiClient, locationRequest, listener); - } - - @Override - protected void onDisposed(GoogleApiClient locationClient) { - if (locationClient.isConnected()) { - LocationServices.FusedLocationApi.removeLocationUpdates(locationClient, listener); - } - } - - private static class LocationUpdatesLocationListener implements LocationListener { - private final WeakReference> weakRef; - - LocationUpdatesLocationListener(ObservableEmitter emitter) { - this.weakRef = new WeakReference<>(emitter); - } - - @Override - public void onLocationChanged(Location location) { - final ObservableEmitter observer = weakRef.get(); - if (observer != null && !observer.isDisposed()) { - observer.onNext(location); - } - } - } -} diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/location/LocationUpdatesObservableOnSubscribe.kt b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/location/LocationUpdatesObservableOnSubscribe.kt new file mode 100644 index 00000000..ab7160c3 --- /dev/null +++ b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/location/LocationUpdatesObservableOnSubscribe.kt @@ -0,0 +1,85 @@ +package pl.charmas.android.reactivelocation2.observables.location + +import android.annotation.SuppressLint +import android.location.Location +import com.google.android.gms.common.api.GoogleApiClient +import com.google.android.gms.location.FusedLocationProviderClient +import com.google.android.gms.location.LocationCallback +import com.google.android.gms.location.LocationRequest +import com.google.android.gms.location.LocationResult +import io.reactivex.Observable +import io.reactivex.ObservableEmitter +import pl.charmas.android.reactivelocation2.observables.BaseLocationObservableOnSubscribe +import pl.charmas.android.reactivelocation2.observables.ObservableContext +import pl.charmas.android.reactivelocation2.observables.ObservableFactory +import java.lang.ref.WeakReference + +class LocationUpdatesObservableOnSubscribe private constructor( + private val fusedLocationProviderClient: FusedLocationProviderClient, + ctx: ObservableContext, + private val locationRequest: LocationRequest +) : BaseLocationObservableOnSubscribe(ctx) { + private var listener: LocationCallback? = null + + @SuppressLint("MissingPermission") + override fun onGoogleApiClientReady( + apiClient: GoogleApiClient, + emitter: ObservableEmitter + ) { + listener = LocationUpdatesLocationListener(emitter) + fusedLocationProviderClient + .requestLocationUpdates( + locationRequest, + listener, + null + ) + } + + override fun onDisposed(locationClient: GoogleApiClient) { + if (locationClient.isConnected) { + fusedLocationProviderClient.removeLocationUpdates(listener) + } + } + + private class LocationUpdatesLocationListener internal constructor(emitter: ObservableEmitter) : + LocationCallback() { + + private val weakRef: WeakReference> = WeakReference(emitter) + + override fun onLocationResult(locationResult: LocationResult?) { + super.onLocationResult(locationResult) + val observer = weakRef.get() + val locations = locationResult?.locations ?: emptyList() + for (item in locations) { + if (observer != null && !observer.isDisposed && item != null) { + observer.onNext(item) + } + } + + locationResult?.lastLocation?.let {item-> + if (observer != null && !observer.isDisposed) { + observer.onNext(item) + } + } + } + } + + companion object { + fun createObservable( + fusedLocationProviderClient: FusedLocationProviderClient, + ctx: ObservableContext, + factory: ObservableFactory, + locationRequest: LocationRequest + ): Observable { + var observable = + factory.create( + LocationUpdatesObservableOnSubscribe(fusedLocationProviderClient, ctx, locationRequest) + ) + val requestedNumberOfUpdates = locationRequest.numUpdates + if (requestedNumberOfUpdates > 0 && requestedNumberOfUpdates < Int.MAX_VALUE) { + observable = observable.take(requestedNumberOfUpdates.toLong()) + } + return observable + } + } +} \ No newline at end of file diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/location/MockLocationObservableOnSubscribe.java b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/location/MockLocationObservableOnSubscribe.java deleted file mode 100644 index cfd389ad..00000000 --- a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/location/MockLocationObservableOnSubscribe.java +++ /dev/null @@ -1,100 +0,0 @@ -package pl.charmas.android.reactivelocation2.observables.location; - -import android.location.Location; -import androidx.annotation.NonNull; - -import com.google.android.gms.common.api.GoogleApiClient; -import com.google.android.gms.common.api.ResultCallback; -import com.google.android.gms.common.api.Status; -import com.google.android.gms.location.LocationServices; - -import io.reactivex.Observable; -import io.reactivex.ObservableEmitter; -import io.reactivex.disposables.Disposable; -import io.reactivex.functions.Action; -import io.reactivex.functions.Consumer; -import pl.charmas.android.reactivelocation2.observables.BaseLocationObservableOnSubscribe; -import pl.charmas.android.reactivelocation2.observables.ObservableContext; -import pl.charmas.android.reactivelocation2.observables.ObservableFactory; -import pl.charmas.android.reactivelocation2.observables.StatusException; - -@SuppressWarnings("MissingPermission") -public class MockLocationObservableOnSubscribe extends BaseLocationObservableOnSubscribe { - private final Observable locationObservable; - private Disposable mockLocationSubscription; - - public static Observable createObservable(ObservableContext context, ObservableFactory factory, Observable locationObservable) { - return factory.createObservable(new MockLocationObservableOnSubscribe(context, locationObservable)); - } - - private MockLocationObservableOnSubscribe(ObservableContext ctx, Observable locationObservable) { - super(ctx); - this.locationObservable = locationObservable; - } - - @Override - protected void onGoogleApiClientReady(final GoogleApiClient apiClient, final ObservableEmitter emitter) { - // this throws SecurityException if permissions are bad or mock locations are not enabled, - // which is passed to observer's onError by BaseObservable - LocationServices.FusedLocationApi.setMockMode(apiClient, true) - .setResultCallback(new ResultCallback() { - @Override - public void onResult(@NonNull Status status) { - if (status.isSuccess()) { - startLocationMocking(apiClient, emitter); - } else { - emitter.onError(new StatusException(status)); - } - } - }); - } - - private void startLocationMocking(final GoogleApiClient apiClient, final ObservableEmitter emitter) { - mockLocationSubscription = locationObservable - .subscribe(new Consumer() { - @Override - public void accept(Location location) throws Exception { - LocationServices.FusedLocationApi.setMockLocation(apiClient, location) - .setResultCallback(new ResultCallback() { - @Override - public void onResult(@NonNull Status status) { - if (status.isSuccess()) { - emitter.onNext(status); - } else { - emitter.onError(new StatusException(status)); - } - } - }); - } - }, - - new Consumer() { - @Override - public void accept(Throwable throwable) throws Exception { - emitter.onError(throwable); - } - - }, new Action() { - @Override - public void run() throws Exception { - emitter.onComplete(); - } - }); - - } - - @Override - protected void onDisposed(GoogleApiClient locationClient) { - if (locationClient.isConnected()) { - try { - LocationServices.FusedLocationApi.setMockMode(locationClient, false); - } catch (SecurityException e) { - // if this happens then we couldn't have switched mock mode on in the first place, - // and the observer's onError will already have been called - } - } - if (mockLocationSubscription != null && !mockLocationSubscription.isDisposed()) { - mockLocationSubscription.dispose(); - } - } -} diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/location/MockLocationObservableOnSubscribe.kt b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/location/MockLocationObservableOnSubscribe.kt new file mode 100644 index 00000000..6fc5d840 --- /dev/null +++ b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/location/MockLocationObservableOnSubscribe.kt @@ -0,0 +1,94 @@ +package pl.charmas.android.reactivelocation2.observables.location + +import android.annotation.SuppressLint +import android.location.Location +import com.google.android.gms.common.api.GoogleApiClient +import com.google.android.gms.location.FusedLocationProviderClient +import io.reactivex.Observable +import io.reactivex.ObservableEmitter +import io.reactivex.disposables.Disposable +import pl.charmas.android.reactivelocation2.observables.BaseLocationObservableOnSubscribe +import pl.charmas.android.reactivelocation2.observables.ObservableContext +import pl.charmas.android.reactivelocation2.observables.ObservableFactory + +class MockLocationObservableOnSubscribe private constructor( + private val fusedLocationProviderClient: FusedLocationProviderClient, + ctx: ObservableContext, + private val locationObservable: Observable +) : BaseLocationObservableOnSubscribe(ctx) { + private var mockLocationSubscription: Disposable? = null + + @SuppressLint("MissingPermission") + protected override fun onGoogleApiClientReady( + apiClient: GoogleApiClient, + emitter: ObservableEmitter + ) { + // this throws SecurityException if permissions are bad or mock locations are not enabled, + // which is passed to observer's onError by BaseObservable + fusedLocationProviderClient.setMockMode(true) + .addOnSuccessListener { sd: Void? -> + startLocationMocking( + emitter + ) + } + .addOnFailureListener { sd: Exception? -> + if (emitter.isDisposed) return@addOnFailureListener + emitter.onError(sd!!) + } + } + + @SuppressLint("MissingPermission") + private fun startLocationMocking(emitter: ObservableEmitter) { + mockLocationSubscription = locationObservable + .subscribe({ location -> + fusedLocationProviderClient.setMockLocation(location) + .addOnSuccessListener { d: Void? -> + if (emitter.isDisposed) return@addOnSuccessListener + emitter.onNext(true) + } + .addOnFailureListener { error: Exception? -> + if (emitter.isDisposed) return@addOnFailureListener + emitter.onError(error!!) + } + }, + { throwable: Throwable? -> + if (emitter.isDisposed) return@subscribe + emitter.onError(throwable!!) + }) { + if (emitter.isDisposed) return@subscribe + emitter.onComplete() + } + } + + @SuppressLint("MissingPermission") + override fun onDisposed(locationClient: GoogleApiClient) { + if (locationClient.isConnected) { + try { + fusedLocationProviderClient.setMockMode(false) + } catch (e: SecurityException) { + // if this happens then we couldn't have switched mock mode on in the first place, + // and the observer's onError will already have been called + } + } + if (mockLocationSubscription != null && !mockLocationSubscription!!.isDisposed) { + mockLocationSubscription!!.dispose() + } + } + + companion object { + fun create( + fusedLocationProviderClient: FusedLocationProviderClient, + context: ObservableContext, + factory: ObservableFactory, + locationObservable: Observable + ): Observable { + return factory.create( + MockLocationObservableOnSubscribe( + fusedLocationProviderClient, + context, + locationObservable + ) + ) + } + } +} \ No newline at end of file diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/location/RemoveLocationIntentUpdatesObservableOnSubscribe.java b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/location/RemoveLocationIntentUpdatesObservableOnSubscribe.java deleted file mode 100644 index ddf8351b..00000000 --- a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/location/RemoveLocationIntentUpdatesObservableOnSubscribe.java +++ /dev/null @@ -1,47 +0,0 @@ -package pl.charmas.android.reactivelocation2.observables.location; - -import android.app.PendingIntent; -import androidx.annotation.NonNull; - -import com.google.android.gms.common.api.GoogleApiClient; -import com.google.android.gms.common.api.ResultCallback; -import com.google.android.gms.common.api.Status; -import com.google.android.gms.location.LocationServices; - -import io.reactivex.Observable; -import io.reactivex.ObservableEmitter; -import pl.charmas.android.reactivelocation2.observables.BaseLocationObservableOnSubscribe; -import pl.charmas.android.reactivelocation2.observables.ObservableContext; -import pl.charmas.android.reactivelocation2.observables.ObservableFactory; -import pl.charmas.android.reactivelocation2.observables.StatusException; - - -public class RemoveLocationIntentUpdatesObservableOnSubscribe extends BaseLocationObservableOnSubscribe { - private final PendingIntent intent; - - public static Observable createObservable(ObservableContext ctx, ObservableFactory factory, PendingIntent intent) { - return factory.createObservable(new RemoveLocationIntentUpdatesObservableOnSubscribe(ctx, intent)); - } - - private RemoveLocationIntentUpdatesObservableOnSubscribe(ObservableContext ctx, PendingIntent intent) { - super(ctx); - this.intent = intent; - } - - @Override - protected void onGoogleApiClientReady(GoogleApiClient apiClient, final ObservableEmitter emitter) { - LocationServices.FusedLocationApi.removeLocationUpdates(apiClient, intent) - .setResultCallback(new ResultCallback() { - @Override - public void onResult(@NonNull Status status) { - if (emitter.isDisposed()) return; - if (status.isSuccess()) { - emitter.onNext(status); - emitter.onComplete(); - } else { - emitter.onError(new StatusException(status)); - } - } - }); - } -} diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/location/RemoveLocationIntentUpdatesObservableOnSubscribe.kt b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/location/RemoveLocationIntentUpdatesObservableOnSubscribe.kt new file mode 100644 index 00000000..517384e1 --- /dev/null +++ b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/location/RemoveLocationIntentUpdatesObservableOnSubscribe.kt @@ -0,0 +1,48 @@ +package pl.charmas.android.reactivelocation2.observables.location + +import android.app.PendingIntent +import com.google.android.gms.common.api.GoogleApiClient +import com.google.android.gms.location.FusedLocationProviderClient +import io.reactivex.Maybe +import io.reactivex.MaybeEmitter +import pl.charmas.android.reactivelocation2.observables.BaseLocationMaybeOnSubscribe +import pl.charmas.android.reactivelocation2.observables.MaybeContext +import pl.charmas.android.reactivelocation2.observables.MaybeFactory + +class RemoveLocationIntentUpdatesObservableOnSubscribe private constructor( + private val fusedLocationProviderClient: FusedLocationProviderClient, + ctx: MaybeContext, + private val intent: PendingIntent +) : BaseLocationMaybeOnSubscribe(ctx) { + override fun onGoogleApiClientReady( + apiClient: GoogleApiClient, + emitter: MaybeEmitter + ) { + fusedLocationProviderClient.removeLocationUpdates(intent) + .addOnCompleteListener { + if (emitter.isDisposed) return@addOnCompleteListener + emitter.onSuccess(true) + } + .addOnFailureListener { error -> + if (emitter.isDisposed) return@addOnFailureListener + emitter.onError(error) + } + } + + companion object { + fun create( + fusedLocationProviderClient: FusedLocationProviderClient, + ctx: MaybeContext, + factory: MaybeFactory, + intent: PendingIntent + ): Maybe { + return factory.create( + RemoveLocationIntentUpdatesObservableOnSubscribe( + fusedLocationProviderClient, + ctx, + intent + ) + ) + } + } +} \ No newline at end of file diff --git a/build.gradle b/build.gradle index db40e817..d81a3a75 100644 --- a/build.gradle +++ b/build.gradle @@ -8,7 +8,7 @@ buildscript { google() } dependencies { - classpath 'com.android.tools.build:gradle:4.0.0' + classpath 'com.android.tools.build:gradle:4.0.1' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } diff --git a/sample/build.gradle b/sample/build.gradle index 3c7dfe59..2cf35d70 100755 --- a/sample/build.gradle +++ b/sample/build.gradle @@ -4,7 +4,7 @@ apply plugin: 'kotlin-android-extensions' def getGooglePlayServicesApiKey() { if (REACTIVE_LOCATION_GMS_API_KEY != null && !REACTIVE_LOCATION_GMS_API_KEY.isEmpty()) { - return REACTIVE_LOCATION_GMS_API_KEY; + return REACTIVE_LOCATION_GMS_API_KEY } return "" } @@ -38,12 +38,12 @@ android { dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" - implementation "androidx.core:core-ktx:1.3.0" + implementation "androidx.core:core-ktx:1.3.1" implementation 'androidx.appcompat:appcompat:1.1.0' - implementation ('com.google.android.gms:play-services-location:17.0.0'){ - exclude group: 'com.google.android.gms', module: 'play-services-places' + implementation('com.google.android.gms:play-services-location:17.0.0') { + exclude group: 'com.google.android.gms', module: 'play-services-places' } implementation 'com.google.android.libraries.places:places:2.3.0' implementation 'io.reactivex.rxjava2:rxjava:2.2.19' diff --git a/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/GeofenceActivity.java b/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/GeofenceActivity.java index 3031a4e7..092a027b 100755 --- a/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/GeofenceActivity.java +++ b/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/GeofenceActivity.java @@ -1,20 +1,16 @@ package pl.charmas.android.reactivelocation2.sample; +import android.annotation.SuppressLint; import android.app.PendingIntent; import android.content.Intent; import android.os.Bundle; import android.util.Log; -import android.view.View; import android.widget.EditText; import android.widget.TextView; import android.widget.Toast; -import com.google.android.gms.common.api.Status; import com.google.android.gms.location.Geofence; import com.google.android.gms.location.GeofencingRequest; -import io.reactivex.Observable; import io.reactivex.disposables.Disposable; -import io.reactivex.functions.Consumer; -import io.reactivex.functions.Function; import pl.charmas.android.reactivelocation2.ReactiveLocationProvider; import pl.charmas.android.reactivelocation2.sample.utils.DisplayTextOnViewAction; import pl.charmas.android.reactivelocation2.sample.utils.LocationToStringFunc; @@ -40,24 +36,15 @@ protected void onCreate(Bundle savedInstanceState) { } private void initViews() { - lastKnownLocationView = (TextView) findViewById(R.id.last_known_location_view); - latitudeInput = (EditText) findViewById(R.id.latitude_input); - longitudeInput = (EditText) findViewById(R.id.longitude_input); - radiusInput = (EditText) findViewById(R.id.radius_input); - findViewById(R.id.add_button).setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - addGeofence(); - } - }); - findViewById(R.id.clear_button).setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - clearGeofence(); - } - }); + lastKnownLocationView = findViewById(R.id.last_known_location_view); + latitudeInput = findViewById(R.id.latitude_input); + longitudeInput = findViewById(R.id.longitude_input); + radiusInput = findViewById(R.id.radius_input); + findViewById(R.id.add_button).setOnClickListener(v -> addGeofence()); + findViewById(R.id.clear_button).setOnClickListener(v -> clearGeofence()); } + @SuppressLint("MissingPermission") @Override protected void onLocationPermissionGranted() { lastKnownLocationDisposable = reactiveLocationProvider @@ -73,20 +60,13 @@ protected void onStop() { } private void clearGeofence() { - reactiveLocationProvider + Disposable subscribe = reactiveLocationProvider .removeGeofences(createNotificationBroadcastPendingIntent()) - .subscribe(new Consumer() { - @Override - public void accept(Status status) throws Exception { - toast("Geofences removed"); - } - }, new Consumer() { - @Override - public void accept(Throwable throwable) throws Exception { - toast("Error removing geofences"); - Log.d(TAG, "Error removing geofences", throwable); - } - }); + .subscribe( + status -> toast("Geofences removed"), throwable -> { + toast("Error removing geofences"); + Log.d(TAG, "Error removing geofences", throwable); + }); } private void toast(String text) { @@ -97,33 +77,21 @@ private PendingIntent createNotificationBroadcastPendingIntent() { return PendingIntent.getBroadcast(this, 0, new Intent(this, GeofenceBroadcastReceiver.class), PendingIntent.FLAG_UPDATE_CURRENT); } + @SuppressLint("MissingPermission") private void addGeofence() { final GeofencingRequest geofencingRequest = createGeofencingRequest(); if (geofencingRequest == null) return; final PendingIntent pendingIntent = createNotificationBroadcastPendingIntent(); - reactiveLocationProvider + Disposable subscribe = reactiveLocationProvider .removeGeofences(pendingIntent) - .flatMap(new Function>() { - @Override - public Observable apply(Status status) throws Exception { - return reactiveLocationProvider.addGeofences(pendingIntent, geofencingRequest); - } - - }) - .subscribe(new Consumer() { - @Override - public void accept(Status addGeofenceResult) { - toast("Geofence added, success: " + addGeofenceResult.isSuccess()); - } - }, new Consumer() { - @Override - public void accept(Throwable throwable) { - toast("Error adding geofence."); - Log.d(TAG, "Error adding geofence.", throwable); - } - }); + .flatMap(status -> reactiveLocationProvider.addGeofences(pendingIntent, geofencingRequest)) + .subscribe(addGeofenceResult -> toast("Geofence added, success: " + addGeofenceResult) + , throwable -> { + toast("Error adding geofence."); + Log.d(TAG, "Error adding geofence.", throwable); + }); } private GeofencingRequest createGeofencingRequest() { diff --git a/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/MainActivity.java b/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/MainActivity.java index db690418..253b1bbf 100755 --- a/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/MainActivity.java +++ b/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/MainActivity.java @@ -11,13 +11,15 @@ import android.view.Menu; import android.widget.TextView; import android.widget.Toast; -import com.google.android.gms.common.api.Status; +import com.google.android.gms.common.api.ApiException; +import com.google.android.gms.common.api.ResolvableApiException; import com.google.android.gms.location.ActivityRecognitionResult; import com.google.android.gms.location.LocationRequest; import com.google.android.gms.location.LocationSettingsRequest; -import com.google.android.gms.location.LocationSettingsResult; +import com.google.android.gms.location.LocationSettingsResponse; import com.google.android.gms.location.LocationSettingsStates; import com.google.android.gms.location.LocationSettingsStatusCodes; +import io.reactivex.Maybe; import io.reactivex.Observable; import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.disposables.Disposable; @@ -47,7 +49,7 @@ public class MainActivity extends BaseActivity { private TextView addressLocationView; private TextView currentActivityView; - private Observable lastKnownLocationObservable; + private Maybe lastKnownLocationObservable; private Observable locationUpdatesObservable; private Observable activityObservable; @@ -89,17 +91,30 @@ protected void onCreate(Bundle savedInstanceState) { .setAlwaysShow(true) //Refrence: http://stackoverflow.com/questions/29824408/google-play-services-locationservices-api-new-option-never .build() ) - .doOnNext(locationSettingsResult -> { - Status status = locationSettingsResult.getStatus(); - if (status.getStatusCode() == LocationSettingsStatusCodes.RESOLUTION_REQUIRED) { - try { - status.startResolutionForResult(MainActivity.this, REQUEST_CHECK_SETTINGS); - } catch (IntentSender.SendIntentException th) { - Log.e("MainActivity", "Error opening settings activity.", th); - } + .doOnSuccess(locationSettingsResponse -> { + Log.d("MainActivity", "getLocationSettingsStates isGpsUsable = " + locationSettingsResponse.getLocationSettingsStates().isGpsUsable()); + }) + .doOnError(throwable -> { + int statusCode = ((ApiException) throwable).getStatusCode(); + switch (statusCode) { + case LocationSettingsStatusCodes.RESOLUTION_REQUIRED: + try { + // Show the dialog by calling startResolutionForResult(), and check the + // result in onActivityResult(). + ResolvableApiException rae = (ResolvableApiException) throwable; + rae.startResolutionForResult(MainActivity.this, REQUEST_CHECK_SETTINGS); + } catch (IntentSender.SendIntentException sie) { + Log.i(TAG, "PendingIntent unable to execute request."); + } + break; + case LocationSettingsStatusCodes.SETTINGS_CHANGE_UNAVAILABLE: + String errorMessage = "Location settings are inadequate, and cannot be " + + "fixed here. Fix in Settings."; + Log.e(TAG, errorMessage); + Toast.makeText(MainActivity.this, errorMessage, Toast.LENGTH_LONG).show(); } }) - .flatMap((Function>) locationSettingsResult -> locationProvider.getUpdatedLocation(locationRequest)) + .flatMapObservable((Function>) locationSettingsResult -> locationProvider.getUpdatedLocation(locationRequest)) .observeOn(AndroidSchedulers.mainThread()); addressObservable = locationProvider.getUpdatedLocation(locationRequest) From 236493d81a8e723495992e4090a055cf437d9f45 Mon Sep 17 00:00:00 2001 From: Nikolay Unuchek Date: Thu, 30 Jul 2020 12:40:06 +0300 Subject: [PATCH 10/21] added apt key for requests updated classes to kotlin style --- .../ReactiveLocationProvider.kt | 13 +- .../observables/MaybeEmitterWrapper.kt | 28 ++++ .../ActivityUpdatesObservableOnSubscribe.java | 5 + .../FallbackReverseGeocodeObservable.java | 157 ------------------ .../FallbackReverseGeocodeObservable.kt | 154 +++++++++++++++++ .../geocode/ReverseGeocodeObservable.java | 57 ------- .../geocode/ReverseGeocodeObservable.kt | 64 +++++++ .../sample/GeofenceActivity.java | 2 +- .../sample/MainActivity.java | 6 +- .../sample/MockLocationsActivity.java | 12 +- .../sample/PlacesActivity.kt | 4 +- .../sample/PlacesResultActivity.kt | 4 +- 12 files changed, 272 insertions(+), 234 deletions(-) create mode 100644 android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/MaybeEmitterWrapper.kt delete mode 100644 android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/geocode/FallbackReverseGeocodeObservable.java create mode 100644 android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/geocode/FallbackReverseGeocodeObservable.kt delete mode 100644 android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/geocode/ReverseGeocodeObservable.java create mode 100644 android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/geocode/ReverseGeocodeObservable.kt diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/ReactiveLocationProvider.kt b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/ReactiveLocationProvider.kt index 47afb958..0a7333fa 100644 --- a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/ReactiveLocationProvider.kt +++ b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/ReactiveLocationProvider.kt @@ -61,6 +61,7 @@ class ReactiveLocationProvider @JvmOverloads constructor( val context: Context, + private val apiKey: String, configuration: ReactiveLocationProviderConfiguration = ReactiveLocationProviderConfiguration.builder() .build() ) { @@ -71,8 +72,7 @@ constructor( private val settingsClient = LocationServices.getSettingsClient(context) private val geofencingClient = LocationServices.getGeofencingClient(context) - private val fusedLocationProviderClient = - LocationServices.getFusedLocationProviderClient(context) + private val fusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(context) /** * Creates observable that obtains last known location and than completes. @@ -201,15 +201,16 @@ constructor( * @param maxResults maximal number of results you are interested in * @return observable that serves list of address based on location */ - fun getReverseGeocodeObservable( + fun getReverseGeocodeMaybe( locale: Locale = Locale.getDefault(), lat: Double, lng: Double, maxResults: Int - ): Observable> { - return ReverseGeocodeObservable.createObservable( + ): Maybe> { + return ReverseGeocodeObservable.create( ctxObservable.context, - factoryObservable, + apiKey, + factoryMaybe, locale, lat, lng, diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/MaybeEmitterWrapper.kt b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/MaybeEmitterWrapper.kt new file mode 100644 index 00000000..1b8bdd6f --- /dev/null +++ b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/MaybeEmitterWrapper.kt @@ -0,0 +1,28 @@ +package pl.charmas.android.reactivelocation2.observables + +import io.reactivex.MaybeEmitter +import io.reactivex.MaybeObserver +import io.reactivex.disposables.Disposable + +class MaybeEmitterWrapper(private val emitter: MaybeEmitter) : + MaybeObserver { + override fun onSubscribe(d: Disposable) {} + + override fun onError(e: Throwable) { + if (!emitter.isDisposed) { + emitter.onError(e) + } + } + + override fun onComplete() { + if (!emitter.isDisposed) { + emitter.onComplete() + } + } + + override fun onSuccess(value: T) { + if (!emitter.isDisposed) { + emitter.onSuccess(value) + } + } +} \ No newline at end of file diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/activity/ActivityUpdatesObservableOnSubscribe.java b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/activity/ActivityUpdatesObservableOnSubscribe.java index 4fd2e55c..10c32c70 100644 --- a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/activity/ActivityUpdatesObservableOnSubscribe.java +++ b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/activity/ActivityUpdatesObservableOnSubscribe.java @@ -16,7 +16,12 @@ import pl.charmas.android.reactivelocation2.observables.ObservableFactory; + +/** + * deprecated: need to update to new api of google com.google.android.gms.location.ActivityRecognition#getClient(android.app.Activity) + * */ @SuppressWarnings("MissingPermission") +@Deprecated public class ActivityUpdatesObservableOnSubscribe extends BaseActivityObservableOnSubscribe { private static final String ACTION_ACTIVITY_DETECTED = "pl.charmas.android.reactivelocation2.ACTION_ACTIVITY_UPDATE_DETECTED"; diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/geocode/FallbackReverseGeocodeObservable.java b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/geocode/FallbackReverseGeocodeObservable.java deleted file mode 100644 index 90ddd45e..00000000 --- a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/geocode/FallbackReverseGeocodeObservable.java +++ /dev/null @@ -1,157 +0,0 @@ -package pl.charmas.android.reactivelocation2.observables.geocode; - -import android.location.Address; -import android.text.TextUtils; - -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; -import java.net.HttpURLConnection; -import java.net.URL; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Locale; - -import io.reactivex.ObservableEmitter; -import io.reactivex.ObservableOnSubscribe; - -class FallbackReverseGeocodeObservable implements ObservableOnSubscribe> { - private final Locale locale; - private final double latitude; - private final double longitude; - private final int maxResults; - - FallbackReverseGeocodeObservable(Locale locale, double latitude, double longitude, int maxResults) { - this.locale = locale; - this.latitude = latitude; - this.longitude = longitude; - this.maxResults = maxResults; - } - - @Override - public void subscribe(ObservableEmitter> emitter) throws Exception { - try { - List
addresses = alternativeReverseGeocodeQuery(); - if (!emitter.isDisposed()) { - emitter.onNext(addresses); - emitter.onComplete(); - } - } catch (Exception ex) { - if (!emitter.isDisposed()) { - emitter.onError(ex); - } - } - } - - /** - * This function fetches a list of addresses for the set latitude, longitude and maxResults properties from the - * Google Geocode API (http://maps.googleapis.com/maps/api/geocode). - * - * @return List of addresses - * @throws IOException In case of network problems - * @throws JSONException In case of problems while parsing the json response from google geocode API servers - */ - private List
alternativeReverseGeocodeQuery() throws IOException, JSONException { - URL url = new URL(String.format(Locale.ENGLISH, - "http://maps.googleapis.com/maps/api/geocode/json?" - + "latlng=%1$f,%2$f&sensor=true&language=%3$s", - latitude, longitude, locale.getLanguage() - )); - HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection(); - StringBuilder stringBuilder = new StringBuilder(); - List
outResult = new ArrayList<>(); - - try { - BufferedReader reader = new BufferedReader(new InputStreamReader(urlConnection.getInputStream(), "UTF-8")); - String line; - while ((line = reader.readLine()) != null) { - stringBuilder.append(line); - } - - // Root json response object - JSONObject jsonRootObject = new JSONObject(stringBuilder.toString()); - - // No results status - if ("ZERO_RESULTS".equalsIgnoreCase(jsonRootObject.getString("status"))) { - return Collections.emptyList(); - } - - // Other non-OK responses status - if (!"OK".equalsIgnoreCase(jsonRootObject.getString("status"))) { - throw new RuntimeException("Wrong API response"); - } - - // Process results - JSONArray results = jsonRootObject.getJSONArray("results"); - for (int i = 0; i < results.length() && i < maxResults; i++) { - Address address = new Address(Locale.getDefault()); - String addressLineString = ""; - JSONObject sourceResult = results.getJSONObject(i); - JSONArray addressComponents = sourceResult.getJSONArray("address_components"); - - // Assemble address by various components - for (int ac = 0; ac < addressComponents.length(); ac++) { - String longNameVal = addressComponents.getJSONObject(ac).getString("long_name"); - String shortNameVal = addressComponents.getJSONObject(ac).getString("short_name"); - JSONArray acTypes = addressComponents.getJSONObject(ac).getJSONArray("types"); - String acType = acTypes.getString(0); - - if (!TextUtils.isEmpty(longNameVal)) { - if (acType.equalsIgnoreCase("street_number")) { - if (TextUtils.isEmpty(addressLineString)) { - addressLineString = longNameVal; - } else { - addressLineString += " " + longNameVal; - } - } else if (acType.equalsIgnoreCase("route")) { - if (TextUtils.isEmpty(addressLineString)) { - addressLineString = longNameVal; - } else { - addressLineString = longNameVal + " " + addressLineString; - } - } else if (acType.equalsIgnoreCase("sublocality")) { - address.setSubLocality(longNameVal); - } else if (acType.equalsIgnoreCase("locality")) { - address.setLocality(longNameVal); - } else if (acType.equalsIgnoreCase("administrative_area_level_2")) { - address.setSubAdminArea(longNameVal); - } else if (acType.equalsIgnoreCase("administrative_area_level_1")) { - address.setAdminArea(longNameVal); - } else if (acType.equalsIgnoreCase("country")) { - address.setCountryName(longNameVal); - address.setCountryCode(shortNameVal); - } else if (acType.equalsIgnoreCase("postal_code")) { - address.setPostalCode(longNameVal); - } - } - } - - // Try to get the already formatted address - String formattedAddress = sourceResult.getString("formatted_address"); - if (!TextUtils.isEmpty(formattedAddress)) { - String[] formattedAddressLines = formattedAddress.split(","); - - for (int ia = 0; ia < formattedAddressLines.length; ia++) { - address.setAddressLine(ia, formattedAddressLines[ia].trim()); - } - } else if (!TextUtils.isEmpty(addressLineString)) { - // If that fails use our manually assembled formatted address - address.setAddressLine(0, addressLineString); - } - - // Finally add address to resulting set - outResult.add(address); - } - - } finally { - urlConnection.disconnect(); - } - - return Collections.unmodifiableList(outResult); - } -} diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/geocode/FallbackReverseGeocodeObservable.kt b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/geocode/FallbackReverseGeocodeObservable.kt new file mode 100644 index 00000000..ebfd9495 --- /dev/null +++ b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/geocode/FallbackReverseGeocodeObservable.kt @@ -0,0 +1,154 @@ +package pl.charmas.android.reactivelocation2.observables.geocode + +import android.location.Address +import io.reactivex.MaybeEmitter +import io.reactivex.MaybeOnSubscribe +import org.json.JSONException +import org.json.JSONObject +import java.io.BufferedReader +import java.io.IOException +import java.io.InputStreamReader +import java.net.HttpURLConnection +import java.net.URL +import java.util.Collections +import java.util.Locale + +internal class FallbackReverseGeocodeObservable( + private val apiKey: String, + private val locale: Locale, + private val latitude: Double, + private val longitude: Double, + private val maxResults: Int +) : MaybeOnSubscribe> { + + override fun subscribe(emitter: MaybeEmitter?>) { + try { + val addresses = alternativeReverseGeocodeQuery() + if (!emitter.isDisposed) { + emitter.onSuccess(addresses) + } + } catch (exception: Exception) { + if (!emitter.isDisposed) { + emitter.onError(exception) + } + } + } + + /** + * This function fetches a list of addresses for the set latitude, longitude and maxResults properties from the + * Google Geocode API (http://maps.googleapis.com/maps/api/geocode). + * + * @return List of addresses + * @throws IOException In case of network problems + * @throws JSONException In case of problems while parsing the json response from google geocode API servers + */ + @Throws(IOException::class, JSONException::class) + private fun alternativeReverseGeocodeQuery(): List
{ + val url = URL( + "http://maps.googleapis.com/maps/api/geocode/json?" + + "latlng=$latitude},${longitude}&sensor=true&key=$apiKey&language=${locale.language}" + ) + val urlConnection = url.openConnection() as HttpURLConnection + val stringBuilder = StringBuilder() + val outResult = mutableListOf
() + try { + val reader = + BufferedReader(InputStreamReader(urlConnection.inputStream, Charsets.UTF_8)) + var line: String? + while (reader.readLine().also { line = it } != null) { + stringBuilder.append(line) + } + + // Root json response object + val jsonRootObject = JSONObject(stringBuilder.toString()) + + // No results status + if ("ZERO_RESULTS".equals(jsonRootObject.getString("status"), ignoreCase = true)) { + return emptyList() + } + + // Other non-OK responses status + if (!"OK".equals(jsonRootObject.getString("status"), ignoreCase = true)) { + throw RuntimeException("Wrong API response") + } + + // Process results + val results = jsonRootObject.getJSONArray("results") + var i = 0 + while (i < results.length() && i < maxResults) { + val address = Address(Locale.getDefault()) + var addressLineString = "" + val sourceResult = results.getJSONObject(i) + val addressComponents = sourceResult.getJSONArray("address_components") + + // Assemble address by various components + for (ac in 0 until addressComponents.length()) { + val jsonObject = addressComponents.getJSONObject(ac) + val longNameVal = jsonObject.getString("long_name") + val shortNameVal = jsonObject.getString("short_name") + val acTypes = jsonObject.getJSONArray("types") + val acType = acTypes.getString(0) + if (longNameVal.isNotEmpty()) { + if (acType.equals("street_number", ignoreCase = true)) { + if (addressLineString.isEmpty()) { + addressLineString = longNameVal + } else { + addressLineString += " $longNameVal" + } + } else if (acType.equals("route", ignoreCase = true)) { + addressLineString = if (addressLineString.isEmpty()) { + longNameVal + } else { + "$longNameVal $addressLineString" + } + } else if (acType.equals("sublocality", ignoreCase = true)) { + address.subLocality = longNameVal + } else if (acType.equals("locality", ignoreCase = true)) { + address.locality = longNameVal + } else if (acType.equals( + "administrative_area_level_2", + ignoreCase = true + ) + ) { + address.subAdminArea = longNameVal + } else if (acType.equals( + "administrative_area_level_1", + ignoreCase = true + ) + ) { + address.adminArea = longNameVal + } else if (acType.equals("country", ignoreCase = true)) { + address.countryName = longNameVal + address.countryCode = shortNameVal + } else if (acType.equals("postal_code", ignoreCase = true)) { + address.postalCode = longNameVal + } + } + } + + // Try to get the already formatted address + val formattedAddress = sourceResult.getString("formatted_address") + if (formattedAddress.isNotEmpty()) { + val formattedAddressLines = + formattedAddress.split(",".toRegex()).toTypedArray() + for (ia in formattedAddressLines.indices) { + address.setAddressLine( + ia, + formattedAddressLines[ia].trim { it <= ' ' } + ) + } + } else if (addressLineString.isNotEmpty()) { + // If that fails use our manually assembled formatted address + address.setAddressLine(0, addressLineString) + } + + // Finally add address to resulting set + outResult.add(address) + i++ + } + } finally { + urlConnection.disconnect() + } + return Collections.unmodifiableList(outResult) + } +} \ No newline at end of file diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/geocode/ReverseGeocodeObservable.java b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/geocode/ReverseGeocodeObservable.java deleted file mode 100644 index 1945854f..00000000 --- a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/geocode/ReverseGeocodeObservable.java +++ /dev/null @@ -1,57 +0,0 @@ -package pl.charmas.android.reactivelocation2.observables.geocode; - -import android.content.Context; -import android.location.Address; -import android.location.Geocoder; - -import java.io.IOException; -import java.util.List; -import java.util.Locale; - -import io.reactivex.Observable; -import io.reactivex.ObservableEmitter; -import io.reactivex.ObservableOnSubscribe; -import io.reactivex.schedulers.Schedulers; -import pl.charmas.android.reactivelocation2.observables.ObservableEmitterWrapper; -import pl.charmas.android.reactivelocation2.observables.ObservableFactory; - - -public class ReverseGeocodeObservable implements ObservableOnSubscribe> { - private final Context ctx; - private final Locale locale; - private final double latitude; - private final double longitude; - private final int maxResults; - - public static Observable> createObservable(Context ctx, ObservableFactory factory, Locale locale, double latitude, double longitude, int maxResults) { - return factory.create(new ReverseGeocodeObservable(ctx, locale, latitude, longitude, maxResults)); - } - - private ReverseGeocodeObservable(Context ctx, Locale locale, double latitude, double longitude, int maxResults) { - this.ctx = ctx; - this.latitude = latitude; - this.longitude = longitude; - this.maxResults = maxResults; - this.locale = locale; - } - - @Override - public void subscribe(ObservableEmitter> emitter) throws Exception { - Geocoder geocoder = new Geocoder(ctx, locale); - try { - List
addresses = geocoder.getFromLocation(latitude, longitude, maxResults); - if (!emitter.isDisposed()) { - emitter.onNext(addresses); - emitter.onComplete(); - } - } catch (IOException e) { - // If it's a service not available error try a different approach using google web api - if (!emitter.isDisposed()) { - Observable - .create(new FallbackReverseGeocodeObservable(locale, latitude, longitude, maxResults)) - .subscribeOn(Schedulers.io()) - .subscribe(new ObservableEmitterWrapper<>(emitter)); - } - } - } -} diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/geocode/ReverseGeocodeObservable.kt b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/geocode/ReverseGeocodeObservable.kt new file mode 100644 index 00000000..c19b91d9 --- /dev/null +++ b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/geocode/ReverseGeocodeObservable.kt @@ -0,0 +1,64 @@ +package pl.charmas.android.reactivelocation2.observables.geocode + +import android.content.Context +import android.location.Address +import android.location.Geocoder +import io.reactivex.Maybe +import io.reactivex.MaybeEmitter +import io.reactivex.MaybeOnSubscribe +import io.reactivex.schedulers.Schedulers +import pl.charmas.android.reactivelocation2.observables.MaybeEmitterWrapper +import pl.charmas.android.reactivelocation2.observables.MaybeFactory +import java.io.IOException +import java.util.Locale + +class ReverseGeocodeObservable private constructor( + private val ctx: Context, + private val locale: Locale, + private val apiKey: String, + private val latitude: Double, + private val longitude: Double, + private val maxResults: Int +) : MaybeOnSubscribe> { + + override fun subscribe(emitter: MaybeEmitter>) { + val geoCoder = Geocoder(ctx, locale) + try { + val addresses = geoCoder.getFromLocation(latitude, longitude, maxResults) + if (!emitter.isDisposed) { + emitter.onSuccess(addresses) + } + } catch (e: IOException) { + // If it's a service not available error try a different approach using google web api + if (!emitter.isDisposed) { + Maybe.create( + FallbackReverseGeocodeObservable( + apiKey, + locale, + latitude, + longitude, + maxResults + ) + ) + .subscribeOn(Schedulers.io()) + .subscribe(MaybeEmitterWrapper(emitter)) + } + } + } + + companion object { + fun create( + ctx: Context, + apiKey: String, + factory: MaybeFactory, + locale: Locale, + latitude: Double, + longitude: Double, + maxResults: Int + ): Maybe> { + return factory.create( + ReverseGeocodeObservable(ctx, locale, apiKey, latitude, longitude, maxResults) + ) + } + } +} \ No newline at end of file diff --git a/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/GeofenceActivity.java b/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/GeofenceActivity.java index 092a027b..834676b3 100755 --- a/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/GeofenceActivity.java +++ b/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/GeofenceActivity.java @@ -30,7 +30,7 @@ public class GeofenceActivity extends BaseActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - reactiveLocationProvider = new ReactiveLocationProvider(this); + reactiveLocationProvider = new ReactiveLocationProvider(this, getString(R.string.API_KEY)); setContentView(R.layout.activity_geofence); initViews(); } diff --git a/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/MainActivity.java b/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/MainActivity.java index 253b1bbf..8fa2a59a 100755 --- a/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/MainActivity.java +++ b/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/MainActivity.java @@ -3,7 +3,6 @@ import android.annotation.SuppressLint; import android.content.Intent; import android.content.IntentSender; -import android.location.Address; import android.location.Location; import android.os.Bundle; import android.text.TextUtils; @@ -34,7 +33,6 @@ import pl.charmas.android.reactivelocation2.sample.utils.LocationToStringFunc; import pl.charmas.android.reactivelocation2.sample.utils.ToMostProbableActivity; -import java.util.List; import java.util.Locale; import static pl.charmas.android.reactivelocation2.sample.utils.UnsubscribeIfPresent.dispose; @@ -70,7 +68,7 @@ protected void onCreate(Bundle savedInstanceState) { addressLocationView = findViewById(R.id.address_for_location_view); currentActivityView = findViewById(R.id.activity_recent_view); - locationProvider = new ReactiveLocationProvider(getApplicationContext(), ReactiveLocationProviderConfiguration + locationProvider = new ReactiveLocationProvider(getApplicationContext(),getString(R.string.API_KEY), ReactiveLocationProviderConfiguration .builder() .setRetryOnConnectionSuspended(true) .build() @@ -118,7 +116,7 @@ protected void onCreate(Bundle savedInstanceState) { .observeOn(AndroidSchedulers.mainThread()); addressObservable = locationProvider.getUpdatedLocation(locationRequest) - .flatMap((Function>>) location -> locationProvider.getReverseGeocodeObservable(Locale.getDefault(), location.getLatitude(), location.getLongitude(), 1)) + .flatMapMaybe(location -> locationProvider.getReverseGeocodeMaybe(Locale.getDefault(), location.getLatitude(), location.getLongitude(), 1)) .map(addresses -> addresses != null && !addresses.isEmpty() ? addresses.get(0) : null) .map(new AddressToStringFunc()) .subscribeOn(Schedulers.io()) diff --git a/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/MockLocationsActivity.java b/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/MockLocationsActivity.java index 6cd45dc3..2204eb12 100755 --- a/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/MockLocationsActivity.java +++ b/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/MockLocationsActivity.java @@ -1,6 +1,7 @@ package pl.charmas.android.reactivelocation2.sample; import android.Manifest; +import android.annotation.SuppressLint; import android.content.pm.PackageManager; import android.location.Location; import android.os.Build; @@ -15,7 +16,6 @@ import android.widget.Toast; import android.widget.ToggleButton; import androidx.core.app.ActivityCompat; -import com.google.android.gms.common.api.Status; import com.google.android.gms.location.LocationRequest; import io.reactivex.Observable; import io.reactivex.disposables.Disposable; @@ -53,7 +53,7 @@ protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_mocklocations); - locationProvider = new ReactiveLocationProvider(this); + locationProvider = new ReactiveLocationProvider(this,getString(R.string.API_KEY)); mockLocationSubject = PublishSubject.create(); mockLocationObservable = mockLocationSubject.hide(); @@ -118,6 +118,7 @@ private void addMockLocation() { } } + @SuppressLint("MissingPermission") private void setMockMode(boolean toggle) { if (toggle) { if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) { @@ -125,12 +126,13 @@ private void setMockMode(boolean toggle) { } mockLocationDisposable = - Observable.zip(locationProvider.mockLocation(mockLocationObservable), - mockLocationObservable, new BiFunction() { + Observable.zip( + locationProvider.mockLocation(mockLocationObservable), + mockLocationObservable, new BiFunction() { int count = 0; @Override - public String apply(Status result, Location location) { + public String apply(Boolean result, Location location) { return new LocationToStringFunc().apply(location) + " " + count++; } }) diff --git a/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/PlacesActivity.kt b/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/PlacesActivity.kt index 29b29a03..55c5e18f 100755 --- a/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/PlacesActivity.kt +++ b/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/PlacesActivity.kt @@ -47,7 +47,7 @@ class PlacesActivity : BaseActivity() { startActivity(PlacesResultActivity.getStartIntent(this@PlacesActivity, placeId)) } } - reactiveLocationProvider = ReactiveLocationProvider(this) + reactiveLocationProvider = ReactiveLocationProvider(this,getString(R.string.API_KEY)) } @SuppressLint("MissingPermission") @@ -76,7 +76,7 @@ class PlacesActivity : BaseActivity() { compositeDisposable.add( Observable.combineLatest( queryObservable, - reactiveLocationProvider.lastKnownLocation, + reactiveLocationProvider.lastKnownLocation.toObservable(), BiFunction { query, currentLocation -> QueryWithCurrentLocation(query, currentLocation) } diff --git a/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/PlacesResultActivity.kt b/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/PlacesResultActivity.kt index cfdc9cfe..1e6bbf74 100755 --- a/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/PlacesResultActivity.kt +++ b/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/PlacesResultActivity.kt @@ -22,7 +22,7 @@ class PlacesResultActivity : BaseActivity() { placeNameView = findViewById(R.id.place_name_view) as TextView placeLocationView = findViewById(R.id.place_location_view) as TextView placeAddressView = findViewById(R.id.place_address_view) as TextView - reactiveLocationProvider = ReactiveLocationProvider(this) + reactiveLocationProvider = ReactiveLocationProvider(this, getString(R.string.API_KEY)) placeIdFromIntent } @@ -38,7 +38,7 @@ class PlacesResultActivity : BaseActivity() { compositeSubscription.add( reactiveLocationProvider.getPlaceById(placeId) .subscribe { res -> - val place = res.place + val place = res.place placeNameView.text = place.name val text = place.latLng?.latitude.toString() + ", " + place.latLng?.longitude placeLocationView.text = text From 8bf3342ad21e031466fe2a1f5dc2fcf1dcb70677 Mon Sep 17 00:00:00 2001 From: Nikolay Unuchek Date: Mon, 3 Aug 2020 16:17:42 +0300 Subject: [PATCH 11/21] added LocationManager --- android-reactive-location/build.gradle | 3 +- .../src/main/AndroidManifest.xml | 2 +- .../ReactiveLocationProvider.kt | 184 +++++++++- .../android/reactivelocation2/ext/ListExt.kt | 6 + .../android/reactivelocation2/ext/MaybeExt.kt | 30 ++ .../reactivelocation2/ext/OptionalKt.kt | 21 ++ .../android/reactivelocation2/ext/TasksExt.kt | 1 + .../observables/TaskResultMaybeOnSubscribe.kt | 6 - sample/src/main/AndroidManifest.xml | 15 +- .../sample/BaseActivity.java | 26 -- .../reactivelocation2/sample/BaseActivity.kt | 36 ++ .../sample/GeofenceActivity.java | 4 +- .../sample/MainActivity.java | 219 ------------ .../reactivelocation2/sample/MainActivity.kt | 321 ++++++++++++++++++ .../sample/MockLocationsActivity.java | 6 +- .../sample/ext/AddressExt.kt | 14 + .../sample/ext/ContextExt.kt | 14 + .../sample/ext/DisposableExt.kt | 8 + .../sample/ext/LocationExt.kt | 8 + .../sample/utils/AddressToStringFunc.java | 18 - .../sample/utils/LocationToStringFunc.java | 14 - sample/src/main/res/layout/activity_main.xml | 52 +++ 22 files changed, 704 insertions(+), 304 deletions(-) create mode 100644 android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/ext/ListExt.kt create mode 100644 android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/ext/MaybeExt.kt create mode 100644 android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/ext/OptionalKt.kt delete mode 100755 sample/src/main/java/pl/charmas/android/reactivelocation2/sample/BaseActivity.java create mode 100755 sample/src/main/java/pl/charmas/android/reactivelocation2/sample/BaseActivity.kt delete mode 100755 sample/src/main/java/pl/charmas/android/reactivelocation2/sample/MainActivity.java create mode 100755 sample/src/main/java/pl/charmas/android/reactivelocation2/sample/MainActivity.kt create mode 100755 sample/src/main/java/pl/charmas/android/reactivelocation2/sample/ext/AddressExt.kt create mode 100644 sample/src/main/java/pl/charmas/android/reactivelocation2/sample/ext/ContextExt.kt create mode 100644 sample/src/main/java/pl/charmas/android/reactivelocation2/sample/ext/DisposableExt.kt create mode 100755 sample/src/main/java/pl/charmas/android/reactivelocation2/sample/ext/LocationExt.kt delete mode 100755 sample/src/main/java/pl/charmas/android/reactivelocation2/sample/utils/AddressToStringFunc.java delete mode 100755 sample/src/main/java/pl/charmas/android/reactivelocation2/sample/utils/LocationToStringFunc.java diff --git a/android-reactive-location/build.gradle b/android-reactive-location/build.gradle index 5b84c690..ea00dc81 100644 --- a/android-reactive-location/build.gradle +++ b/android-reactive-location/build.gradle @@ -22,13 +22,14 @@ android { dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" - implementation "androidx.core:core-ktx:1.3.0" + implementation "androidx.core:core-ktx:1.3.1" implementation ('com.google.android.gms:play-services-location:17.0.0'){ exclude group: 'com.google.android.gms', module: 'play-services-places' } implementation 'com.google.android.libraries.places:places:2.3.0' implementation 'io.reactivex.rxjava2:rxjava:2.2.19' + implementation 'io.reactivex.rxjava2:rxandroid:2.1.1' } // Comment this to deploy to local maven repository diff --git a/android-reactive-location/src/main/AndroidManifest.xml b/android-reactive-location/src/main/AndroidManifest.xml index 489959df..311c1a8a 100644 --- a/android-reactive-location/src/main/AndroidManifest.xml +++ b/android-reactive-location/src/main/AndroidManifest.xml @@ -1,2 +1,2 @@ - + diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/ReactiveLocationProvider.kt b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/ReactiveLocationProvider.kt index 0a7333fa..8cf4dabe 100644 --- a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/ReactiveLocationProvider.kt +++ b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/ReactiveLocationProvider.kt @@ -1,10 +1,16 @@ package pl.charmas.android.reactivelocation2 +import android.annotation.SuppressLint import android.app.PendingIntent import android.content.Context import android.graphics.Bitmap import android.location.Address +import android.location.Criteria import android.location.Location +import android.location.LocationListener +import android.location.LocationManager +import android.os.Bundle +import android.util.Log import androidx.annotation.IntRange import androidx.annotation.RequiresPermission import com.google.android.gms.common.api.Api @@ -29,7 +35,13 @@ import com.google.android.libraries.places.api.net.FindCurrentPlaceRequest import com.google.android.libraries.places.api.net.FindCurrentPlaceResponse import io.reactivex.Maybe import io.reactivex.Observable +import io.reactivex.Scheduler +import io.reactivex.android.schedulers.AndroidSchedulers +import io.reactivex.disposables.Disposables +import pl.charmas.android.reactivelocation2.ext.calldownOrEmpty import pl.charmas.android.reactivelocation2.ext.fromSuccessFailureToMaybe +import pl.charmas.android.reactivelocation2.ext.onSuccessOrComplete +import pl.charmas.android.reactivelocation2.ext.reduceRightDefault import pl.charmas.android.reactivelocation2.ext.toMaybe import pl.charmas.android.reactivelocation2.observables.GoogleAPIClientMaybeOnSubscribe import pl.charmas.android.reactivelocation2.observables.MaybeContext @@ -46,6 +58,7 @@ import pl.charmas.android.reactivelocation2.observables.location.LocationUpdates import pl.charmas.android.reactivelocation2.observables.location.MockLocationObservableOnSubscribe import pl.charmas.android.reactivelocation2.observables.location.RemoveLocationIntentUpdatesObservableOnSubscribe import java.util.Locale +import java.util.concurrent.TimeUnit /** * Factory of observables that can manipulate location @@ -65,6 +78,9 @@ constructor( configuration: ReactiveLocationProviderConfiguration = ReactiveLocationProviderConfiguration.builder() .build() ) { + + private val locationManager: LocationManager = + context.getSystemService(Context.LOCATION_SERVICE) as LocationManager private val ctxObservable: ObservableContext = ObservableContext(context, configuration) private val ctxMaybe: MaybeContext = MaybeContext(context, configuration) private val factoryObservable: ObservableFactory = ObservableFactory(ctxObservable) @@ -72,7 +88,8 @@ constructor( private val settingsClient = LocationServices.getSettingsClient(context) private val geofencingClient = LocationServices.getGeofencingClient(context) - private val fusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(context) + private val fusedLocationProviderClient = + LocationServices.getFusedLocationProviderClient(context) /** * Creates observable that obtains last known location and than completes. @@ -89,11 +106,162 @@ constructor( @get:RequiresPermission(anyOf = ["android.permission.ACCESS_COARSE_LOCATION", "android.permission.ACCESS_FINE_LOCATION"]) val lastKnownLocation: Maybe get() { - return fusedLocationProviderClient - .lastLocation + return fusedLocationProviderClient.lastLocation .toMaybe() } + /** + * Creates observable that obtains last known location from BestProvider and than completes. + * */ + @get:RequiresPermission(anyOf = ["android.permission.ACCESS_COARSE_LOCATION", "android.permission.ACCESS_FINE_LOCATION"]) + val locationBestProvider: Maybe + get() { + return Maybe.create { emitter -> + val bestProvider = locationManager.getBestProvider( + Criteria(), false + ) + + if (!emitter.isDisposed) + emitter.onSuccessOrComplete(bestProvider) + + } + .flatMap { provider -> + getLastKnownLocationFromProvider(provider) + } + } + + @SuppressLint("MissingPermission") + fun getLastKnownLocationFromProvider(provider: String): Maybe { + return Maybe.create { emitter -> + val locationListener = object : LocationListener { + override fun onLocationChanged(location: Location?) { + Log.d(TAG, "onLocationChanged: $location") + emitter.onSuccessOrComplete(location) + } + + override fun onStatusChanged(provider: String?, status: Int, extras: Bundle?) { + Log.d(TAG, "onStatusChanged: $provider status = $status") + } + + override fun onProviderEnabled(provider: String?) { + Log.d(TAG, "onProviderEnabled: $provider") + } + + override fun onProviderDisabled(provider: String?) { + Log.d(TAG, "onProviderDisabled: $provider") + if (!emitter.isDisposed) + emitter.onError(RuntimeException("onProviderDisabled: $provider")) + } + } + + emitter.setDisposable(Disposables.fromAction { + locationManager.removeUpdates(locationListener) + }) + + locationManager.requestLocationUpdates( + provider, + 1000, + 1000f, + locationListener, + null + ) + + val location = locationManager.getLastKnownLocation(provider) + + if (location != null) { + emitter.onSuccess(location) + } + } + .subscribeOn(AndroidSchedulers.mainThread()) + } + + @SuppressLint("MissingPermission") + fun getLocationUpdatesFromProvider(provider: String): Observable { + return Observable.create { emitter -> + val locationListener = object : LocationListener { + override fun onLocationChanged(location: Location?) { + Log.d(TAG, "onLocationChanged: $location") + if (location != null) { + if (!emitter.isDisposed) { + emitter.onNext(location) + } + } + } + + override fun onStatusChanged(provider: String?, status: Int, extras: Bundle?) { + Log.d(TAG, "onStatusChanged: $provider status = $status") + } + + override fun onProviderEnabled(provider: String?) { + Log.d(TAG, "onProviderEnabled: $provider") + } + + override fun onProviderDisabled(provider: String?) { + Log.d(TAG, "onProviderDisabled: $provider") + } + } + + emitter.setDisposable(Disposables.fromAction { + locationManager.removeUpdates(locationListener) + }) + + locationManager.requestLocationUpdates( + provider, + 1000, + 1000f, + locationListener, + null + ) + + val location = locationManager.getLastKnownLocation(provider) + + if (location != null) { + if (!emitter.isDisposed) { + emitter.onNext(location) + } + } + } + .subscribeOn(AndroidSchedulers.mainThread()) + } + + /** + * Creates observable that obtains best last known location from Providers and than completes. + * */ + @RequiresPermission(anyOf = ["android.permission.ACCESS_COARSE_LOCATION", "android.permission.ACCESS_FINE_LOCATION"]) + fun getLastKnownLocationFromAllProviders( + time: Int, + timeUnit: TimeUnit, + scheduler: Scheduler + ): Maybe { + return Maybe.create> { emitter -> + emitter.onSuccessOrComplete(locationManager.allProviders) + }.flatMap { allProviders -> + Observable.fromIterable(allProviders) + .flatMapMaybe { + getLastKnownLocationFromProvider(it) + .onErrorResumeNext { throwable: Throwable -> Maybe.empty() } + .calldownOrEmpty(time, timeUnit, scheduler) + } + .toList() + .flatMapMaybe { locations -> + val bestLocation = + locations.reduceRightDefault(locations.firstOrNull()) { first, second -> + when { + first == null -> second + second == null -> first + second.accuracy > first.accuracy -> first + else -> second + } + } + if (bestLocation == null) { + Maybe.empty() + } else { + Maybe.just(bestLocation) + } + } + } + } + /** * Creates observable that allows to observe infinite stream of location updates. * To stop the stream you have to unsubscribe from observable - location updates are @@ -356,9 +524,7 @@ constructor( */ @RequiresPermission(allOf = ["android.permission.ACCESS_FINE_LOCATION", "android.permission.ACCESS_WIFI_STATE"]) fun getCurrentPlace(placeFilter: FindCurrentPlaceRequest): Maybe { - return Places.createClient( - ctxObservable.context - ).findCurrentPlace(placeFilter) + return Places.createClient(ctxObservable.context).findCurrentPlace(placeFilter) .toMaybe() } @@ -468,7 +634,11 @@ constructor( * @param apis collection of apis to connect to * @return observable that emits apis client after successful connection */ - fun getGoogleApiClientMaybe(vararg apis: Api): Maybe { + private fun getGoogleApiClientMaybe(vararg apis: Api): Maybe { return GoogleAPIClientMaybeOnSubscribe.create(ctxMaybe, factoryMaybe, *apis) } + + companion object { + const val TAG: String = "ReactiveLocationProvide" + } } diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/ext/ListExt.kt b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/ext/ListExt.kt new file mode 100644 index 00000000..6d09f992 --- /dev/null +++ b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/ext/ListExt.kt @@ -0,0 +1,6 @@ +package pl.charmas.android.reactivelocation2.ext + +inline fun List.reduceRightDefault(defaultIfEmpty: T, operation: (T, acc: T) -> T): T { + return if (isEmpty()) defaultIfEmpty + else reduceRight(operation) +} diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/ext/MaybeExt.kt b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/ext/MaybeExt.kt new file mode 100644 index 00000000..06d48674 --- /dev/null +++ b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/ext/MaybeExt.kt @@ -0,0 +1,30 @@ +package pl.charmas.android.reactivelocation2.ext + +import io.reactivex.Maybe +import io.reactivex.MaybeEmitter +import io.reactivex.Scheduler +import java.util.concurrent.TimeUnit + +fun MaybeEmitter.onSuccessOrComplete(item: T?) { + if (isDisposed) { + return + } + item?.let { onSuccess(it) } ?: onComplete() +} + +fun Maybe.calldownOrEmpty(time: Int, timeUnit: TimeUnit, scheduler: Scheduler, id: String? = null): Maybe { + return this.compose { source -> + Maybe.merge( + source.map { it.asOptional() }, + Maybe.timer(time.toLong(), timeUnit, scheduler).mapOrEmpty { Optional.empty() } + ) + .firstElement() + } + .mapOrEmpty { it.value } +} + +fun Maybe.mapOrEmpty(mapper: (T) -> R?): Maybe { + return this.flatMap { item -> + mapper(item)?.let { Maybe.just(it) } ?: Maybe.empty() + } +} diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/ext/OptionalKt.kt b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/ext/OptionalKt.kt new file mode 100644 index 00000000..312ea2d3 --- /dev/null +++ b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/ext/OptionalKt.kt @@ -0,0 +1,21 @@ +package pl.charmas.android.reactivelocation2.ext + +data class Optional(val value: T? = null) { + + companion object { + fun empty(): Optional { + return Optional() + } + } + + fun isEmpty(): Boolean { + return value == null + } + + fun isNotEmpty(): Boolean { + return value != null + } + +} + +fun T?.asOptional() = Optional(this) diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/ext/TasksExt.kt b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/ext/TasksExt.kt index 9f8f7436..bde1d6e5 100644 --- a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/ext/TasksExt.kt +++ b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/ext/TasksExt.kt @@ -4,6 +4,7 @@ import com.google.android.gms.common.api.PendingResult import com.google.android.gms.common.api.Result import com.google.android.gms.tasks.Task import io.reactivex.Maybe +import io.reactivex.MaybeEmitter import io.reactivex.Observable import pl.charmas.android.reactivelocation2.observables.PendingResultObservableOnSubscribe import pl.charmas.android.reactivelocation2.observables.TaskResultMaybeOnSubscribe diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/TaskResultMaybeOnSubscribe.kt b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/TaskResultMaybeOnSubscribe.kt index 0cddc9b1..bc1d6f27 100644 --- a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/TaskResultMaybeOnSubscribe.kt +++ b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/TaskResultMaybeOnSubscribe.kt @@ -12,12 +12,6 @@ class TaskResultMaybeOnSubscribe(private val task: Task) : result?.let { emitter.onSuccess(it) } ?: emitter.onComplete() } } - task.addOnCompleteListener { command -> - if (!emitter.isDisposed) { - val result = command.result - result?.let { emitter.onSuccess(it) } ?: emitter.onComplete() - } - } task.addOnFailureListener { exception -> if (!emitter.isDisposed) { emitter.onError(exception) diff --git a/sample/src/main/AndroidManifest.xml b/sample/src/main/AndroidManifest.xml index c7122be9..66125344 100644 --- a/sample/src/main/AndroidManifest.xml +++ b/sample/src/main/AndroidManifest.xml @@ -3,9 +3,10 @@ xmlns:tools="http://schemas.android.com/tools" package="pl.charmas.android.reactivelocation2.sample"> + + - + - + - + - + - + - + diff --git a/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/BaseActivity.java b/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/BaseActivity.java deleted file mode 100755 index e95fc55b..00000000 --- a/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/BaseActivity.java +++ /dev/null @@ -1,26 +0,0 @@ -package pl.charmas.android.reactivelocation2.sample; - -import android.Manifest; -import android.widget.Toast; -import androidx.appcompat.app.AppCompatActivity; -import com.tbruyelle.rxpermissions2.RxPermissions; -import io.reactivex.disposables.Disposable; - -public abstract class BaseActivity extends AppCompatActivity { - - @Override - protected void onStart() { - super.onStart(); - Disposable subscribe = new RxPermissions(this) - .request(Manifest.permission.ACCESS_FINE_LOCATION) - .subscribe(granted -> { - if (granted) { - onLocationPermissionGranted(); - } else { - Toast.makeText(BaseActivity.this, "Sorry, no demo without permission...", Toast.LENGTH_SHORT).show(); - } - }); - } - - protected abstract void onLocationPermissionGranted(); -} diff --git a/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/BaseActivity.kt b/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/BaseActivity.kt new file mode 100755 index 00000000..07424a13 --- /dev/null +++ b/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/BaseActivity.kt @@ -0,0 +1,36 @@ +package pl.charmas.android.reactivelocation2.sample + +import android.Manifest +import androidx.appcompat.app.AppCompatActivity +import com.tbruyelle.rxpermissions2.RxPermissions +import io.reactivex.disposables.CompositeDisposable +import pl.charmas.android.reactivelocation2.sample.ext.toast + +abstract class BaseActivity : AppCompatActivity() { + + var disposables = CompositeDisposable() + + override fun onStart() { + super.onStart() + val subscribe = RxPermissions(this) + .request( + Manifest.permission.ACCESS_FINE_LOCATION, + Manifest.permission.ACCESS_COARSE_LOCATION + ) + .reduce { t1, t2 -> t1 && t2 } + .subscribe { granted: Boolean -> + if (granted) { + onLocationPermissionGranted() + } else { + toast("Sorry, no demo without permission...") + } + } + } + + override fun onStop() { + super.onStop() + disposables.clear() + } + + protected abstract fun onLocationPermissionGranted() +} \ No newline at end of file diff --git a/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/GeofenceActivity.java b/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/GeofenceActivity.java index 834676b3..031ed8dc 100755 --- a/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/GeofenceActivity.java +++ b/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/GeofenceActivity.java @@ -12,8 +12,8 @@ import com.google.android.gms.location.GeofencingRequest; import io.reactivex.disposables.Disposable; import pl.charmas.android.reactivelocation2.ReactiveLocationProvider; +import pl.charmas.android.reactivelocation2.sample.ext.LocationExtKt; import pl.charmas.android.reactivelocation2.sample.utils.DisplayTextOnViewAction; -import pl.charmas.android.reactivelocation2.sample.utils.LocationToStringFunc; import static pl.charmas.android.reactivelocation2.sample.utils.UnsubscribeIfPresent.dispose; @@ -49,7 +49,7 @@ private void initViews() { protected void onLocationPermissionGranted() { lastKnownLocationDisposable = reactiveLocationProvider .getLastKnownLocation() - .map(new LocationToStringFunc()) + .map(LocationExtKt::text) .subscribe(new DisplayTextOnViewAction(lastKnownLocationView)); } diff --git a/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/MainActivity.java b/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/MainActivity.java deleted file mode 100755 index 8fa2a59a..00000000 --- a/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/MainActivity.java +++ /dev/null @@ -1,219 +0,0 @@ -package pl.charmas.android.reactivelocation2.sample; - -import android.annotation.SuppressLint; -import android.content.Intent; -import android.content.IntentSender; -import android.location.Location; -import android.os.Bundle; -import android.text.TextUtils; -import android.util.Log; -import android.view.Menu; -import android.widget.TextView; -import android.widget.Toast; -import com.google.android.gms.common.api.ApiException; -import com.google.android.gms.common.api.ResolvableApiException; -import com.google.android.gms.location.ActivityRecognitionResult; -import com.google.android.gms.location.LocationRequest; -import com.google.android.gms.location.LocationSettingsRequest; -import com.google.android.gms.location.LocationSettingsResponse; -import com.google.android.gms.location.LocationSettingsStates; -import com.google.android.gms.location.LocationSettingsStatusCodes; -import io.reactivex.Maybe; -import io.reactivex.Observable; -import io.reactivex.android.schedulers.AndroidSchedulers; -import io.reactivex.disposables.Disposable; -import io.reactivex.functions.Consumer; -import io.reactivex.functions.Function; -import io.reactivex.schedulers.Schedulers; -import pl.charmas.android.reactivelocation2.ReactiveLocationProvider; -import pl.charmas.android.reactivelocation2.ReactiveLocationProviderConfiguration; -import pl.charmas.android.reactivelocation2.sample.utils.AddressToStringFunc; -import pl.charmas.android.reactivelocation2.sample.utils.DetectedActivityToString; -import pl.charmas.android.reactivelocation2.sample.utils.DisplayTextOnViewAction; -import pl.charmas.android.reactivelocation2.sample.utils.LocationToStringFunc; -import pl.charmas.android.reactivelocation2.sample.utils.ToMostProbableActivity; - -import java.util.Locale; - -import static pl.charmas.android.reactivelocation2.sample.utils.UnsubscribeIfPresent.dispose; - -public class MainActivity extends BaseActivity { - private final static int REQUEST_CHECK_SETTINGS = 0; - private final static String TAG = "MainActivity"; - private ReactiveLocationProvider locationProvider; - - private TextView lastKnownLocationView; - private TextView updatableLocationView; - private TextView addressLocationView; - private TextView currentActivityView; - - private Maybe lastKnownLocationObservable; - private Observable locationUpdatesObservable; - private Observable activityObservable; - - private Disposable lastKnownLocationDisposable; - private Disposable updatableLocationDisposable; - private Disposable addressDisposable; - private Disposable activityDisposable; - private Observable addressObservable; - - @SuppressLint("MissingPermission") - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_main); - - lastKnownLocationView = findViewById(R.id.last_known_location_view); - updatableLocationView = findViewById(R.id.updated_location_view); - addressLocationView = findViewById(R.id.address_for_location_view); - currentActivityView = findViewById(R.id.activity_recent_view); - - locationProvider = new ReactiveLocationProvider(getApplicationContext(),getString(R.string.API_KEY), ReactiveLocationProviderConfiguration - .builder() - .setRetryOnConnectionSuspended(true) - .build() - ); - - lastKnownLocationObservable = locationProvider - .getLastKnownLocation() - .observeOn(AndroidSchedulers.mainThread()); - - final LocationRequest locationRequest = LocationRequest.create() - .setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY) - .setNumUpdates(5) - .setInterval(100); - locationUpdatesObservable = locationProvider - .checkLocationSettings( - new LocationSettingsRequest.Builder() - .addLocationRequest(locationRequest) - .setAlwaysShow(true) //Refrence: http://stackoverflow.com/questions/29824408/google-play-services-locationservices-api-new-option-never - .build() - ) - .doOnSuccess(locationSettingsResponse -> { - Log.d("MainActivity", "getLocationSettingsStates isGpsUsable = " + locationSettingsResponse.getLocationSettingsStates().isGpsUsable()); - }) - .doOnError(throwable -> { - int statusCode = ((ApiException) throwable).getStatusCode(); - switch (statusCode) { - case LocationSettingsStatusCodes.RESOLUTION_REQUIRED: - try { - // Show the dialog by calling startResolutionForResult(), and check the - // result in onActivityResult(). - ResolvableApiException rae = (ResolvableApiException) throwable; - rae.startResolutionForResult(MainActivity.this, REQUEST_CHECK_SETTINGS); - } catch (IntentSender.SendIntentException sie) { - Log.i(TAG, "PendingIntent unable to execute request."); - } - break; - case LocationSettingsStatusCodes.SETTINGS_CHANGE_UNAVAILABLE: - String errorMessage = "Location settings are inadequate, and cannot be " + - "fixed here. Fix in Settings."; - Log.e(TAG, errorMessage); - Toast.makeText(MainActivity.this, errorMessage, Toast.LENGTH_LONG).show(); - } - }) - .flatMapObservable((Function>) locationSettingsResult -> locationProvider.getUpdatedLocation(locationRequest)) - .observeOn(AndroidSchedulers.mainThread()); - - addressObservable = locationProvider.getUpdatedLocation(locationRequest) - .flatMapMaybe(location -> locationProvider.getReverseGeocodeMaybe(Locale.getDefault(), location.getLatitude(), location.getLongitude(), 1)) - .map(addresses -> addresses != null && !addresses.isEmpty() ? addresses.get(0) : null) - .map(new AddressToStringFunc()) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()); - - activityObservable = locationProvider - .getDetectedActivity(50) - .observeOn(AndroidSchedulers.mainThread()); - } - - @Override - protected void onLocationPermissionGranted() { - lastKnownLocationDisposable = lastKnownLocationObservable - .map(new LocationToStringFunc()) - .subscribe(new DisplayTextOnViewAction(lastKnownLocationView), new ErrorHandler()); - - updatableLocationDisposable = locationUpdatesObservable - .map(new LocationToStringFunc()) - .map(new Function() { - int count = 0; - - @Override - public String apply(String s) { - return s + " " + count++; - } - }) - .subscribe(new DisplayTextOnViewAction(updatableLocationView), new ErrorHandler()); - - - addressDisposable = addressObservable - .subscribe(new DisplayTextOnViewAction(addressLocationView), new ErrorHandler()); - - activityDisposable = activityObservable - .map(new ToMostProbableActivity()) - .map(new DetectedActivityToString()) - .subscribe(new DisplayTextOnViewAction(currentActivityView), new ErrorHandler()); - } - - @Override - protected void onStop() { - super.onStop(); - dispose(updatableLocationDisposable); - dispose(addressDisposable); - dispose(lastKnownLocationDisposable); - dispose(activityDisposable); - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - menu.add("Geofencing").setOnMenuItemClickListener(item -> { - startActivity(new Intent(MainActivity.this, GeofenceActivity.class)); - return true; - }); - menu.add("Places").setOnMenuItemClickListener(item -> { - if (TextUtils.isEmpty(getString(R.string.API_KEY))) { - Toast.makeText(MainActivity.this, "First you need to configure your API Key - see README.md", Toast.LENGTH_SHORT).show(); - } else { - startActivity(new Intent(MainActivity.this, PlacesActivity.class)); - } - return true; - }); - menu.add("Mock Locations").setOnMenuItemClickListener(item -> { - startActivity(new Intent(MainActivity.this, MockLocationsActivity.class)); - return true; - }); - return true; - } - - private class ErrorHandler implements Consumer { - @Override - public void accept(Throwable throwable) { - Toast.makeText(MainActivity.this, "Error occurred.", Toast.LENGTH_SHORT).show(); - Log.d("MainActivity", "Error occurred", throwable); - } - } - - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) { - super.onActivityResult(requestCode, resultCode, data); - final LocationSettingsStates states = LocationSettingsStates.fromIntent(data);//intent); - switch (requestCode) { - case REQUEST_CHECK_SETTINGS: - //Reference: https://developers.google.com/android/reference/com/google/android/gms/location/SettingsApi - switch (resultCode) { - case RESULT_OK: - // All required changes were successfully made - Log.d(TAG, "User enabled location"); - break; - case RESULT_CANCELED: - // The user was asked to change settings, but chose not to - Log.d(TAG, "User Cancelled enabling location"); - break; - default: - break; - } - break; - } - } - -} diff --git a/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/MainActivity.kt b/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/MainActivity.kt new file mode 100755 index 00000000..83a4943e --- /dev/null +++ b/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/MainActivity.kt @@ -0,0 +1,321 @@ +package pl.charmas.android.reactivelocation2.sample + +import android.annotation.SuppressLint +import android.app.Activity +import android.content.Intent +import android.content.IntentSender.SendIntentException +import android.location.LocationManager +import android.os.Bundle +import android.util.Log +import android.view.Menu +import android.widget.TextView +import com.google.android.gms.common.api.ApiException +import com.google.android.gms.common.api.ResolvableApiException +import com.google.android.gms.location.LocationRequest +import com.google.android.gms.location.LocationSettingsRequest +import com.google.android.gms.location.LocationSettingsResponse +import com.google.android.gms.location.LocationSettingsStates +import com.google.android.gms.location.LocationSettingsStatusCodes +import io.reactivex.Maybe +import io.reactivex.android.schedulers.AndroidSchedulers +import io.reactivex.functions.Consumer +import io.reactivex.schedulers.Schedulers +import pl.charmas.android.reactivelocation2.ReactiveLocationProvider +import pl.charmas.android.reactivelocation2.ReactiveLocationProviderConfiguration.Companion.builder +import pl.charmas.android.reactivelocation2.sample.ext.addTo +import pl.charmas.android.reactivelocation2.sample.ext.addressToString +import pl.charmas.android.reactivelocation2.sample.ext.text +import pl.charmas.android.reactivelocation2.sample.ext.toast +import pl.charmas.android.reactivelocation2.sample.utils.DetectedActivityToString +import pl.charmas.android.reactivelocation2.sample.utils.DisplayTextOnViewAction +import pl.charmas.android.reactivelocation2.sample.utils.ToMostProbableActivity +import java.util.Locale +import java.util.concurrent.TimeUnit + +class MainActivity : BaseActivity() { + + private val lastKnownLocationView: TextView by lazy { + findViewById(R.id.last_known_location_view) + } + + private val lastBestProviderLocationView: TextView by lazy { + findViewById(R.id.last_best_provider_location_view) + } + + private val locationAllProvidersView: TextView by lazy { + findViewById(R.id.locationAllProviders) + } + private val locationNetworkProviderView: TextView by lazy { + findViewById(R.id.locationNetworkProviderView) + } + + private val locationNetworkProviderUpdatesView: TextView by lazy { + findViewById(R.id.locationNetworkProviderUpdatesView) + } + + private val updatableLocationView: TextView by lazy { + findViewById(R.id.updated_location_view) + } + + private val addressLocationView: TextView by lazy { + findViewById(R.id.address_for_location_view) + } + + private val currentActivityView: TextView by lazy { + findViewById(R.id.activity_recent_view) + } + + private val rxLocationProvider: ReactiveLocationProvider by lazy { + ReactiveLocationProvider( + applicationContext, + getString(R.string.API_KEY), + builder() + .setRetryOnConnectionSuspended(true) + .build() + ) + } + + private val locationRequest: LocationRequest = LocationRequest.create() + .setPriority(LocationRequest.PRIORITY_BALANCED_POWER_ACCURACY) + .setInterval(TimeUnit.SECONDS.toMillis(1)) + + @SuppressLint("MissingPermission") + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_main) + } + + override fun onLocationPermissionGranted() { + lastKnownLocationSubscribe() + locationBestProviderSubscribe() + locationAllProvidersMaybeSubscribe() + lastLocationNetworkProviderMaybeSubscribe() + updatedLocationNetworkProviderSubscribe() + updatedLocationSubscribe() + addressObservableSubscribe() + activityObservableSubscribe() + } + + private fun activityObservableSubscribe() { + rxLocationProvider + .getDetectedActivity(50) + .observeOn(AndroidSchedulers.mainThread()) + .map(ToMostProbableActivity()) + .map(DetectedActivityToString()) + .subscribe( + DisplayTextOnViewAction(currentActivityView), + ErrorHandler("activityObservable") + ) + .addTo(disposables) + } + + @SuppressLint("MissingPermission") + private fun addressObservableSubscribe() { + rxLocationProvider.getUpdatedLocation(locationRequest) + .flatMapMaybe { location -> + rxLocationProvider.getReverseGeocodeMaybe( + Locale.getDefault(), + location.latitude, + location.longitude, + 1 + ) + } + .map { addresses -> + val address = addresses.firstOrNull() + address?.addressToString() ?: "" + } + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe( + DisplayTextOnViewAction(addressLocationView), + ErrorHandler("addressObservable") + ) + .addTo(disposables) + } + + @SuppressLint("MissingPermission") + private fun updatedLocationSubscribe() { + var count = 0 + + rxLocationProvider + .checkLocationSettings( + LocationSettingsRequest.Builder() + .addLocationRequest(locationRequest) + .setAlwaysShow(true) //Refrence: http://stackoverflow.com/questions/29824408/google-play-services-locationservices-api-new-option-never + .build() + ) + .doOnSuccess { locationSettingsResponse: LocationSettingsResponse -> + Log.d( + "MainActivity", + "getLocationSettingsStates isGpsUsable = " + locationSettingsResponse.locationSettingsStates + .isGpsUsable + ) + } + .doOnError { throwable: Throwable -> + val statusCode = (throwable as ApiException).statusCode + when (statusCode) { + LocationSettingsStatusCodes.RESOLUTION_REQUIRED -> try { + // Show the dialog by calling startResolutionForResult(), and check the + // result in onActivityResult(). + val rae = throwable as ResolvableApiException + rae.startResolutionForResult( + this@MainActivity, + REQUEST_CHECK_SETTINGS + ) + } catch (sie: SendIntentException) { + Log.i( + TAG, + "PendingIntent unable to execute request." + ) + } + LocationSettingsStatusCodes.SETTINGS_CHANGE_UNAVAILABLE -> { + val errorMessage = + "Location settings are inadequate, and cannot be " + + "fixed here. Fix in Settings." + Log.e(TAG, errorMessage) + toast(errorMessage) + } + } + } + .flatMapObservable { rxLocationProvider.getUpdatedLocation(locationRequest) } + .observeOn(AndroidSchedulers.mainThread()) + .map { location -> location.text() + " " + count++ } + .subscribe( + DisplayTextOnViewAction(updatableLocationView), + Consumer { throwable -> + if (throwable is ResolvableApiException) { + return@Consumer + } + + toast("Error occurred.") + Log.d("MainActivity", "Error occurred locationUpdatesObservable:", throwable) + } + ) + .addTo(disposables) + } + + private fun lastKnownLocationSubscribe() { + Maybe.timer(100, TimeUnit.MILLISECONDS, Schedulers.io()) + .flatMap { rxLocationProvider.lastKnownLocation } + .subscribeOn(Schedulers.io()) + .map { it.text() } + .observeOn(AndroidSchedulers.mainThread()) + .subscribe( + DisplayTextOnViewAction(lastKnownLocationView), + ErrorHandler("lastKnownLocationMaybe") + ) + .addTo(disposables) + } + + private fun locationBestProviderSubscribe() { + Maybe.timer(100, TimeUnit.MILLISECONDS, Schedulers.io()) + .flatMap { rxLocationProvider.locationBestProvider } + .subscribeOn(Schedulers.io()) + .map { it.text() } + .observeOn(AndroidSchedulers.mainThread()) + .subscribe( + DisplayTextOnViewAction(lastBestProviderLocationView), + ErrorHandler("locationBestProviderMaybe") + ) + .addTo(disposables) + } + + @SuppressLint("MissingPermission") + private fun locationAllProvidersMaybeSubscribe() { + Maybe.timer(100, TimeUnit.MILLISECONDS, Schedulers.io()) + .flatMap { rxLocationProvider.getLastKnownLocationFromAllProviders(1, TimeUnit.SECONDS, Schedulers.io()) } + .subscribeOn(Schedulers.io()) + .map { it.text() } + .observeOn(AndroidSchedulers.mainThread()) + .subscribe( + DisplayTextOnViewAction(locationAllProvidersView), + ErrorHandler("locationBestProviderMaybe") + ) + .addTo(disposables) + } + + private fun lastLocationNetworkProviderMaybeSubscribe() { + Maybe.timer(100, TimeUnit.MILLISECONDS, Schedulers.io()) + .flatMap { + rxLocationProvider.getLastKnownLocationFromProvider(LocationManager.NETWORK_PROVIDER) + } + .observeOn(Schedulers.io()) + .map { it.text() } + .observeOn(AndroidSchedulers.mainThread()) + .subscribe( + DisplayTextOnViewAction(locationNetworkProviderView), + ErrorHandler("locationNetworkProviderMaybe") + ) + .addTo(disposables) + } + + private fun updatedLocationNetworkProviderSubscribe() { + var count = 0 + + Maybe.timer(100, TimeUnit.MILLISECONDS, Schedulers.io()) + .flatMapObservable { + rxLocationProvider.getLocationUpdatesFromProvider(LocationManager.NETWORK_PROVIDER) + } + .observeOn(Schedulers.io()) + .map { location -> location.text() + " " + count++ } + .observeOn(AndroidSchedulers.mainThread()) + .subscribe( + DisplayTextOnViewAction(locationNetworkProviderUpdatesView), + ErrorHandler("updatedLocationNetworkProviderSubscribe") + ) + .addTo(disposables) + } + + override fun onCreateOptionsMenu(menu: Menu): Boolean { + menu.add("Geofencing") + .setOnMenuItemClickListener { + startActivity(Intent(this@MainActivity, GeofenceActivity::class.java)) + true + } + menu.add("Places") + .setOnMenuItemClickListener { + if (getString(R.string.API_KEY).isEmpty()) { + toast("First you need to configure your API Key - see README.md") + } else { + startActivity(Intent(this@MainActivity, PlacesActivity::class.java)) + } + true + } + menu.add("Mock Locations") + .setOnMenuItemClickListener { + startActivity(Intent(this@MainActivity, MockLocationsActivity::class.java)) + true + } + return true + } + + private inner class ErrorHandler(val source: String) : Consumer { + override fun accept(throwable: Throwable) { + toast("Error occurred.") + Log.d("MainActivity", "Error occurred $source:", throwable) + } + } + + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + super.onActivityResult(requestCode, resultCode, data) + val states = LocationSettingsStates.fromIntent(data) //intent); + when (requestCode) { + REQUEST_CHECK_SETTINGS -> when (resultCode) { + Activity.RESULT_OK -> // All required changes were successfully made + Log.d(TAG, "User enabled location") + Activity.RESULT_CANCELED -> // The user was asked to change settings, but chose not to + Log.d( + TAG, + "User Cancelled enabling location" + ) + else -> { + } + } + } + } + + companion object { + private const val REQUEST_CHECK_SETTINGS = 0 + private const val TAG = "MainActivity" + } +} diff --git a/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/MockLocationsActivity.java b/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/MockLocationsActivity.java index 2204eb12..3963ebb5 100755 --- a/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/MockLocationsActivity.java +++ b/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/MockLocationsActivity.java @@ -24,8 +24,8 @@ import io.reactivex.functions.Function; import io.reactivex.subjects.PublishSubject; import pl.charmas.android.reactivelocation2.ReactiveLocationProvider; +import pl.charmas.android.reactivelocation2.sample.ext.LocationExtKt; import pl.charmas.android.reactivelocation2.sample.utils.DisplayTextOnViewAction; -import pl.charmas.android.reactivelocation2.sample.utils.LocationToStringFunc; import java.util.Date; @@ -98,7 +98,7 @@ protected void onLocationPermissionGranted() { updatedLocationDisposable = locationProvider .getUpdatedLocation(locationRequest) - .map(new LocationToStringFunc()) + .map(LocationExtKt::text) .map(new Function() { int count = 0; @@ -133,7 +133,7 @@ private void setMockMode(boolean toggle) { @Override public String apply(Boolean result, Location location) { - return new LocationToStringFunc().apply(location) + " " + count++; + return LocationExtKt.text(location) + " " + count++; } }) .subscribe(new DisplayTextOnViewAction(mockLocationView), new ErrorHandler()); diff --git a/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/ext/AddressExt.kt b/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/ext/AddressExt.kt new file mode 100755 index 00000000..782796c0 --- /dev/null +++ b/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/ext/AddressExt.kt @@ -0,0 +1,14 @@ +package pl.charmas.android.reactivelocation2.sample.ext + +import android.location.Address + +fun Address.addressToString(): String { + var addressLines = "" + for (i in 0..maxAddressLineIndex) { + addressLines += """ + ${getAddressLine(i)} + + """.trimIndent() + } + return addressLines +} \ No newline at end of file diff --git a/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/ext/ContextExt.kt b/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/ext/ContextExt.kt new file mode 100644 index 00000000..a53d5f46 --- /dev/null +++ b/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/ext/ContextExt.kt @@ -0,0 +1,14 @@ +package pl.charmas.android.reactivelocation2.sample.ext + +import android.content.Context +import android.widget.Toast + +fun Context.toast( + text: String +) { + Toast.makeText( + this, + text, + Toast.LENGTH_SHORT + ).show() +} \ No newline at end of file diff --git a/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/ext/DisposableExt.kt b/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/ext/DisposableExt.kt new file mode 100644 index 00000000..11cdc6b7 --- /dev/null +++ b/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/ext/DisposableExt.kt @@ -0,0 +1,8 @@ +package pl.charmas.android.reactivelocation2.sample.ext + +import io.reactivex.disposables.CompositeDisposable +import io.reactivex.disposables.Disposable + +fun Disposable.addTo(compositeDisposable: CompositeDisposable) { + compositeDisposable.add(this) +} \ No newline at end of file diff --git a/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/ext/LocationExt.kt b/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/ext/LocationExt.kt new file mode 100755 index 00000000..3c89d779 --- /dev/null +++ b/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/ext/LocationExt.kt @@ -0,0 +1,8 @@ +package pl.charmas.android.reactivelocation2.sample.ext + +import android.location.Location +import io.reactivex.functions.Function + +fun Location.text(): String { + return "$latitude $longitude ($provider $accuracy)" +} \ No newline at end of file diff --git a/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/utils/AddressToStringFunc.java b/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/utils/AddressToStringFunc.java deleted file mode 100755 index c0bcc913..00000000 --- a/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/utils/AddressToStringFunc.java +++ /dev/null @@ -1,18 +0,0 @@ -package pl.charmas.android.reactivelocation2.sample.utils; - -import android.location.Address; - -import io.reactivex.functions.Function; - -public class AddressToStringFunc implements Function { - @Override - public String apply(Address address) { - if (address == null) return ""; - - String addressLines = ""; - for (int i = 0; i <= address.getMaxAddressLineIndex(); i++) { - addressLines += address.getAddressLine(i) + '\n'; - } - return addressLines; - } -} diff --git a/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/utils/LocationToStringFunc.java b/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/utils/LocationToStringFunc.java deleted file mode 100755 index dc9e9827..00000000 --- a/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/utils/LocationToStringFunc.java +++ /dev/null @@ -1,14 +0,0 @@ -package pl.charmas.android.reactivelocation2.sample.utils; - -import android.location.Location; - -import io.reactivex.functions.Function; - -public class LocationToStringFunc implements Function { - @Override - public String apply(Location location) { - if (location != null) - return location.getLatitude() + " " + location.getLongitude() + " (" + location.getAccuracy() + ")"; - return "no location available"; - } -} diff --git a/sample/src/main/res/layout/activity_main.xml b/sample/src/main/res/layout/activity_main.xml index c4942281..1b8e4647 100644 --- a/sample/src/main/res/layout/activity_main.xml +++ b/sample/src/main/res/layout/activity_main.xml @@ -22,6 +22,58 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" tools:text="Sample location" /> + + + + + + + + + + + + Date: Wed, 19 Aug 2020 12:37:55 +0300 Subject: [PATCH 12/21] fixed search of place by text --- .../ReactiveLocationProvider.kt | 51 +++++++++++++------ 1 file changed, 35 insertions(+), 16 deletions(-) diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/ReactiveLocationProvider.kt b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/ReactiveLocationProvider.kt index 8cf4dabe..e44cda1e 100644 --- a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/ReactiveLocationProvider.kt +++ b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/ReactiveLocationProvider.kt @@ -36,6 +36,7 @@ import com.google.android.libraries.places.api.net.FindCurrentPlaceResponse import io.reactivex.Maybe import io.reactivex.Observable import io.reactivex.Scheduler +import io.reactivex.Single import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.disposables.Disposables import pl.charmas.android.reactivelocation2.ext.calldownOrEmpty @@ -91,6 +92,19 @@ constructor( private val fusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(context) + fun isGpsEnabled(): Single { + return isProviderEnabled(LocationManager.GPS_PROVIDER) + } + + fun isProviderEnabled(provider: String): Single { + return Single.create { subscriber -> + if (subscriber.isDisposed) { + return@create + } + subscriber.onSuccess(locationManager.isProviderEnabled(provider)) + } + } + /** * Creates observable that obtains last known location and than completes. * Delivered location is never null - when it is unavailable Observable completes without emitting @@ -537,14 +551,12 @@ constructor( * @deprecated use {@link ReactiveLocationProvider#getPlaceCompatById(java.lang.String)} */ fun getPlaceById(placeId: String): Maybe { - return Places.createClient(ctxObservable.context) - .fetchPlace( - FetchPlaceRequest.builder( - placeId, - listOf(Place.Field.ID) - ).build() - ) - .toMaybe() + val listOf = listOf( + Place.Field.ID, + Place.Field.LAT_LNG, + Place.Field.ADDRESS + ) + return getPlaceById(placeId, listOf) } /** @@ -577,14 +589,7 @@ constructor( @IntRange(from = 0L) height: Int, @IntRange(from = 0L) width: Int ): Maybe { - return Places.createClient(ctxObservable.context) - .fetchPlace( - FetchPlaceRequest.builder( - placeId, - listOf(Place.Field.PHOTO_METADATAS) - ).build() - ) - .toMaybe() + return getPlaceById(placeId, listOf(Place.Field.PHOTO_METADATAS)) .flatMap { res -> val photoMetadata = res.place.photoMetadatas?.firstOrNull() if (photoMetadata == null) { @@ -607,6 +612,20 @@ constructor( } } + fun getPlaceById( + placeId: String, + fields: List + ): Maybe { + return Places.createClient(ctxObservable.context) + .fetchPlace( + FetchPlaceRequest.builder( + placeId, + fields + ).build() + ) + .toMaybe() + } + /** * Returns observable that fetches a placePhotoMetadata from the Places API using the place placePhotoMetadata metadata. * Use after fetching the place placePhotoMetadata metadata with [ReactiveLocationProvider.getPhotoMetadataById] From 9cf17d3e8223d27f9ab93899f249d367bda3320e Mon Sep 17 00:00:00 2001 From: Nikolay Unuchek Date: Thu, 15 Oct 2020 14:03:11 +0300 Subject: [PATCH 13/21] updated gradle --- build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index d81a3a75..9304b1bf 100644 --- a/build.gradle +++ b/build.gradle @@ -1,14 +1,14 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { - ext.kotlin_version = '1.3.72' + ext.kotlin_version = '1.4.10' repositories { mavenCentral() jcenter() google() } dependencies { - classpath 'com.android.tools.build:gradle:4.0.1' + classpath 'com.android.tools.build:gradle:4.1.0' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } From 249f04ccdd2495d7caf037d4cf763e40cf8f7ca8 Mon Sep 17 00:00:00 2001 From: Nikolay Unuchek Date: Thu, 15 Oct 2020 14:03:51 +0300 Subject: [PATCH 14/21] updated versions --- android-reactive-location/build.gradle | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/android-reactive-location/build.gradle b/android-reactive-location/build.gradle index ea00dc81..f4ea71e0 100644 --- a/android-reactive-location/build.gradle +++ b/android-reactive-location/build.gradle @@ -22,13 +22,13 @@ android { dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" - implementation "androidx.core:core-ktx:1.3.1" + implementation "androidx.core:core-ktx:1.3.2" - implementation ('com.google.android.gms:play-services-location:17.0.0'){ + implementation ('com.google.android.gms:play-services-location:17.1.0'){ exclude group: 'com.google.android.gms', module: 'play-services-places' } implementation 'com.google.android.libraries.places:places:2.3.0' - implementation 'io.reactivex.rxjava2:rxjava:2.2.19' + implementation 'io.reactivex.rxjava2:rxjava:2.2.20' implementation 'io.reactivex.rxjava2:rxandroid:2.1.1' } From 924ff56bbbb60c09c60ac2c81e14987cbaa3fae5 Mon Sep 17 00:00:00 2001 From: Nikolay Unuchek Date: Thu, 15 Oct 2020 14:14:44 +0300 Subject: [PATCH 15/21] updated gradle-wrapper.properties --- gradle/wrapper/gradle-wrapper.properties | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 922ca983..cc6052d4 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Wed Jun 17 16:51:28 MSK 2020 +#Thu Oct 15 14:02:00 MSK 2020 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-bin.zip From 6a303823d4025912e1705fe7b985cf0c2f7aa786 Mon Sep 17 00:00:00 2001 From: Nikolay Unuchek Date: Wed, 3 Feb 2021 13:56:01 +0300 Subject: [PATCH 16/21] moved places wrapper to ReactivePlacesProvider.kt --- .../ReactiveLocationProvider.kt | 146 +----------------- .../ReactivePlacesProvider.kt | 141 +++++++++++++++++ .../android/reactivelocation2/ext/TasksExt.kt | 14 -- .../observables/TaskResultMaybeOnSubscribe.kt | 2 +- .../TaskSuccessFailureMaybeOnSubscribe.kt | 21 --- build.gradle | 2 +- 6 files changed, 151 insertions(+), 175 deletions(-) create mode 100644 android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/ReactivePlacesProvider.kt delete mode 100644 android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/TaskSuccessFailureMaybeOnSubscribe.kt diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/ReactiveLocationProvider.kt b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/ReactiveLocationProvider.kt index e44cda1e..1b3d71cf 100644 --- a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/ReactiveLocationProvider.kt +++ b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/ReactiveLocationProvider.kt @@ -3,7 +3,6 @@ package pl.charmas.android.reactivelocation2 import android.annotation.SuppressLint import android.app.PendingIntent import android.content.Context -import android.graphics.Bitmap import android.location.Address import android.location.Criteria import android.location.Location @@ -11,7 +10,6 @@ import android.location.LocationListener import android.location.LocationManager import android.os.Bundle import android.util.Log -import androidx.annotation.IntRange import androidx.annotation.RequiresPermission import com.google.android.gms.common.api.Api import com.google.android.gms.common.api.GoogleApiClient @@ -22,17 +20,6 @@ import com.google.android.gms.location.LocationServices import com.google.android.gms.location.LocationSettingsRequest import com.google.android.gms.location.LocationSettingsResponse import com.google.android.gms.maps.model.LatLngBounds -import com.google.android.libraries.places.api.Places -import com.google.android.libraries.places.api.model.AutocompletePrediction -import com.google.android.libraries.places.api.model.PhotoMetadata -import com.google.android.libraries.places.api.model.Place -import com.google.android.libraries.places.api.net.FetchPhotoRequest -import com.google.android.libraries.places.api.net.FetchPlaceRequest -import com.google.android.libraries.places.api.net.FetchPlaceResponse -import com.google.android.libraries.places.api.net.FindAutocompletePredictionsRequest -import com.google.android.libraries.places.api.net.FindAutocompletePredictionsResponse -import com.google.android.libraries.places.api.net.FindCurrentPlaceRequest -import com.google.android.libraries.places.api.net.FindCurrentPlaceResponse import io.reactivex.Maybe import io.reactivex.Observable import io.reactivex.Scheduler @@ -40,7 +27,6 @@ import io.reactivex.Single import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.disposables.Disposables import pl.charmas.android.reactivelocation2.ext.calldownOrEmpty -import pl.charmas.android.reactivelocation2.ext.fromSuccessFailureToMaybe import pl.charmas.android.reactivelocation2.ext.onSuccessOrComplete import pl.charmas.android.reactivelocation2.ext.reduceRightDefault import pl.charmas.android.reactivelocation2.ext.toMaybe @@ -72,12 +58,11 @@ class ReactiveLocationProvider * @param context preferably application context * @param configuration configuration instance */ -@JvmOverloads constructor( val context: Context, private val apiKey: String, configuration: ReactiveLocationProviderConfiguration = ReactiveLocationProviderConfiguration.builder() - .build() + .build(), ) { private val locationManager: LocationManager = @@ -245,7 +230,7 @@ constructor( fun getLastKnownLocationFromAllProviders( time: Int, timeUnit: TimeUnit, - scheduler: Scheduler + scheduler: Scheduler, ): Maybe { return Maybe.create> { emitter -> emitter.onSuccessOrComplete(locationManager.allProviders) @@ -253,7 +238,7 @@ constructor( Observable.fromIterable(allProviders) .flatMapMaybe { getLastKnownLocationFromProvider(it) - .onErrorResumeNext { throwable: Throwable -> Maybe.empty() } + .onErrorResumeNext { throwable: Throwable -> Maybe.empty() } .calldownOrEmpty(time, timeUnit, scheduler) } .toList() @@ -343,7 +328,7 @@ constructor( @RequiresPermission(anyOf = ["android.permission.ACCESS_COARSE_LOCATION", "android.permission.ACCESS_FINE_LOCATION"]) fun requestLocationUpdates( locationRequest: LocationRequest, - intent: PendingIntent + intent: PendingIntent, ): Maybe { return AddLocationIntentUpdatesMaybeOnSubscribe.create( fusedLocationProviderClient, @@ -387,7 +372,7 @@ constructor( locale: Locale = Locale.getDefault(), lat: Double, lng: Double, - maxResults: Int + maxResults: Int, ): Maybe> { return ReverseGeocodeObservable.create( ctxObservable.context, @@ -418,7 +403,7 @@ constructor( locationName: String, maxResults: Int, bounds: LatLngBounds? = null, - locale: Locale? = null + locale: Locale? = null, ): Maybe> { return GeocodeMaybe.create( ctxObservable.context, @@ -447,7 +432,7 @@ constructor( @RequiresPermission("android.permission.ACCESS_FINE_LOCATION") fun addGeofences( geofenceTransitionPendingIntent: PendingIntent, - request: GeofencingRequest + request: GeofencingRequest, ): Maybe { return AddGeofenceMaybeOnSubscribe.createMaybe( geofencingClient, @@ -521,126 +506,11 @@ constructor( * @see com.google.android.gms.location.SettingsApi */ fun checkLocationSettings( - locationRequest: LocationSettingsRequest + locationRequest: LocationSettingsRequest, ): Maybe { return settingsClient .checkLocationSettings(locationRequest) - .fromSuccessFailureToMaybe() - } - - /** - * Returns observable that fetches current place from Places API. To flatmap and auto release - * buffer to {@link com.google.android.gms.location.places.PlaceLikelihood} observable use - * {@link DataBufferObservable}. - * - * @param placeFilter filter - * @return observable that emits current places buffer and completes - */ - @RequiresPermission(allOf = ["android.permission.ACCESS_FINE_LOCATION", "android.permission.ACCESS_WIFI_STATE"]) - fun getCurrentPlace(placeFilter: FindCurrentPlaceRequest): Maybe { - return Places.createClient(ctxObservable.context).findCurrentPlace(placeFilter) - .toMaybe() - } - - /** - * Returns observable that fetches a place from the Places API using the place ID. - * - * @param placeId id for place - * @return observable that emits places buffer and completes - * - * @deprecated use {@link ReactiveLocationProvider#getPlaceCompatById(java.lang.String)} - */ - fun getPlaceById(placeId: String): Maybe { - val listOf = listOf( - Place.Field.ID, - Place.Field.LAT_LNG, - Place.Field.ADDRESS - ) - return getPlaceById(placeId, listOf) - } - - /** - * Returns observable that fetches autocomplete predictions from Places API. To flatmap and autorelease - * {@link com.google.android.gms.location.places.AutocompletePredictionBuffer} you can use - * {@link DataBufferObservable}. - * - * @param query search query - * @param bounds bounds where to fetch suggestions from - * @param filter filter - * @return observable with suggestions buffer and completes - * - * @deprecated use {@link ReactiveLocationProvider#getPlaceCompatAutocompletePredictions(java.lang.String, com.google.android.gms.maps.model.LatLngBounds, com.google.android.libraries.places.compat.AutocompleteFilter)} - */ - fun getPlaceAutocompletePredictions(result: FindAutocompletePredictionsRequest): Maybe> { - return Places.createClient(ctxObservable.context) - .findAutocompletePredictions(result) - .toMaybe() - .map { obj: FindAutocompletePredictionsResponse -> obj.autocompletePredictions } - } - - /** - * Returns observable that fetches photo metadata from the Places API using the place ID. - * - * @param placeId id for place - * @return observable that emits metadata buffer and completes - */ - fun getPhotoMetadataById( - placeId: String, - @IntRange(from = 0L) height: Int, - @IntRange(from = 0L) width: Int - ): Maybe { - return getPlaceById(placeId, listOf(Place.Field.PHOTO_METADATAS)) - .flatMap { res -> - val photoMetadata = res.place.photoMetadatas?.firstOrNull() - if (photoMetadata == null) { - Maybe.empty() - } else { - Places.createClient(ctxObservable.context) - .fetchPhoto( - FetchPhotoRequest.builder( - PhotoMetadata.builder(placeId) - .setHeight(height) - .setWidth(width) - .setAttributions(photoMetadata.attributions) - .build() - ) - .build() - ) - .toMaybe() - .map { it.bitmap } - } - } - } - - fun getPlaceById( - placeId: String, - fields: List - ): Maybe { - return Places.createClient(ctxObservable.context) - .fetchPlace( - FetchPlaceRequest.builder( - placeId, - fields - ).build() - ) - .toMaybe() - } - - /** - * Returns observable that fetches a placePhotoMetadata from the Places API using the place placePhotoMetadata metadata. - * Use after fetching the place placePhotoMetadata metadata with [ReactiveLocationProvider.getPhotoMetadataById] - * - * @param placePhotoMetadata the place photo meta data - * @return observable that emits the photo result and completes - */ - fun getPhotoForMetadata(placePhotoMetadata: PhotoMetadata): Maybe { - return Places.createClient(ctxObservable.context) - .fetchPhoto( - FetchPhotoRequest.builder(placePhotoMetadata) - .build() - ) .toMaybe() - .map { it.bitmap } } /** diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/ReactivePlacesProvider.kt b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/ReactivePlacesProvider.kt new file mode 100644 index 00000000..c58d1a2d --- /dev/null +++ b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/ReactivePlacesProvider.kt @@ -0,0 +1,141 @@ +package pl.charmas.android.reactivelocation2 + +import android.content.Context +import android.graphics.Bitmap +import androidx.annotation.IntRange +import androidx.annotation.RequiresPermission +import com.google.android.libraries.places.api.Places +import com.google.android.libraries.places.api.model.AutocompletePrediction +import com.google.android.libraries.places.api.model.PhotoMetadata +import com.google.android.libraries.places.api.model.Place +import com.google.android.libraries.places.api.net.FetchPhotoRequest +import com.google.android.libraries.places.api.net.FetchPlaceRequest +import com.google.android.libraries.places.api.net.FetchPlaceResponse +import com.google.android.libraries.places.api.net.FindAutocompletePredictionsRequest +import com.google.android.libraries.places.api.net.FindAutocompletePredictionsResponse +import com.google.android.libraries.places.api.net.FindCurrentPlaceRequest +import com.google.android.libraries.places.api.net.FindCurrentPlaceResponse +import io.reactivex.Maybe +import pl.charmas.android.reactivelocation2.ext.toMaybe + +class ReactivePlacesProvider constructor( + val context: Context, +) { + + private val placesClient = Places.createClient(context) + + /** + * Returns observable that fetches current place from Places API. To flatmap and auto release + * buffer to {@link com.google.android.gms.location.places.PlaceLikelihood} observable use + * {@link DataBufferObservable}. + * + * @param placeFilter filter + * @return observable that emits current places buffer and completes + */ + @RequiresPermission(allOf = ["android.permission.ACCESS_FINE_LOCATION", "android.permission.ACCESS_WIFI_STATE"]) + fun getCurrentPlace(placeFilter: FindCurrentPlaceRequest): Maybe { + return placesClient.findCurrentPlace(placeFilter) + .toMaybe() + } + + /** + * Returns observable that fetches autocomplete predictions from Places API. To flatmap and autorelease + * {@link com.google.android.gms.location.places.AutocompletePredictionBuffer} you can use + * {@link DataBufferObservable}. + * + * @param query search query + * @param bounds bounds where to fetch suggestions from + * @param filter filter + * @return observable with suggestions buffer and completes + * + * @deprecated use {@link ReactiveLocationProvider#getPlaceCompatAutocompletePredictions(java.lang.String, com.google.android.gms.maps.model.LatLngBounds, com.google.android.libraries.places.compat.AutocompleteFilter)} + */ + fun getPlaceAutocompletePredictions(result: FindAutocompletePredictionsRequest): Maybe> { + return placesClient + .findAutocompletePredictions(result) + .toMaybe() + .map { obj: FindAutocompletePredictionsResponse -> obj.autocompletePredictions } + } + + /** + * Returns observable that fetches photo metadata from the Places API using the place ID. + * + * @param placeId id for place + * @return observable that emits metadata buffer and completes + */ + fun getPhotoMetadataById( + placeId: String, + @IntRange(from = 0L) height: Int, + @IntRange(from = 0L) width: Int, + ): Maybe { + return getPlaceById(placeId, listOf(Place.Field.PHOTO_METADATAS)) + .flatMap { res -> + val photoMetadata = res.place.photoMetadatas?.firstOrNull() + if (photoMetadata == null) { + Maybe.empty() + } else { + placesClient + .fetchPhoto( + FetchPhotoRequest.builder( + PhotoMetadata.builder(placeId) + .setHeight(height) + .setWidth(width) + .setAttributions(photoMetadata.attributions) + .build() + ) + .build() + ) + .toMaybe() + .map { it.bitmap } + } + } + } + + fun getPlaceById( + placeId: String, + fields: List, + ): Maybe { + return placesClient + .fetchPlace( + FetchPlaceRequest.builder( + placeId, + fields + ).build() + ) + .toMaybe() + } + + /** + * Returns observable that fetches a placePhotoMetadata from the Places API using the place placePhotoMetadata metadata. + * Use after fetching the place placePhotoMetadata metadata with [ReactiveLocationProvider.getPhotoMetadataById] + * + * @param placePhotoMetadata the place photo meta data + * @return observable that emits the photo result and completes + */ + fun getPhotoForMetadata(placePhotoMetadata: PhotoMetadata): Maybe { + return placesClient + .fetchPhoto( + FetchPhotoRequest.builder(placePhotoMetadata) + .build() + ) + .toMaybe() + .map { it.bitmap } + } + + /** + * Returns observable that fetches a place from the Places API using the place ID. + * + * @param placeId id for place + * @return observable that emits places buffer and completes + * + * @deprecated use {@link ReactiveLocationProvider#getPlaceCompatById(java.lang.String)} + */ + fun getPlaceById(placeId: String): Maybe { + val listOf = listOf( + Place.Field.ID, + Place.Field.LAT_LNG, + Place.Field.ADDRESS + ) + return getPlaceById(placeId, listOf) + } +} diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/ext/TasksExt.kt b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/ext/TasksExt.kt index bde1d6e5..6f9ca31a 100644 --- a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/ext/TasksExt.kt +++ b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/ext/TasksExt.kt @@ -1,23 +1,9 @@ package pl.charmas.android.reactivelocation2.ext -import com.google.android.gms.common.api.PendingResult -import com.google.android.gms.common.api.Result import com.google.android.gms.tasks.Task import io.reactivex.Maybe -import io.reactivex.MaybeEmitter -import io.reactivex.Observable -import pl.charmas.android.reactivelocation2.observables.PendingResultObservableOnSubscribe import pl.charmas.android.reactivelocation2.observables.TaskResultMaybeOnSubscribe -import pl.charmas.android.reactivelocation2.observables.TaskSuccessFailureMaybeOnSubscribe fun Task.toMaybe(): Maybe { return Maybe.create(TaskResultMaybeOnSubscribe(this)) } - -fun Task.fromSuccessFailureToMaybe(): Maybe { - return Maybe.create(TaskSuccessFailureMaybeOnSubscribe(this)) -} - -fun PendingResult.toObservable(): Observable { - return Observable.create(PendingResultObservableOnSubscribe(this)) -} diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/TaskResultMaybeOnSubscribe.kt b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/TaskResultMaybeOnSubscribe.kt index bc1d6f27..d7f88bf1 100644 --- a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/TaskResultMaybeOnSubscribe.kt +++ b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/TaskResultMaybeOnSubscribe.kt @@ -4,7 +4,7 @@ import com.google.android.gms.tasks.Task import io.reactivex.MaybeEmitter import io.reactivex.MaybeOnSubscribe -class TaskResultMaybeOnSubscribe(private val task: Task) : +class TaskResultMaybeOnSubscribe constructor(private val task: Task) : MaybeOnSubscribe { override fun subscribe(emitter: MaybeEmitter) { task.addOnSuccessListener { result: T? -> diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/TaskSuccessFailureMaybeOnSubscribe.kt b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/TaskSuccessFailureMaybeOnSubscribe.kt deleted file mode 100644 index 9e5915b5..00000000 --- a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/TaskSuccessFailureMaybeOnSubscribe.kt +++ /dev/null @@ -1,21 +0,0 @@ -package pl.charmas.android.reactivelocation2.observables - -import com.google.android.gms.tasks.Task -import io.reactivex.MaybeEmitter -import io.reactivex.MaybeOnSubscribe - -class TaskSuccessFailureMaybeOnSubscribe(private val task: Task) : - MaybeOnSubscribe { - override fun subscribe(emitter: MaybeEmitter) { - task.addOnSuccessListener { t: T -> - if (!emitter.isDisposed) { - emitter.onSuccess(t) - } - } - task.addOnFailureListener { exception -> - if (!emitter.isDisposed) { - emitter.onError(exception) - } - } - } -} \ No newline at end of file diff --git a/build.gradle b/build.gradle index 9304b1bf..8875f9fb 100644 --- a/build.gradle +++ b/build.gradle @@ -8,7 +8,7 @@ buildscript { google() } dependencies { - classpath 'com.android.tools.build:gradle:4.1.0' + classpath 'com.android.tools.build:gradle:4.1.2' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } From 7ad11d08c6b5368317625ffe800f1289ffa8b5f1 Mon Sep 17 00:00:00 2001 From: Nikolay Unuchek Date: Wed, 3 Feb 2021 14:05:48 +0300 Subject: [PATCH 17/21] small fixes --- .../reactivelocation2/DataBufferObservable.kt | 4 +- .../GoogleAPIClientMaybeOnSubscribe.kt | 5 +- .../observables/MaybeEmitterWrapper.kt | 6 ++- .../observables/ObservableEmitterWrapper.kt | 4 +- ...emoveGeofenceRequestIdsMaybeOnSubscribe.kt | 10 ++-- ...ddLocationIntentUpdatesMaybeOnSubscribe.kt | 10 ++-- .../LocationUpdatesObservableOnSubscribe.kt | 2 +- .../MockLocationObservableOnSubscribe.kt | 51 ++++++++++--------- ...ationIntentUpdatesObservableOnSubscribe.kt | 10 ++-- 9 files changed, 60 insertions(+), 42 deletions(-) diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/DataBufferObservable.kt b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/DataBufferObservable.kt index 41a1142c..12735a21 100644 --- a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/DataBufferObservable.kt +++ b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/DataBufferObservable.kt @@ -21,7 +21,9 @@ object DataBufferObservable { emitter.setDisposable(Disposables.fromAction { buffer.release() }) for (item in buffer) { if (!emitter.isDisposed) { - emitter.onNext(item) + if (item != null) { + emitter.onNext(item) + } } } if (!emitter.isDisposed) { diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/GoogleAPIClientMaybeOnSubscribe.kt b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/GoogleAPIClientMaybeOnSubscribe.kt index fb22408a..4caf52bb 100644 --- a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/GoogleAPIClientMaybeOnSubscribe.kt +++ b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/GoogleAPIClientMaybeOnSubscribe.kt @@ -15,8 +15,9 @@ class GoogleAPIClientMaybeOnSubscribe @SafeVarargs private constructor( apiClient: GoogleApiClient, emitter: MaybeEmitter ) { - if (emitter.isDisposed) return - emitter.onSuccess(apiClient) + if (!emitter.isDisposed) { + emitter.onSuccess(apiClient) + } } companion object { diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/MaybeEmitterWrapper.kt b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/MaybeEmitterWrapper.kt index 1b8bdd6f..1ab083b9 100644 --- a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/MaybeEmitterWrapper.kt +++ b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/MaybeEmitterWrapper.kt @@ -22,7 +22,11 @@ class MaybeEmitterWrapper(private val emitter: MaybeEmitter) : override fun onSuccess(value: T) { if (!emitter.isDisposed) { - emitter.onSuccess(value) + if (value != null) { + emitter.onSuccess(value) + }else{ + emitter.onComplete() + } } } } \ No newline at end of file diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/ObservableEmitterWrapper.kt b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/ObservableEmitterWrapper.kt index c7c54970..3e5e7f6c 100644 --- a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/ObservableEmitterWrapper.kt +++ b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/ObservableEmitterWrapper.kt @@ -9,7 +9,9 @@ class ObservableEmitterWrapper(private val emitter: ObservableEmitter) : override fun onSubscribe(d: Disposable) {} override fun onNext(value: T) { if (!emitter.isDisposed) { - emitter.onNext(value) + if (value != null) { + emitter.onNext(value) + } } } diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/geofence/RemoveGeofenceRequestIdsMaybeOnSubscribe.kt b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/geofence/RemoveGeofenceRequestIdsMaybeOnSubscribe.kt index 8b7f2b2b..60d0f2a8 100644 --- a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/geofence/RemoveGeofenceRequestIdsMaybeOnSubscribe.kt +++ b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/geofence/RemoveGeofenceRequestIdsMaybeOnSubscribe.kt @@ -17,12 +17,14 @@ internal class RemoveGeofenceRequestIdsMaybeOnSubscribe constructor( ) { geofencingClient.removeGeofences(geofenceRequestIds) .addOnSuccessListener { - if (emitter.isDisposed) return@addOnSuccessListener - emitter.onSuccess(true) + if (!emitter.isDisposed) { + emitter.onSuccess(true) + } } .addOnFailureListener { error -> - if (emitter.isDisposed) return@addOnFailureListener - emitter.onError(error) + if (!emitter.isDisposed) { + emitter.onError(error) + } } } } \ No newline at end of file diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/location/AddLocationIntentUpdatesMaybeOnSubscribe.kt b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/location/AddLocationIntentUpdatesMaybeOnSubscribe.kt index 43cca943..08bde7c5 100644 --- a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/location/AddLocationIntentUpdatesMaybeOnSubscribe.kt +++ b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/location/AddLocationIntentUpdatesMaybeOnSubscribe.kt @@ -25,12 +25,14 @@ class AddLocationIntentUpdatesMaybeOnSubscribe private constructor( ) { fusedLocationProviderClient.requestLocationUpdates(locationRequest, intent) .addOnSuccessListener { - if (emitter.isDisposed) return@addOnSuccessListener - emitter.onSuccess(true) + if (!emitter.isDisposed) { + emitter.onSuccess(true) + } } .addOnFailureListener { error -> - if (emitter.isDisposed) return@addOnFailureListener - emitter.onError(error) + if (!emitter.isDisposed) { + emitter.onError(error) + } } } diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/location/LocationUpdatesObservableOnSubscribe.kt b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/location/LocationUpdatesObservableOnSubscribe.kt index ab7160c3..cf26ec4a 100644 --- a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/location/LocationUpdatesObservableOnSubscribe.kt +++ b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/location/LocationUpdatesObservableOnSubscribe.kt @@ -36,7 +36,7 @@ class LocationUpdatesObservableOnSubscribe private constructor( } override fun onDisposed(locationClient: GoogleApiClient) { - if (locationClient.isConnected) { + if (locationClient.isConnected && listener != null) { fusedLocationProviderClient.removeLocationUpdates(listener) } } diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/location/MockLocationObservableOnSubscribe.kt b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/location/MockLocationObservableOnSubscribe.kt index 6fc5d840..3ccc1566 100644 --- a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/location/MockLocationObservableOnSubscribe.kt +++ b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/location/MockLocationObservableOnSubscribe.kt @@ -14,26 +14,25 @@ import pl.charmas.android.reactivelocation2.observables.ObservableFactory class MockLocationObservableOnSubscribe private constructor( private val fusedLocationProviderClient: FusedLocationProviderClient, ctx: ObservableContext, - private val locationObservable: Observable + private val locationObservable: Observable, ) : BaseLocationObservableOnSubscribe(ctx) { private var mockLocationSubscription: Disposable? = null @SuppressLint("MissingPermission") protected override fun onGoogleApiClientReady( apiClient: GoogleApiClient, - emitter: ObservableEmitter + emitter: ObservableEmitter, ) { // this throws SecurityException if permissions are bad or mock locations are not enabled, // which is passed to observer's onError by BaseObservable fusedLocationProviderClient.setMockMode(true) - .addOnSuccessListener { sd: Void? -> - startLocationMocking( - emitter - ) + .addOnSuccessListener { + startLocationMocking(emitter) } - .addOnFailureListener { sd: Exception? -> - if (emitter.isDisposed) return@addOnFailureListener - emitter.onError(sd!!) + .addOnFailureListener { exception -> + if (!emitter.isDisposed) { + emitter.onError(exception) + } } } @@ -41,22 +40,26 @@ class MockLocationObservableOnSubscribe private constructor( private fun startLocationMocking(emitter: ObservableEmitter) { mockLocationSubscription = locationObservable .subscribe({ location -> - fusedLocationProviderClient.setMockLocation(location) - .addOnSuccessListener { d: Void? -> - if (emitter.isDisposed) return@addOnSuccessListener + fusedLocationProviderClient.setMockLocation(location) + .addOnSuccessListener { + if (!emitter.isDisposed) { emitter.onNext(true) } - .addOnFailureListener { error: Exception? -> - if (emitter.isDisposed) return@addOnFailureListener - emitter.onError(error!!) + } + .addOnFailureListener { error -> + if (!emitter.isDisposed) { + emitter.onError(error) } - }, - { throwable: Throwable? -> - if (emitter.isDisposed) return@subscribe - emitter.onError(throwable!!) + } + }, + { throwable -> + if (!emitter.isDisposed) { + emitter.onError(throwable) + } }) { - if (emitter.isDisposed) return@subscribe - emitter.onComplete() + if (!emitter.isDisposed) { + emitter.onComplete() + } } } @@ -70,8 +73,8 @@ class MockLocationObservableOnSubscribe private constructor( // and the observer's onError will already have been called } } - if (mockLocationSubscription != null && !mockLocationSubscription!!.isDisposed) { - mockLocationSubscription!!.dispose() + if (mockLocationSubscription?.isDisposed != true) { + mockLocationSubscription?.dispose() } } @@ -80,7 +83,7 @@ class MockLocationObservableOnSubscribe private constructor( fusedLocationProviderClient: FusedLocationProviderClient, context: ObservableContext, factory: ObservableFactory, - locationObservable: Observable + locationObservable: Observable, ): Observable { return factory.create( MockLocationObservableOnSubscribe( diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/location/RemoveLocationIntentUpdatesObservableOnSubscribe.kt b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/location/RemoveLocationIntentUpdatesObservableOnSubscribe.kt index 517384e1..c371debb 100644 --- a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/location/RemoveLocationIntentUpdatesObservableOnSubscribe.kt +++ b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/location/RemoveLocationIntentUpdatesObservableOnSubscribe.kt @@ -20,12 +20,14 @@ class RemoveLocationIntentUpdatesObservableOnSubscribe private constructor( ) { fusedLocationProviderClient.removeLocationUpdates(intent) .addOnCompleteListener { - if (emitter.isDisposed) return@addOnCompleteListener - emitter.onSuccess(true) + if (!emitter.isDisposed) { + emitter.onSuccess(true) + } } .addOnFailureListener { error -> - if (emitter.isDisposed) return@addOnFailureListener - emitter.onError(error) + if (!emitter.isDisposed) { + emitter.onError(error) + } } } From caaff9ee3b4d70845f681cdbf89fb672d6ba985e Mon Sep 17 00:00:00 2001 From: Nikolay Unuchek Date: Thu, 18 Feb 2021 18:08:43 +0300 Subject: [PATCH 18/21] fixed fetch of photos --- .../ReactivePlacesProvider.kt | 78 +++++++++++++------ .../android/reactivelocation2/ext/MaybeExt.kt | 9 +++ 2 files changed, 64 insertions(+), 23 deletions(-) diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/ReactivePlacesProvider.kt b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/ReactivePlacesProvider.kt index c58d1a2d..8bcdadc0 100644 --- a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/ReactivePlacesProvider.kt +++ b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/ReactivePlacesProvider.kt @@ -16,7 +16,11 @@ import com.google.android.libraries.places.api.net.FindAutocompletePredictionsRe import com.google.android.libraries.places.api.net.FindCurrentPlaceRequest import com.google.android.libraries.places.api.net.FindCurrentPlaceResponse import io.reactivex.Maybe +import io.reactivex.Observable +import pl.charmas.android.reactivelocation2.ext.errorSkip +import pl.charmas.android.reactivelocation2.ext.mapOrEmpty import pl.charmas.android.reactivelocation2.ext.toMaybe +import java.util.Collections class ReactivePlacesProvider constructor( val context: Context, @@ -63,32 +67,60 @@ class ReactivePlacesProvider constructor( * @param placeId id for place * @return observable that emits metadata buffer and completes */ - fun getPhotoMetadataById( + fun getPhotosByPlaceId( placeId: String, - @IntRange(from = 0L) height: Int, - @IntRange(from = 0L) width: Int, - ): Maybe { - return getPlaceById(placeId, listOf(Place.Field.PHOTO_METADATAS)) - .flatMap { res -> - val photoMetadata = res.place.photoMetadatas?.firstOrNull() - if (photoMetadata == null) { - Maybe.empty() - } else { - placesClient - .fetchPhoto( - FetchPhotoRequest.builder( - PhotoMetadata.builder(placeId) - .setHeight(height) - .setWidth(width) - .setAttributions(photoMetadata.attributions) + @IntRange(from = 1L, to = 1600L) height: Int, + @IntRange(from = 1L, to = 1600L) width: Int, + ): Maybe> { + return getPlaceById(placeId, Collections.singletonList(Place.Field.PHOTO_METADATAS)) + .mapOrEmpty { it.place.photoMetadatas } + .filter { it.isNotEmpty() } + .flatMapSingle { photoMetadatas -> + Observable.fromIterable(photoMetadatas) + .flatMapMaybe { photoMetadata -> + placesClient + .fetchPhoto( + FetchPhotoRequest.builder( + photoMetadata + ) + .setMaxHeight(height) + .setMaxWidth(width) .build() ) - .build() + .toMaybe() + .errorSkip() + } + .map { it.bitmap } + .toList() + } + .filter { it.isNotEmpty() } + } + /** + * Returns observable that fetches photo metadata from the Places API using the place ID. + * + * @param placeId id for place + * @return observable that emits metadata buffer and completes + */ + fun getFirstPhotoByPlaceId( + placeId: String, + @IntRange(from = 1L, to = 1600L) height: Int, + @IntRange(from = 1L, to = 1600L) width: Int, + ): Maybe { + return getPlaceById(placeId, Collections.singletonList(Place.Field.PHOTO_METADATAS)) + .mapOrEmpty { it.place.photoMetadatas?.firstOrNull() } + .flatMap { photoMetadata -> + placesClient + .fetchPhoto( + FetchPhotoRequest.builder( + photoMetadata ) - .toMaybe() - .map { it.bitmap } - } + .setMaxHeight(height) + .setMaxWidth(width) + .build() + ) + .toMaybe() } + .map { it.bitmap } } fun getPlaceById( @@ -97,10 +129,10 @@ class ReactivePlacesProvider constructor( ): Maybe { return placesClient .fetchPlace( - FetchPlaceRequest.builder( + FetchPlaceRequest.newInstance( placeId, fields - ).build() + ) ) .toMaybe() } diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/ext/MaybeExt.kt b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/ext/MaybeExt.kt index 06d48674..8749f0ee 100644 --- a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/ext/MaybeExt.kt +++ b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/ext/MaybeExt.kt @@ -28,3 +28,12 @@ fun Maybe.mapOrEmpty(mapper: (T) -> R?): Maybe { mapper(item)?.let { Maybe.just(it) } ?: Maybe.empty() } } + +fun Maybe.errorSkip(printStackTrace: Boolean = false): Maybe { + return this.onErrorResumeNext { throwable: Throwable -> + if (printStackTrace) { + throwable.printStackTrace() + } + Maybe.empty() + } +} From 72932887bf644a4aad8bdd2c466f4ab8c236460c Mon Sep 17 00:00:00 2001 From: Nikolay Unuchek Date: Thu, 18 Feb 2021 18:32:41 +0300 Subject: [PATCH 19/21] up kotlin_version version 1.4.30 up places version 2.4.0 --- android-reactive-location/build.gradle | 2 +- build.gradle | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/android-reactive-location/build.gradle b/android-reactive-location/build.gradle index f4ea71e0..9ff949df 100644 --- a/android-reactive-location/build.gradle +++ b/android-reactive-location/build.gradle @@ -27,7 +27,7 @@ dependencies { implementation ('com.google.android.gms:play-services-location:17.1.0'){ exclude group: 'com.google.android.gms', module: 'play-services-places' } - implementation 'com.google.android.libraries.places:places:2.3.0' + implementation 'com.google.android.libraries.places:places:2.4.0' implementation 'io.reactivex.rxjava2:rxjava:2.2.20' implementation 'io.reactivex.rxjava2:rxandroid:2.1.1' } diff --git a/build.gradle b/build.gradle index 8875f9fb..22f8808f 100644 --- a/build.gradle +++ b/build.gradle @@ -1,7 +1,7 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { - ext.kotlin_version = '1.4.10' + ext.kotlin_version = '1.4.30' repositories { mavenCentral() jcenter() From c5ea8b824801d574eb673673fc4b405a7a3951c1 Mon Sep 17 00:00:00 2001 From: Nikolay Unuchek Date: Mon, 15 Aug 2022 16:58:22 +0300 Subject: [PATCH 20/21] fixed googleapis link --- .../observables/geocode/FallbackReverseGeocodeObservable.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/geocode/FallbackReverseGeocodeObservable.kt b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/geocode/FallbackReverseGeocodeObservable.kt index ebfd9495..1923645b 100644 --- a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/geocode/FallbackReverseGeocodeObservable.kt +++ b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/geocode/FallbackReverseGeocodeObservable.kt @@ -46,7 +46,7 @@ internal class FallbackReverseGeocodeObservable( private fun alternativeReverseGeocodeQuery(): List
{ val url = URL( "http://maps.googleapis.com/maps/api/geocode/json?" - + "latlng=$latitude},${longitude}&sensor=true&key=$apiKey&language=${locale.language}" + + "latlng=$latitude,${longitude}&sensor=true&key=$apiKey&language=${locale.language}" ) val urlConnection = url.openConnection() as HttpURLConnection val stringBuilder = StringBuilder() From e1cec8b78ee6d7644f668126e0038634e2b7ddea Mon Sep 17 00:00:00 2001 From: Nikolay Unuchek Date: Mon, 15 Aug 2022 17:18:39 +0300 Subject: [PATCH 21/21] fixed {"error_message":"Requests to this API must be over SSL. Load the API with \"https:\/\/\" instead of \"http:\/\/\".","results":[],"status":"REQUEST_DENIED"} --- .../observables/geocode/FallbackReverseGeocodeObservable.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/geocode/FallbackReverseGeocodeObservable.kt b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/geocode/FallbackReverseGeocodeObservable.kt index 1923645b..a5444fa0 100644 --- a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/geocode/FallbackReverseGeocodeObservable.kt +++ b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/geocode/FallbackReverseGeocodeObservable.kt @@ -36,7 +36,7 @@ internal class FallbackReverseGeocodeObservable( /** * This function fetches a list of addresses for the set latitude, longitude and maxResults properties from the - * Google Geocode API (http://maps.googleapis.com/maps/api/geocode). + * Google Geocode API (https://maps.googleapis.com/maps/api/geocode). * * @return List of addresses * @throws IOException In case of network problems @@ -45,7 +45,7 @@ internal class FallbackReverseGeocodeObservable( @Throws(IOException::class, JSONException::class) private fun alternativeReverseGeocodeQuery(): List
{ val url = URL( - "http://maps.googleapis.com/maps/api/geocode/json?" + "https://maps.googleapis.com/maps/api/geocode/json?" + "latlng=$latitude,${longitude}&sensor=true&key=$apiKey&language=${locale.language}" ) val urlConnection = url.openConnection() as HttpURLConnection