diff --git a/android/src/main/java/com/reactnativecommunity/geolocation/GeolocationModule.java b/android/src/main/java/com/reactnativecommunity/geolocation/GeolocationModule.java index 78b2f72..0216689 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.Objects; public class GeolocationModule extends ReactContextBaseJavaModule { @@ -49,13 +50,13 @@ public String getName() { public void setConfiguration(ReadableMap config) { mConfiguration = Configuration.fromReactMap(config); - onConfigutationChange(mConfiguration); + onConfigurationChange(mConfiguration); } - private void onConfigutationChange(Configuration config) { - if (config.locationProvider == "android" && mLocationManager instanceof PlayServicesLocationManager) { + private void onConfigurationChange(Configuration config) { + if (Objects.equals(config.locationProvider, "android") && mLocationManager instanceof PlayServicesLocationManager) { mLocationManager = new AndroidLocationManager(mLocationManager.mReactContext); - } else if (config.locationProvider == "playServices" && mLocationManager instanceof AndroidLocationManager) { + } else if (Objects.equals(config.locationProvider, "playServices") && mLocationManager instanceof AndroidLocationManager) { mLocationManager = new PlayServicesLocationManager(mLocationManager.mReactContext); } } @@ -68,7 +69,7 @@ public void requestAuthorization(final Callback success, final Callback error) { final PermissionsModule perms = getReactApplicationContext().getNativeModule(PermissionsModule.class); ArrayList permissions = new ArrayList<>(); permissions.add(Manifest.permission.ACCESS_COARSE_LOCATION); - permissions.add( Manifest.permission.ACCESS_FINE_LOCATION); + permissions.add(Manifest.permission.ACCESS_FINE_LOCATION); ReadableArray permissionsArray = JavaOnlyArray.from(permissions); final Callback onPermissionGranted = args -> { @@ -94,7 +95,7 @@ public void requestAuthorization(final Callback success, final Callback error) { } }; - perms.checkPermission(Manifest.permission.ACCESS_FINE_LOCATION, new PromiseImpl(onPermissionChecked, onPermissionCheckFailed)); + perms.checkPermission(Manifest.permission.ACCESS_FINE_LOCATION, new PromiseImpl(onPermissionChecked, args -> perms.checkPermission(Manifest.permission.ACCESS_COARSE_LOCATION, new PromiseImpl(onPermissionChecked, onPermissionCheckFailed)))); return; } diff --git a/android/src/main/java/com/reactnativecommunity/geolocation/PlayServicesLocationManager.java b/android/src/main/java/com/reactnativecommunity/geolocation/PlayServicesLocationManager.java index 6cc59cb..fdf836d 100644 --- a/android/src/main/java/com/reactnativecommunity/geolocation/PlayServicesLocationManager.java +++ b/android/src/main/java/com/reactnativecommunity/geolocation/PlayServicesLocationManager.java @@ -2,7 +2,11 @@ import android.annotation.SuppressLint; import android.location.Location; +import android.os.Build; import android.os.Looper; +import android.util.Log; + +import androidx.annotation.RequiresApi; import com.facebook.react.bridge.Callback; import com.facebook.react.bridge.ReactApplicationContext; @@ -10,33 +14,31 @@ import com.facebook.react.common.SystemClock; import com.facebook.react.modules.core.DeviceEventManagerModule; import com.google.android.gms.location.FusedLocationProviderClient; +import com.google.android.gms.location.LocationAvailability; import com.google.android.gms.location.LocationCallback; import com.google.android.gms.location.LocationRequest; 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 { private FusedLocationProviderClient mFusedLocationClient; private LocationCallback mLocationCallback; + private LocationCallback mSingleLocationCallback; + private SettingsClient mLocationServicesSettingsClient; protected PlayServicesLocationManager(ReactApplicationContext reactContext) { super(reactContext); - mFusedLocationClient = LocationServices.getFusedLocationProviderClient(reactContext.getCurrentActivity()); - mLocationCallback = new LocationCallback() { - @Override - public void onLocationResult(LocationResult locationResult) { - if (locationResult == null) { - emitError(PositionError.POSITION_UNAVAILABLE, "No location provided by FusedLocationProviderClient."); - return; - } - for (Location location : locationResult.getLocations()) { - mReactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) - .emit("geolocationDidChange", locationToMap(location)); - } - } - }; + mFusedLocationClient = LocationServices.getFusedLocationProviderClient(reactContext); + mLocationServicesSettingsClient = LocationServices.getSettingsClient(reactContext); } @Override @@ -44,16 +46,45 @@ public void getCurrentLocationData(ReadableMap options, Callback success, Callba AndroidLocationManager.LocationOptions locationOptions = AndroidLocationManager.LocationOptions.fromReactMap(options); try { - mFusedLocationClient.getCurrentLocation(locationOptions.highAccuracy ? Priority.PRIORITY_HIGH_ACCURACY : Priority.PRIORITY_LOW_POWER, null) + mFusedLocationClient.getLastLocation() .addOnSuccessListener(mReactContext.getCurrentActivity(), location -> { if (location != null) { if ((SystemClock.currentTimeMillis() - location.getTime()) < locationOptions.maximumAge) { success.invoke(locationToMap(location)); + } else { + error.invoke(PositionError.buildError( + PositionError.POSITION_UNAVAILABLE, "Last found location is older than maximumAge (FusedLocationProvider/lastLocation).") + ); } } else { - error.invoke(PositionError.buildError( - PositionError.POSITION_UNAVAILABLE, "No location provided by FusedLocationProviderClient.") - ); + mSingleLocationCallback = new LocationCallback() { + @Override + public void onLocationResult(LocationResult locationResult) { + if (locationResult == null) { + emitError(PositionError.POSITION_UNAVAILABLE, "No location provided (FusedLocationProvider/lastLocation)."); + return; + } + + AndroidLocationManager.LocationOptions locationOptions = AndroidLocationManager.LocationOptions.fromReactMap(options); + Location location = locationResult.getLastLocation(); + if ((SystemClock.currentTimeMillis() - location.getTime()) < locationOptions.maximumAge) { + success.invoke(locationToMap(location)); + } else { + emitError(PositionError.POSITION_UNAVAILABLE, "Last found location is older than maximumAge (FusedLocationProvider/lastLocation)."); + } + + mFusedLocationClient.removeLocationUpdates(mSingleLocationCallback); + mSingleLocationCallback = null; + } + + @Override + public void onLocationAvailability(LocationAvailability locationAvailability) { + if (!locationAvailability.isLocationAvailable()) { + emitError(PositionError.POSITION_UNAVAILABLE, "Location not available (FusedLocationProvider/lastLocation)."); + } + } + }; + checkLocationSettings(options, mSingleLocationCallback); } }); } catch (SecurityException e) { @@ -63,6 +94,41 @@ public void getCurrentLocationData(ReadableMap options, Callback success, Callba @Override public void startObserving(ReadableMap options) { + mLocationCallback = new LocationCallback() { + @Override + public void onLocationResult(LocationResult locationResult) { + if (locationResult == null) { + emitError(PositionError.POSITION_UNAVAILABLE, "No location provided (FusedLocationProvider/observer)."); + return; + } + + AndroidLocationManager.LocationOptions locationOptions = AndroidLocationManager.LocationOptions.fromReactMap(options); + Location location = locationResult.getLastLocation(); + if ((SystemClock.currentTimeMillis() - location.getTime()) < locationOptions.maximumAge) { + mReactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) + .emit("geolocationDidChange", locationToMap(locationResult.getLastLocation())); + } else { + emitError(PositionError.POSITION_UNAVAILABLE, "Last found location is older than maximumAge (FusedLocationProvider/observer)."); + } + } + + @Override + public void onLocationAvailability(LocationAvailability locationAvailability) { + if (!locationAvailability.isLocationAvailable()) { + emitError(PositionError.POSITION_UNAVAILABLE, "Location not available (FusedLocationProvider)."); + } + } + }; + + checkLocationSettings(options, mLocationCallback); + } + + @Override + public void stopObserving() { + mFusedLocationClient.removeLocationUpdates(mLocationCallback); + } + + private void checkLocationSettings(ReadableMap options, LocationCallback locationCallback) { LocationOptions locationOptions = LocationOptions.fromReactMap(options); LocationRequest locationRequest = LocationRequest.create(); locationRequest.setInterval(locationOptions.interval); @@ -74,15 +140,21 @@ public void startObserving(ReadableMap options) { locationRequest.setPriority( locationOptions.highAccuracy ? LocationRequest.PRIORITY_HIGH_ACCURACY : LocationRequest.PRIORITY_LOW_POWER ); + + LocationSettingsRequest.Builder builder = new LocationSettingsRequest.Builder(); + builder.addLocationRequest(locationRequest); + LocationSettingsRequest locationSettingsRequest = builder.build(); + + mLocationServicesSettingsClient.checkLocationSettings(locationSettingsRequest) + .addOnSuccessListener(locationSettingsResponse -> requestLocationUpdates(locationRequest, locationCallback)) + .addOnFailureListener(err -> emitError(PositionError.POSITION_UNAVAILABLE, "Location not available (FusedLocationProvider/settings).")); + } + + private void requestLocationUpdates(LocationRequest locationRequest, LocationCallback locationCallback) { try { - mFusedLocationClient.requestLocationUpdates(locationRequest, mLocationCallback, Looper.getMainLooper()); + mFusedLocationClient.requestLocationUpdates(locationRequest, locationCallback, Looper.getMainLooper()); } catch (SecurityException e) { throw e; } } - - @Override - public void stopObserving() { - mFusedLocationClient.removeLocationUpdates(mLocationCallback); - } } diff --git a/example/android/app/build.gradle b/example/android/app/build.gradle index be562fd..3585e00 100644 --- a/example/android/app/build.gradle +++ b/example/android/app/build.gradle @@ -79,7 +79,8 @@ import org.apache.tools.ant.taskdefs.condition.Os */ project.ext.react = [ - enableHermes: false, // clean and rebuild if changing + enableHermes: true, // clean and rebuild if changing + entryFile: "index.tsx" ] apply from: "../../node_modules/react-native/react.gradle" diff --git a/example/src/configs/SetConfiguration.tsx b/example/src/configs/SetConfiguration.tsx index 00e7562..0940689 100644 --- a/example/src/configs/SetConfiguration.tsx +++ b/example/src/configs/SetConfiguration.tsx @@ -44,8 +44,9 @@ export default function SetConfigurationExample() { Geolocation.setRNConfiguration({ skipPermissionRequests, authorizationLevel, + locationProvider, }); - }, [skipPermissionRequests, authorizationLevel]); + }, [skipPermissionRequests, authorizationLevel, locationProvider]); return ( diff --git a/example/src/examples/GetCurrentLocation.tsx b/example/src/examples/GetCurrentLocation.tsx index bbf857a..4948806 100644 --- a/example/src/examples/GetCurrentLocation.tsx +++ b/example/src/examples/GetCurrentLocation.tsx @@ -9,7 +9,7 @@ 'use strict'; -import React, { useState, useEffect } from 'react'; +import React, { useState } from 'react'; import { StyleSheet, Text, View, Alert, Button } from 'react-native'; import Geolocation from '@react-native-community/geolocation'; @@ -25,9 +25,6 @@ export default function GetCurrentLocationExample() { }; const [position, setPosition] = useState(null); - useEffect(() => { - getCurrentPosition(); - }, []); return ( diff --git a/example/src/examples/WatchPosition.tsx b/example/src/examples/WatchPosition.tsx index 6f3c054..096a7d8 100644 --- a/example/src/examples/WatchPosition.tsx +++ b/example/src/examples/WatchPosition.tsx @@ -37,8 +37,6 @@ export default function WatchPositionExample() { const [position, setPosition] = useState(null); const [subscriptionId, setSubscriptionId] = useState(null); useEffect(() => { - watchPosition(); - return () => { clearWatch(); };