From 768a76941bb1d814b50431df43b3a57183924e36 Mon Sep 17 00:00:00 2001 From: g4rb4g3 Date: Wed, 10 Jul 2024 21:58:07 +0200 Subject: [PATCH] fix: crash on app start when permission is denied (#304) * fix: crash on app start when permission is denied * fix: callbacks invoked multiple times --- .../geolocation/GeolocationModule.java | 19 ++++--- .../PlayServicesLocationManager.java | 56 +++++++++++++------ 2 files changed, 51 insertions(+), 24 deletions(-) diff --git a/android/src/main/java/com/reactnativecommunity/geolocation/GeolocationModule.java b/android/src/main/java/com/reactnativecommunity/geolocation/GeolocationModule.java index c0bb80f..5b42ba4 100644 --- a/android/src/main/java/com/reactnativecommunity/geolocation/GeolocationModule.java +++ b/android/src/main/java/com/reactnativecommunity/geolocation/GeolocationModule.java @@ -23,6 +23,7 @@ import com.google.android.gms.common.GoogleApiAvailability; import java.util.ArrayList; +import java.util.Arrays; import java.util.Objects; public class GeolocationModule extends ReactContextBaseJavaModule { @@ -120,7 +121,7 @@ public void getCurrentPosition( requestAuthorization(args -> mLocationManager.getCurrentLocationData(options, success, error), error); } catch (SecurityException e) { - throwLocationPermissionMissing(e); + emitLocationPermissionMissing(e); } } @@ -139,10 +140,10 @@ public void startObserving(ReadableMap options) { } requestAuthorization(args -> mLocationManager.startObserving(options), args -> { - throw new SecurityException(args.toString()); + emitLocationPermissionMissing(new SecurityException(Arrays.toString(args))); }); } catch (SecurityException e) { - throwLocationPermissionMissing(e); + emitLocationPermissionMissing(e); } } @@ -159,11 +160,13 @@ public void stopObserving() { /** * Provides a clearer exception message than the default one. */ - private static void throwLocationPermissionMissing(SecurityException e) { - throw new SecurityException( - "Looks like the app doesn't have the permission to access location.\n" + - "Add the following line to your app's AndroidManifest.xml:\n" + - "", e); + private void emitLocationPermissionMissing(SecurityException e) { + String message = + "Looks like the app doesn't have the permission to access location.\n" + + "Add the following line to your app's AndroidManifest.xml:\n" + + "\n" + + e.getMessage(); + mLocationManager.emitError(PositionError.PERMISSION_DENIED, message); } private static class Configuration { diff --git a/android/src/main/java/com/reactnativecommunity/geolocation/PlayServicesLocationManager.java b/android/src/main/java/com/reactnativecommunity/geolocation/PlayServicesLocationManager.java index 5393fda..b7b8050 100644 --- a/android/src/main/java/com/reactnativecommunity/geolocation/PlayServicesLocationManager.java +++ b/android/src/main/java/com/reactnativecommunity/geolocation/PlayServicesLocationManager.java @@ -3,17 +3,17 @@ import android.annotation.SuppressLint; import android.app.Activity; import android.location.Location; -import android.os.Build; import android.os.Looper; import android.util.Log; import android.content.Context; import android.location.LocationManager; -import androidx.annotation.RequiresApi; +import androidx.annotation.NonNull; import com.facebook.react.bridge.Callback; import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.ReadableMap; +import com.facebook.react.bridge.WritableMap; import com.facebook.react.common.SystemClock; import com.facebook.react.modules.core.DeviceEventManagerModule; import com.google.android.gms.location.FusedLocationProviderClient; @@ -23,13 +23,7 @@ import com.google.android.gms.location.LocationResult; 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.location.Priority; import com.google.android.gms.location.SettingsClient; -import com.google.android.gms.tasks.OnSuccessListener; - -import java.util.function.Consumer; -import java.util.function.Function; @SuppressLint("MissingPermission") public class PlayServicesLocationManager extends BaseLocationManager { @@ -157,28 +151,58 @@ private boolean isAnyProviderAvailable() { return locationManager != null && (locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER) || locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER)); } - private LocationCallback createSingleLocationCallback(Callback success, Callback error){ + private LocationCallback createSingleLocationCallback(Callback success, Callback error) { + final CallbackHolder callbackHolder = new CallbackHolder(success, error); + return new LocationCallback() { @Override - public void onLocationResult(LocationResult locationResult) { - if (locationResult == null) { - error.invoke(PositionError.buildError(PositionError.POSITION_UNAVAILABLE, "No location provided (FusedLocationProvider/lastLocation).")); + public void onLocationResult(@NonNull LocationResult locationResult) { + Location location = locationResult.getLastLocation(); + + if (location == null) { + callbackHolder.error(PositionError.buildError(PositionError.POSITION_UNAVAILABLE, "No location provided (FusedLocationProvider/lastLocation).")); return; } - Location location = locationResult.getLastLocation(); - success.invoke(locationToMap(location)); + callbackHolder.success(location); mFusedLocationClient.removeLocationUpdates(mSingleLocationCallback); mSingleLocationCallback = null; } @Override - public void onLocationAvailability(LocationAvailability locationAvailability) { + public void onLocationAvailability(@NonNull LocationAvailability locationAvailability) { if (!locationAvailability.isLocationAvailable()) { - error.invoke(PositionError.buildError(PositionError.POSITION_UNAVAILABLE, "Location not available (FusedLocationProvider/lastLocation).")); + callbackHolder.error(PositionError.buildError(PositionError.POSITION_UNAVAILABLE, "Location not available (FusedLocationProvider/lastLocation).")); } } }; } + + private static class CallbackHolder { + Callback success; + Callback error; + public CallbackHolder(Callback success, Callback error) { + this.success = success; + this.error = error; + } + + public void error(WritableMap cause) { + if (this.error == null) { + Log.e(this.getClass().getSimpleName(), "tried to invoke null error callback -> " + cause.toString()); + return; + } + this.error.invoke(cause); + this.error = null; + } + + public void success(Location location) { + if (this.success == null) { + Log.e(this.getClass().getSimpleName(), "tried to invoke null success callback"); + return; + } + this.success.invoke(locationToMap(location)); + this.success = null; + } + } }