diff --git a/geolocator/CHANGELOG.md b/geolocator/CHANGELOG.md index dfe7dcd9..f29aa007 100644 --- a/geolocator/CHANGELOG.md +++ b/geolocator/CHANGELOG.md @@ -1,3 +1,7 @@ +## 13.1.0 + +- Adds updatePositionStream API for changing parameters while a position stream is running. + ## 13.0.2 - Updates dependency on geolocator_apple to version 2.3.8. diff --git a/geolocator/example/android/app/src/main/AndroidManifest.xml b/geolocator/example/android/app/src/main/AndroidManifest.xml index 829fcb8c..2f76ce97 100644 --- a/geolocator/example/android/app/src/main/AndroidManifest.xml +++ b/geolocator/example/android/app/src/main/AndroidManifest.xml @@ -8,6 +8,11 @@ + + + + + - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleExecutable - App - CFBundleIdentifier - io.flutter.flutter.app - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - App - CFBundlePackageType - FMWK - CFBundleShortVersionString - 1.0 - CFBundleSignature - ???? - CFBundleVersion - 1.0 - MinimumOSVersion - 11.0 - - + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + App + CFBundleIdentifier + io.flutter.flutter.app + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + App + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + MinimumOSVersion + 12.0 + + \ No newline at end of file diff --git a/geolocator/example/ios/Runner.xcodeproj/project.pbxproj b/geolocator/example/ios/Runner.xcodeproj/project.pbxproj index 19138798..6fd1a7ba 100644 --- a/geolocator/example/ios/Runner.xcodeproj/project.pbxproj +++ b/geolocator/example/ios/Runner.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 50; + objectVersion = 54; objects = { /* Begin PBXBuildFile section */ @@ -149,6 +149,7 @@ 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + 2169DAB2B55B50BFEA564873 /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -165,7 +166,7 @@ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 1020; + LastUpgradeCheck = 1510; ORGANIZATIONNAME = ""; TargetAttributes = { 97C146ED1CF9000F007C117D = { @@ -206,12 +207,31 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ + 2169DAB2B55B50BFEA564873 /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Copy Pods Resources"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", ); name = "Thin Binary"; outputPaths = ( @@ -244,6 +264,7 @@ }; 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); @@ -332,7 +353,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -415,7 +436,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -464,7 +485,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; diff --git a/geolocator/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/geolocator/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index a28140cf..e67b2808 100644 --- a/geolocator/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/geolocator/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ UIViewControllerBasedStatusBarAppearance + CADisableMinimumFrameDurationOnPhone + + UIApplicationSupportsIndirectInputEvents + diff --git a/geolocator/example/lib/main.dart b/geolocator/example/lib/main.dart index a2633dcc..444288df 100644 --- a/geolocator/example/lib/main.dart +++ b/geolocator/example/lib/main.dart @@ -1,8 +1,9 @@ import 'dart:async'; import 'dart:io' show Platform; -import 'package:baseflow_plugin_template/baseflow_plugin_template.dart'; import 'package:flutter/material.dart'; + +import 'package:baseflow_plugin_template/baseflow_plugin_template.dart'; import 'package:geolocator/geolocator.dart'; /// Defines the main theme color. @@ -39,9 +40,11 @@ class _GeolocatorWidgetState extends State { final GeolocatorPlatform _geolocatorPlatform = GeolocatorPlatform.instance; final List<_PositionItem> _positionItems = <_PositionItem>[]; + final ScrollController _scrollController = ScrollController(); StreamSubscription? _positionStreamSubscription; StreamSubscription? _serviceStatusStreamSubscription; bool positionStreamStarted = false; + bool fastUpdates = false; @override void initState() { @@ -120,6 +123,7 @@ class _GeolocatorWidgetState extends State { (context) => Scaffold( backgroundColor: Theme.of(context).colorScheme.surface, body: ListView.builder( + controller: _scrollController, itemCount: _positionItems.length, itemBuilder: (context, index) { final positionItem = _positionItems[index]; @@ -167,6 +171,17 @@ class _GeolocatorWidgetState extends State { : const Icon(Icons.pause), ), sizedBox, + if (_positionStreamSubscription != null) + Column( + mainAxisSize: MainAxisSize.min, + children: [ + FloatingActionButton( + onPressed: _toggleUpdateSpeed, + child: const Icon(Icons.speed), + ), + sizedBox, + ], + ), FloatingActionButton( onPressed: _getCurrentPosition, child: const Icon(Icons.my_location), @@ -252,9 +267,12 @@ class _GeolocatorWidgetState extends State { return true; } - void _updatePositionList(_PositionItemType type, String displayValue) { + void _updatePositionList(_PositionItemType type, String displayValue) async { _positionItems.add(_PositionItem(type, displayValue)); setState(() {}); + await Future.delayed(const Duration(milliseconds: 50)); + if (!mounted) return; + _scrollController.jumpTo(_scrollController.position.maxScrollExtent); } bool _isListening() => !(_positionStreamSubscription == null || @@ -299,7 +317,19 @@ class _GeolocatorWidgetState extends State { void _toggleListening() { if (_positionStreamSubscription == null) { - final positionStream = _geolocatorPlatform.getPositionStream(); + final settings = AndroidSettings( + distanceFilter: 50, + foregroundNotificationConfig: const ForegroundNotificationConfig( + notificationTitle: 'Geolocator is tracking your trip', + notificationText: + 'This is a persistent notification and is used to start a foreground service.', + enableWifiLock: true, + enableWakeLock: true, + setOngoing: true, + ), + ); + final positionStream = + _geolocatorPlatform.getPositionStream(locationSettings: settings); _positionStreamSubscription = positionStream.handleError((error) { _positionStreamSubscription?.cancel(); _positionStreamSubscription = null; @@ -331,6 +361,41 @@ class _GeolocatorWidgetState extends State { }); } + Future _toggleUpdateSpeed() async { + fastUpdates = !fastUpdates; + + final LocationSettings settings; + if (Platform.isAndroid) { + settings = AndroidSettings( + accuracy: LocationAccuracy.best, + distanceFilter: 0, + intervalDuration: Duration(seconds: fastUpdates ? 1 : 10), + forceLocationManager: false, + useMSLAltitude: true, + ); + } else if (Platform.isIOS) { + settings = AppleSettings( + accuracy: LocationAccuracy.best, + distanceFilter: fastUpdates ? 0 : 50, + activityType: ActivityType.fitness, + showBackgroundLocationIndicator: false, + allowBackgroundLocationUpdates: false, + ); + } else { + settings = LocationSettings( + accuracy: LocationAccuracy.best, + distanceFilter: fastUpdates ? 0 : 50, + ); + } + + await _geolocatorPlatform.updatePositionStream(locationSettings: settings); + + _updatePositionList( + _PositionItemType.log, + 'Position updates ${fastUpdates ? 'sped up' : 'slowed down'}.', + ); + } + @override void dispose() { if (_positionStreamSubscription != null) { diff --git a/geolocator/example/pubspec.yaml b/geolocator/example/pubspec.yaml index b5fc7b89..b38dd79c 100644 --- a/geolocator/example/pubspec.yaml +++ b/geolocator/example/pubspec.yaml @@ -9,7 +9,7 @@ environment: sdk: ">=2.15.0 <3.0.0" dependencies: - baseflow_plugin_template: ^2.1.2 + baseflow_plugin_template: ^2.2.0 flutter: sdk: flutter @@ -24,16 +24,16 @@ dependencies: # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. - cupertino_icons: ^1.0.1+1 + cupertino_icons: ^1.0.8 # The following adds the URL Launcher plugin which is used by # the demo application to open the links in the web browser. - url_launcher: ^6.0.0-nullsafety.1 + url_launcher: ^6.3.1 dev_dependencies: flutter_test: sdk: flutter - flutter_lints: ^3.0.1 + flutter_lints: ^5.0.0 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec diff --git a/geolocator/lib/geolocator.dart b/geolocator/lib/geolocator.dart index 008796a3..4710287d 100644 --- a/geolocator/lib/geolocator.dart +++ b/geolocator/lib/geolocator.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'package:flutter/foundation.dart'; + import 'package:geolocator_android/geolocator_android.dart'; import 'package:geolocator_apple/geolocator_apple.dart'; import 'package:geolocator_platform_interface/geolocator_platform_interface.dart'; @@ -192,6 +193,17 @@ class Geolocator { locationSettings: locationSettings, ); + /// Updates the parameters of the active position stream. + /// + /// Throws a [NotSubscribedException] when there is no active position stream + /// to update. + static Future updatePositionStream({ + required LocationSettings locationSettings, + }) => + GeolocatorPlatform.instance.updatePositionStream( + locationSettings: locationSettings, + ); + /// Returns a [Future] containing a [LocationAccuracyStatus] /// When the user has given permission for approximate location, /// [LocationAccuracyStatus.reduced] will be returned, if the user has diff --git a/geolocator/pubspec.yaml b/geolocator/pubspec.yaml index 12d08022..0fb7b4e0 100644 --- a/geolocator/pubspec.yaml +++ b/geolocator/pubspec.yaml @@ -2,7 +2,7 @@ name: geolocator description: Geolocation plugin for Flutter. This plugin provides a cross-platform (iOS, Android) API for generic location (GPS etc.) functions. repository: https://github.com/baseflow/flutter-geolocator/tree/main/geolocator issue_tracker: https://github.com/baseflow/flutter-geolocator/issues?q=is%3Aissue+is%3Aopen -version: 13.0.2 +version: 13.1.0 environment: sdk: ">=2.15.0 <4.0.0" @@ -26,15 +26,20 @@ dependencies: flutter: sdk: flutter - geolocator_platform_interface: ^4.2.3 - geolocator_android: ^4.6.0 - geolocator_apple: ^2.3.8 - geolocator_web: ^4.1.1 - geolocator_windows: ^0.2.3 + geolocator_platform_interface: + path: ../geolocator_platform_interface + geolocator_android: + path: ../geolocator_android + geolocator_apple: + path: ../geolocator_apple + geolocator_web: + path: ../geolocator_web + geolocator_windows: + path: ../geolocator_windows dev_dependencies: flutter_test: sdk: flutter - flutter_lints: ">=3.0.1 <5.0.0" - mockito: ^5.0.0-nullsafety.7 + flutter_lints: ">=5.0.0" + mockito: ^5.4.4 plugin_platform_interface: ^2.1.8 diff --git a/geolocator_android/android/src/main/java/com/baseflow/geolocator/GeolocatorLocationService.java b/geolocator_android/android/src/main/java/com/baseflow/geolocator/GeolocatorLocationService.java index 4c0e161c..05bb989f 100644 --- a/geolocator_android/android/src/main/java/com/baseflow/geolocator/GeolocatorLocationService.java +++ b/geolocator_android/android/src/main/java/com/baseflow/geolocator/GeolocatorLocationService.java @@ -1,5 +1,7 @@ package com.baseflow.geolocator; +import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION; + import android.annotation.SuppressLint; import android.app.Activity; import android.app.Notification; @@ -30,8 +32,8 @@ public class GeolocatorLocationService extends Service { private static final String TAG = "FlutterGeolocator"; private static final int ONGOING_NOTIFICATION_ID = 75415; private static final String CHANNEL_ID = "geolocator_channel_01"; - private final String WAKELOCK_TAG = "GeolocatorLocationService:Wakelock"; - private final String WIFILOCK_TAG = "GeolocatorLocationService:WifiLock"; + private static final String WAKELOCK_TAG = "GeolocatorLocationService:Wakelock"; + private static final String WIFILOCK_TAG = "GeolocatorLocationService:WifiLock"; private final LocalBinder binder = new LocalBinder(this); // Service is foreground private boolean isForeground = false; @@ -144,13 +146,16 @@ public void enableBackgroundMode(ForegroundNotificationOptions options) { this.getApplicationContext(), CHANNEL_ID, ONGOING_NOTIFICATION_ID, options); backgroundNotification.updateChannel(options.getNotificationChannelName()); Notification notification = backgroundNotification.build(); - startForeground(ONGOING_NOTIFICATION_ID, notification); - isForeground = true; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + startForeground(ONGOING_NOTIFICATION_ID, notification, FOREGROUND_SERVICE_TYPE_LOCATION); + } else { + startForeground(ONGOING_NOTIFICATION_ID, notification); + } + isForeground = true; } obtainWakeLocks(options); } - @SuppressWarnings("deprecation") public void disableBackgroundMode() { if (isForeground) { Log.d(TAG, "Stop service in foreground."); @@ -222,7 +227,7 @@ private int getWifiLockType() { return WifiManager.WIFI_MODE_FULL_LOW_LATENCY; } - class LocalBinder extends Binder { + static class LocalBinder extends Binder { private final GeolocatorLocationService locationService; LocalBinder(GeolocatorLocationService locationService) { diff --git a/geolocator_android/android/src/main/java/com/baseflow/geolocator/MethodCallHandlerImpl.java b/geolocator_android/android/src/main/java/com/baseflow/geolocator/MethodCallHandlerImpl.java index 297e12ac..e4e36601 100644 --- a/geolocator_android/android/src/main/java/com/baseflow/geolocator/MethodCallHandlerImpl.java +++ b/geolocator_android/android/src/main/java/com/baseflow/geolocator/MethodCallHandlerImpl.java @@ -82,6 +82,9 @@ public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result case "cancelGetCurrentPosition": onCancelGetCurrentPosition(call, result); break; + case "updatePositionStream": + onUpdatePositionStream(call, result); + break; case "openAppSettings": boolean hasOpenedAppSettings = Utils.openAppSettings(this.context); result.success(hasOpenedAppSettings); @@ -279,4 +282,21 @@ private void onCancelGetCurrentPosition(MethodCall call, MethodChannel.Result re result.success(null); } + + private void onUpdatePositionStream(MethodCall call, MethodChannel.Result result) { + @SuppressWarnings("unchecked") + Map arguments = (Map) call.arguments; + LocationOptions locationOptions = LocationOptions.parseArguments(arguments); + + boolean success = geolocationManager.updateLocationOptions(locationOptions); + + if (success) { + result.success(null); + } else { + result.error( + ErrorCodes.locationSubscriptionInactive.toString(), + ErrorCodes.locationSubscriptionInactive.toDescription(), + null); + } + } } diff --git a/geolocator_android/android/src/main/java/com/baseflow/geolocator/errors/ErrorCodes.java b/geolocator_android/android/src/main/java/com/baseflow/geolocator/errors/ErrorCodes.java index cea4141e..62114280 100644 --- a/geolocator_android/android/src/main/java/com/baseflow/geolocator/errors/ErrorCodes.java +++ b/geolocator_android/android/src/main/java/com/baseflow/geolocator/errors/ErrorCodes.java @@ -1,13 +1,18 @@ package com.baseflow.geolocator.errors; +import androidx.annotation.NonNull; + public enum ErrorCodes { activityMissing, errorWhileAcquiringPosition, locationServicesDisabled, + locationSubscriptionInactive, permissionDefinitionsNotFound, permissionDenied, permissionRequestInProgress; + @NonNull + @Override public String toString() { switch (this) { case activityMissing: @@ -16,6 +21,8 @@ public String toString() { return "ERROR_WHILE_ACQUIRING_POSITION"; case locationServicesDisabled: return "LOCATION_SERVICES_DISABLED"; + case locationSubscriptionInactive: + return "LOCATION_SUBSCRIPTION_INACTIVE"; case permissionDefinitionsNotFound: return "PERMISSION_DEFINITIONS_NOT_FOUND"; case permissionDenied: @@ -35,6 +42,8 @@ public String toDescription() { return "An unexpected error occurred while trying to acquire the device's position."; case locationServicesDisabled: return "Location services are disabled. To receive location updates the location services should be enabled."; + case locationSubscriptionInactive: + return "No active location stream subscription found."; case permissionDefinitionsNotFound: return "No location permissions are defined in the manifest. Make sure at least ACCESS_FINE_LOCATION or ACCESS_COARSE_LOCATION are defined in the manifest."; case permissionDenied: diff --git a/geolocator_android/android/src/main/java/com/baseflow/geolocator/location/FusedLocationClient.java b/geolocator_android/android/src/main/java/com/baseflow/geolocator/location/FusedLocationClient.java index a5e3f2b1..38d9cebe 100644 --- a/geolocator_android/android/src/main/java/com/baseflow/geolocator/location/FusedLocationClient.java +++ b/geolocator_android/android/src/main/java/com/baseflow/geolocator/location/FusedLocationClient.java @@ -40,10 +40,11 @@ class FusedLocationClient implements LocationClient { private final FusedLocationProviderClient fusedLocationProviderClient; private final NmeaClient nmeaClient; private final int activityRequestCode; - @Nullable private final LocationOptions locationOptions; + @Nullable private LocationOptions locationOptions; @Nullable private ErrorCallback errorCallback; @Nullable private PositionChangedCallback positionChangedCallback; + private boolean running = false; public FusedLocationClient(@NonNull Context context, @Nullable LocationOptions locationOptions) { this.context = context; @@ -74,8 +75,9 @@ public synchronized void onLocationResult(@NonNull LocationResult locationResult if (location.getExtras() == null) { location.setExtras(Bundle.EMPTY); } - if (locationOptions != null) { - location.getExtras().putBoolean(LocationOptions.USE_MSL_ALTITUDE_EXTRA, locationOptions.isUseMSLAltitude()); + if (FusedLocationClient.this.locationOptions != null) { + location.getExtras().putBoolean(LocationOptions.USE_MSL_ALTITUDE_EXTRA, + FusedLocationClient.this.locationOptions.isUseMSLAltitude()); } nmeaClient.enrichExtrasWithNmea(location); @@ -157,6 +159,7 @@ private void requestPositionUpdates(LocationOptions locationOptions) { this.nmeaClient.start(); fusedLocationProviderClient.requestLocationUpdates( locationRequest, locationCallback, Looper.getMainLooper()); + running = true; } @Override @@ -275,8 +278,17 @@ public void startPositionUpdates( }); } + @Override + public void updateLocationOptions(LocationOptions options) { + this.locationOptions = options; + if (running) { + requestPositionUpdates(options); + } + } + public void stopPositionUpdates() { this.nmeaClient.stop(); fusedLocationProviderClient.removeLocationUpdates(locationCallback); + running = false; } } diff --git a/geolocator_android/android/src/main/java/com/baseflow/geolocator/location/GeolocationManager.java b/geolocator_android/android/src/main/java/com/baseflow/geolocator/location/GeolocationManager.java index 76a4bb26..dd360dd9 100644 --- a/geolocator_android/android/src/main/java/com/baseflow/geolocator/location/GeolocationManager.java +++ b/geolocator_android/android/src/main/java/com/baseflow/geolocator/location/GeolocationManager.java @@ -3,6 +3,7 @@ import android.app.Activity; import android.content.Context; import android.content.Intent; +import android.util.Log; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -17,8 +18,9 @@ public class GeolocationManager implements io.flutter.plugin.common.PluginRegistry.ActivityResultListener { + private static final String TAG = "GeolocationManager"; - private static GeolocationManager geolocationManagerInstance = null; + private static GeolocationManager geolocationManagerInstance = null; private final List locationClients; @@ -64,6 +66,16 @@ public void startPositionUpdates( locationClient.startPositionUpdates(activity, positionChangedCallback, errorCallback); } + public boolean updateLocationOptions(LocationOptions options) { + int numClientsUpdated = 0; + for (LocationClient client : locationClients) { + client.updateLocationOptions(options); + numClientsUpdated += 1; + } + Log.d(TAG, String.format("Updated LocationOptions for %d LocationClient(s).", numClientsUpdated)); + return numClientsUpdated > 0; + } + public void stopPositionUpdates(@NonNull LocationClient locationClient) { locationClients.remove(locationClient); locationClient.stopPositionUpdates(); diff --git a/geolocator_android/android/src/main/java/com/baseflow/geolocator/location/LocationClient.java b/geolocator_android/android/src/main/java/com/baseflow/geolocator/location/LocationClient.java index 181f33ec..2854ecb6 100644 --- a/geolocator_android/android/src/main/java/com/baseflow/geolocator/location/LocationClient.java +++ b/geolocator_android/android/src/main/java/com/baseflow/geolocator/location/LocationClient.java @@ -19,6 +19,8 @@ void startPositionUpdates( PositionChangedCallback positionChangedCallback, ErrorCallback errorCallback); + void updateLocationOptions(LocationOptions options); + void stopPositionUpdates(); default boolean checkLocationService(Context context){ diff --git a/geolocator_android/android/src/main/java/com/baseflow/geolocator/location/LocationManagerClient.java b/geolocator_android/android/src/main/java/com/baseflow/geolocator/location/LocationManagerClient.java index c8b3e59a..a3ecbfac 100644 --- a/geolocator_android/android/src/main/java/com/baseflow/geolocator/location/LocationManagerClient.java +++ b/geolocator_android/android/src/main/java/com/baseflow/geolocator/location/LocationManagerClient.java @@ -9,6 +9,7 @@ import android.os.Build; import android.os.Bundle; import android.os.Looper; +import android.util.Log; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -22,14 +23,15 @@ import java.util.List; class LocationManagerClient implements LocationClient, LocationListenerCompat { - + private static final String TAG = "LocationManagerClient"; private static final long TWO_MINUTES = 120000; + private final LocationManager locationManager; private final NmeaClient nmeaClient; - @Nullable private final LocationOptions locationOptions; public Context context; private boolean isListening = false; + @Nullable private LocationOptions locationOptions; @Nullable private Location currentBestLocation; @Nullable private String currentLocationProvider; @Nullable private PositionChangedCallback positionChangedCallback; @@ -143,57 +145,77 @@ public boolean onActivityResult(int requestCode, int resultCode) { return false; } - @SuppressLint("MissingPermission") @Override public void startPositionUpdates( Activity activity, PositionChangedCallback positionChangedCallback, ErrorCallback errorCallback) { + this.positionChangedCallback = positionChangedCallback; + this.errorCallback = errorCallback; + + if (!checkLocationService(context)) { + errorCallback.onError(ErrorCodes.locationServicesDisabled); + return; + } + + LocationAccuracy accuracy = LocationAccuracy.best; + if (this.locationOptions != null) { + accuracy = locationOptions.getAccuracy(); + } + + this.currentLocationProvider = determineProvider(this.locationManager, accuracy); - if (!checkLocationService(context)) { - errorCallback.onError(ErrorCodes.locationServicesDisabled); + if (this.currentLocationProvider == null) { + errorCallback.onError(ErrorCodes.locationServicesDisabled); + return; + } + + requestPositionUpdates(); + } + + @SuppressLint("MissingPermission") + private void requestPositionUpdates() { + if (this.currentLocationProvider == null) { + Log.e(TAG, "Location provider was null."); return; } - this.positionChangedCallback = positionChangedCallback; - this.errorCallback = errorCallback; - - LocationAccuracy accuracy = LocationAccuracy.best; long timeInterval = 0; float distanceFilter = 0; @LocationRequestCompat.Quality int quality = LocationRequestCompat.QUALITY_BALANCED_POWER_ACCURACY; if (this.locationOptions != null) { distanceFilter = locationOptions.getDistanceFilter(); - accuracy = locationOptions.getAccuracy(); + LocationAccuracy accuracy = locationOptions.getAccuracy(); timeInterval = accuracy == LocationAccuracy.lowest ? LocationRequestCompat.PASSIVE_INTERVAL : locationOptions.getTimeInterval(); quality = accuracyToQuality(accuracy); } - this.currentLocationProvider = determineProvider(this.locationManager, accuracy); - - if (this.currentLocationProvider == null) { - errorCallback.onError(ErrorCodes.locationServicesDisabled); - return; - } - final LocationRequestCompat locationRequest = new LocationRequestCompat.Builder(timeInterval) .setMinUpdateDistanceMeters(distanceFilter) .setMinUpdateIntervalMillis(timeInterval) .setQuality(quality) .build(); - this.isListening = true; this.nmeaClient.start(); - LocationManagerCompat.requestLocationUpdates( this.locationManager, this.currentLocationProvider, locationRequest, this, Looper.getMainLooper()); + + this.isListening = true; + } + + @Override + public void updateLocationOptions(LocationOptions options) { + this.locationOptions = options; + if (isListening) { + requestPositionUpdates(); + } } @SuppressLint("MissingPermission") @@ -205,7 +227,7 @@ public void stopPositionUpdates() { } @Override - public synchronized void onLocationChanged(Location location) { + public synchronized void onLocationChanged(@NonNull Location location) { if (isBetterLocation(location, currentBestLocation)) { this.currentBestLocation = location; @@ -233,9 +255,6 @@ public void onStatusChanged(@NonNull String provider, int status, Bundle extras) } } - @Override - public void onProviderEnabled(@NonNull String provider) {} - @SuppressLint("MissingPermission") @Override public void onProviderDisabled(String provider) { diff --git a/geolocator_android/example/android/app/build.gradle b/geolocator_android/example/android/app/build.gradle index 7eeb09b5..49aa3480 100644 --- a/geolocator_android/example/android/app/build.gradle +++ b/geolocator_android/example/android/app/build.gradle @@ -1,5 +1,6 @@ plugins { id "com.android.application" + id "kotlin-android" id "dev.flutter.flutter-gradle-plugin" } @@ -43,7 +44,7 @@ android { defaultConfig { // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). applicationId "com.baseflow.geolocator_example" - minSdkVersion flutter.minSdkVersion + minSdkVersion 24 targetSdkVersion flutter.targetSdkVersion versionCode flutterVersionCode.toInteger() diff --git a/geolocator_android/example/android/app/src/main/AndroidManifest.xml b/geolocator_android/example/android/app/src/main/AndroidManifest.xml index 7870abc0..4221badd 100644 --- a/geolocator_android/example/android/app/src/main/AndroidManifest.xml +++ b/geolocator_android/example/android/app/src/main/AndroidManifest.xml @@ -10,6 +10,7 @@ + _GeolocatorWidgetState(); + GeolocatorWidgetState createState() => GeolocatorWidgetState(); } -class _GeolocatorWidgetState extends State { +/// State for the [GeolocatorWidget]. +class GeolocatorWidgetState extends State { static const String _kLocationServicesDisabledMessage = 'Location services are disabled.'; static const String _kPermissionDeniedMessage = 'Permission denied.'; @@ -38,8 +40,9 @@ class _GeolocatorWidgetState extends State { 'Permission denied forever.'; static const String _kPermissionGrantedMessage = 'Permission granted.'; - final GeolocatorPlatform geolocatorAndroid = GeolocatorPlatform.instance; + final GeolocatorPlatform _geolocatorAndroid = GeolocatorPlatform.instance; final List<_PositionItem> _positionItems = <_PositionItem>[]; + final ScrollController _scrollController = ScrollController(); StreamSubscription? _positionStreamSubscription; StreamSubscription? _serviceStatusStreamSubscription; @@ -75,26 +78,26 @@ class _GeolocatorWidgetState extends State { }, itemBuilder: (context) => [ const PopupMenuItem( - child: Text("Get Location Accuracy"), value: 1, + child: Text("Get Location Accuracy"), ), if (Platform.isIOS) const PopupMenuItem( - child: Text("Request Temporary Full Accuracy"), value: 2, + child: Text("Request Temporary Full Accuracy"), ), const PopupMenuItem( - child: Text("Open App Settings"), value: 3, + child: Text("Open App Settings"), ), if (Platform.isAndroid) const PopupMenuItem( - child: Text("Open Location Settings"), value: 4, + child: Text("Open Location Settings"), ), const PopupMenuItem( - child: Text("Clear"), value: 5, + child: Text("Clear"), ), ], ); @@ -119,6 +122,7 @@ class _GeolocatorWidgetState extends State { (context) => Scaffold( backgroundColor: Theme.of(context).colorScheme.surface, body: ListView.builder( + controller: _scrollController, itemCount: _positionItems.length, itemBuilder: (context, index) { final positionItem = _positionItems[index]; @@ -150,10 +154,6 @@ class _GeolocatorWidgetState extends State { mainAxisAlignment: MainAxisAlignment.end, children: [ FloatingActionButton( - child: (_positionStreamSubscription == null || - _positionStreamSubscription!.isPaused) - ? const Icon(Icons.play_arrow) - : const Icon(Icons.pause), onPressed: _toggleListening, tooltip: (_positionStreamSubscription == null) ? 'Start position updates' @@ -161,16 +161,31 @@ class _GeolocatorWidgetState extends State { ? 'Resume' : 'Pause', backgroundColor: _determineButtonColor(), + child: (_positionStreamSubscription == null || + _positionStreamSubscription!.isPaused) + ? const Icon(Icons.play_arrow) + : const Icon(Icons.pause), ), sizedBox, + if (_positionStreamSubscription != null) + Column( + mainAxisSize: MainAxisSize.min, + children: [ + FloatingActionButton( + onPressed: _speedUpTracking, + child: const Icon(Icons.speed), + ), + sizedBox, + ], + ), FloatingActionButton( - child: const Icon(Icons.my_location), onPressed: _getCurrentPosition, + child: const Icon(Icons.my_location), ), sizedBox, FloatingActionButton( - child: const Icon(Icons.bookmark), onPressed: _getLastKnownPosition, + child: const Icon(Icons.bookmark), ), ], ), @@ -186,7 +201,7 @@ class _GeolocatorWidgetState extends State { return; } - final position = await geolocatorAndroid.getCurrentPosition(); + final position = await _geolocatorAndroid.getCurrentPosition(); _updatePositionList( _PositionItemType.position, position.toString(), @@ -198,7 +213,7 @@ class _GeolocatorWidgetState extends State { LocationPermission permission; // Test if location services are enabled. - serviceEnabled = await geolocatorAndroid.isLocationServiceEnabled(); + serviceEnabled = await _geolocatorAndroid.isLocationServiceEnabled(); if (!serviceEnabled) { // Location services are not enabled don't continue // accessing the position and request users of the @@ -211,9 +226,9 @@ class _GeolocatorWidgetState extends State { return false; } - permission = await geolocatorAndroid.checkPermission(); + permission = await _geolocatorAndroid.checkPermission(); if (permission == LocationPermission.denied) { - permission = await geolocatorAndroid.requestPermission(); + permission = await _geolocatorAndroid.requestPermission(); if (permission == LocationPermission.denied) { // Permissions are denied, next time you could try // requesting permissions again (this is also where @@ -248,9 +263,12 @@ class _GeolocatorWidgetState extends State { return true; } - void _updatePositionList(_PositionItemType type, String displayValue) { + void _updatePositionList(_PositionItemType type, String displayValue) async { _positionItems.add(_PositionItem(type, displayValue)); setState(() {}); + await Future.delayed(const Duration(milliseconds: 100)); + if (!mounted) return; + _scrollController.jumpTo(_scrollController.position.maxScrollExtent); } bool _isListening() => !(_positionStreamSubscription == null || @@ -262,7 +280,7 @@ class _GeolocatorWidgetState extends State { void _toggleServiceStatusStream() { if (_serviceStatusStreamSubscription == null) { - final serviceStatusStream = geolocatorAndroid.getServiceStatusStream(); + final serviceStatusStream = _geolocatorAndroid.getServiceStatusStream(); _serviceStatusStreamSubscription = serviceStatusStream.handleError((error) { _serviceStatusStreamSubscription?.cancel(); @@ -292,8 +310,8 @@ class _GeolocatorWidgetState extends State { if (_positionStreamSubscription == null) { final androidSettings = AndroidSettings( accuracy: LocationAccuracy.best, - distanceFilter: 10, - intervalDuration: const Duration(seconds: 1), + distanceFilter: 0, + intervalDuration: const Duration(seconds: 10), forceLocationManager: false, useMSLAltitude: true, foregroundNotificationConfig: const ForegroundNotificationConfig( @@ -308,7 +326,7 @@ class _GeolocatorWidgetState extends State { color: Colors.amber, ), ); - final positionStream = geolocatorAndroid.getPositionStream( + final positionStream = _geolocatorAndroid.getPositionStream( locationSettings: androidSettings); _positionStreamSubscription = positionStream.handleError((error) { _positionStreamSubscription?.cancel(); @@ -344,6 +362,21 @@ class _GeolocatorWidgetState extends State { }); } + Future _speedUpTracking() async { + await _geolocatorAndroid.updatePositionStream( + locationSettings: AndroidSettings( + accuracy: LocationAccuracy.best, + distanceFilter: 0, + intervalDuration: const Duration(seconds: 1), + forceLocationManager: false, + useMSLAltitude: true, + )); + _updatePositionList( + _PositionItemType.log, + 'Position updates interval set to 1 second.', + ); + } + @override void dispose() { if (_positionStreamSubscription != null) { @@ -355,7 +388,7 @@ class _GeolocatorWidgetState extends State { } void _getLastKnownPosition() async { - final position = await geolocatorAndroid.getLastKnownPosition(); + final position = await _geolocatorAndroid.getLastKnownPosition(); if (position != null) { _updatePositionList( _PositionItemType.position, @@ -370,12 +403,12 @@ class _GeolocatorWidgetState extends State { } void _getLocationAccuracy() async { - final status = await geolocatorAndroid.getLocationAccuracy(); + final status = await _geolocatorAndroid.getLocationAccuracy(); _handleLocationAccuracyStatus(status); } void _requestTemporaryFullAccuracy() async { - final status = await geolocatorAndroid.requestTemporaryFullAccuracy( + final status = await _geolocatorAndroid.requestTemporaryFullAccuracy( purposeKey: "TemporaryPreciseAccuracy", ); _handleLocationAccuracyStatus(status); @@ -397,7 +430,7 @@ class _GeolocatorWidgetState extends State { } void _openAppSettings() async { - final opened = await geolocatorAndroid.openAppSettings(); + final opened = await _geolocatorAndroid.openAppSettings(); String displayValue; if (opened) { @@ -413,7 +446,7 @@ class _GeolocatorWidgetState extends State { } void _openLocationSettings() async { - final opened = await geolocatorAndroid.openLocationSettings(); + final opened = await _geolocatorAndroid.openLocationSettings(); String displayValue; if (opened) { diff --git a/geolocator_android/example/pubspec.yaml b/geolocator_android/example/pubspec.yaml index 22febac1..2410da1e 100644 --- a/geolocator_android/example/pubspec.yaml +++ b/geolocator_android/example/pubspec.yaml @@ -9,7 +9,7 @@ environment: sdk: ">=2.15.0 <3.0.0" dependencies: - baseflow_plugin_template: ^2.1.2 + baseflow_plugin_template: ^2.2.0 flutter: sdk: flutter @@ -21,18 +21,21 @@ dependencies: # the parent directory to use the current plugin's version. path: ../ + geolocator_platform_interface: + path: ../../geolocator_platform_interface/ + # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. - cupertino_icons: ^1.0.1+1 + cupertino_icons: ^1.0.8 # The following adds the URL Launcher plugin which is used by # the demo application to open the links in the web browser. - url_launcher: ^6.0.0-nullsafety.1 + url_launcher: ^6.3.1 dev_dependencies: flutter_test: sdk: flutter - flutter_lints: ^1.0.4 + flutter_lints: ^5.0.0 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec diff --git a/geolocator_android/lib/src/geolocator_android.dart b/geolocator_android/lib/src/geolocator_android.dart index 04c1ced8..081a8419 100644 --- a/geolocator_android/lib/src/geolocator_android.dart +++ b/geolocator_android/lib/src/geolocator_android.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'package:flutter/services.dart'; + import 'package:geolocator_android/geolocator_android.dart'; import 'package:geolocator_platform_interface/geolocator_platform_interface.dart'; import 'package:uuid/uuid.dart'; @@ -211,6 +212,22 @@ class GeolocatorAndroid extends GeolocatorPlatform { }); } + @override + Future updatePositionStream( + {required LocationSettings locationSettings}) async { + if (_positionStream == null) { + throw const NotSubscribedException(); + } + try { + await _methodChannel.invokeMethod( + 'updatePositionStream', locationSettings.toJson()); + } on PlatformException catch (e) { + final error = _handlePlatformException(e); + + throw error; + } + } + @override Future requestTemporaryFullAccuracy({ required String purposeKey, @@ -247,6 +264,8 @@ class GeolocatorAndroid extends GeolocatorPlatform { return const LocationServiceDisabledException(); case 'LOCATION_SUBSCRIPTION_ACTIVE': return const AlreadySubscribedException(); + case 'LOCATION_SUBSCRIPTION_INACTIVE': + return const NotSubscribedException(); case 'PERMISSION_DEFINITIONS_NOT_FOUND': return PermissionDefinitionsNotFoundException(exception.message); case 'PERMISSION_DENIED': diff --git a/geolocator_android/pubspec.yaml b/geolocator_android/pubspec.yaml index 8841c116..319e7f6d 100644 --- a/geolocator_android/pubspec.yaml +++ b/geolocator_android/pubspec.yaml @@ -2,7 +2,7 @@ name: geolocator_android description: Geolocation plugin for Flutter. This plugin provides the Android implementation for the geolocator. repository: https://github.com/baseflow/flutter-geolocator/tree/main/geolocator_android issue_tracker: https://github.com/baseflow/flutter-geolocator/issues?q=is%3Aissue+is%3Aopen -version: 4.6.1 +version: 4.7.0 environment: sdk: ">=2.15.0 <4.0.0" @@ -20,14 +20,15 @@ flutter: dependencies: flutter: sdk: flutter - geolocator_platform_interface: ^4.1.0 - meta: ^1.10.0 - uuid: ">=4.0.0 <6.0.0" + geolocator_platform_interface: + path: ../geolocator_platform_interface + meta: ^1.15.0 + uuid: ">=4.5.1" dev_dependencies: - async: ^2.8.2 + async: ^2.11.0 flutter_test: sdk: flutter - flutter_lints: ">=3.0.0 <5.0.0" - mockito: ^5.2.0 - plugin_platform_interface: ^2.1.2 + flutter_lints: ">=5.0.0" + mockito: ^5.4.4 + plugin_platform_interface: ^2.1.8 diff --git a/geolocator_apple/example/ios/Podfile b/geolocator_apple/example/ios/Podfile index c654fd27..2bd06077 100644 --- a/geolocator_apple/example/ios/Podfile +++ b/geolocator_apple/example/ios/Podfile @@ -1,5 +1,5 @@ # Uncomment this line to define a global platform for your project -# platform :ios, '12.0' +platform :ios, '12.0' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' diff --git a/geolocator_apple/example/ios/Runner.xcodeproj/project.pbxproj b/geolocator_apple/example/ios/Runner.xcodeproj/project.pbxproj index 33880a1c..13585198 100644 --- a/geolocator_apple/example/ios/Runner.xcodeproj/project.pbxproj +++ b/geolocator_apple/example/ios/Runner.xcodeproj/project.pbxproj @@ -192,7 +192,7 @@ 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, - 591FD69E94FC610DB997784E /* [CP] Copy Pods Resources */, + 7987087EF4670CEDBC08B555 /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -358,6 +358,23 @@ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; + 7987087EF4670CEDBC08B555 /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Copy Pods Resources"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; diff --git a/geolocator_apple/example/lib/main.dart b/geolocator_apple/example/lib/main.dart index 442e0b2c..5777666b 100644 --- a/geolocator_apple/example/lib/main.dart +++ b/geolocator_apple/example/lib/main.dart @@ -1,8 +1,9 @@ import 'dart:async'; import 'dart:io' show Platform; -import 'package:baseflow_plugin_template/baseflow_plugin_template.dart'; import 'package:flutter/material.dart'; + +import 'package:baseflow_plugin_template/baseflow_plugin_template.dart'; import 'package:geolocator_apple/geolocator_apple.dart'; import 'package:geolocator_platform_interface/geolocator_platform_interface.dart'; @@ -40,6 +41,7 @@ class _GeolocatorWidgetState extends State { final GeolocatorPlatform geolocatorApple = GeolocatorPlatform.instance; final List<_PositionItem> _positionItems = <_PositionItem>[]; + final ScrollController _scrollController = ScrollController(); StreamSubscription? _positionStreamSubscription; StreamSubscription? _serviceStatusStreamSubscription; @@ -121,6 +123,7 @@ class _GeolocatorWidgetState extends State { backgroundColor: Theme.of(context).colorScheme.surface, body: ListView.builder( itemCount: _positionItems.length, + controller: _scrollController, itemBuilder: (context, index) { final positionItem = _positionItems[index]; @@ -164,6 +167,17 @@ class _GeolocatorWidgetState extends State { : const Icon(Icons.pause), ), sizedBox, + if (_positionStreamSubscription != null) + Column( + mainAxisSize: MainAxisSize.min, + children: [ + FloatingActionButton( + onPressed: _speedUpTracking, + child: const Icon(Icons.speed), + ), + sizedBox, + ], + ), FloatingActionButton( onPressed: _getCurrentPosition, child: const Icon(Icons.my_location), @@ -254,9 +268,12 @@ class _GeolocatorWidgetState extends State { return true; } - void _updatePositionList(_PositionItemType type, String displayValue) { + void _updatePositionList(_PositionItemType type, String displayValue) async { _positionItems.add(_PositionItem(type, displayValue)); setState(() {}); + await Future.delayed(const Duration(milliseconds: 100)); + if (!mounted) return; + _scrollController.jumpTo(_scrollController.position.maxScrollExtent); } bool _isListening() => !(_positionStreamSubscription == null || @@ -301,7 +318,7 @@ class _GeolocatorWidgetState extends State { final Stream positionStream = geolocatorApple.getPositionStream( locationSettings: AppleSettings( accuracy: LocationAccuracy.best, - distanceFilter: 10, + distanceFilter: 100, activityType: ActivityType.other, // Only set showBackgroundLocationIndicator and // allowBackgroundLocationUpdates to true if our app will be started up @@ -346,6 +363,21 @@ class _GeolocatorWidgetState extends State { }); } + Future _speedUpTracking() async { + await geolocatorApple.updatePositionStream( + locationSettings: AppleSettings( + accuracy: LocationAccuracy.best, + distanceFilter: 0, + activityType: ActivityType.fitness, + showBackgroundLocationIndicator: false, + allowBackgroundLocationUpdates: false, + )); + _updatePositionList( + _PositionItemType.log, + 'Position updates distance filter has been set to zero.', + ); + } + @override void dispose() { if (_positionStreamSubscription != null) { diff --git a/geolocator_apple/example/pubspec.yaml b/geolocator_apple/example/pubspec.yaml index bdaed10f..db58c668 100644 --- a/geolocator_apple/example/pubspec.yaml +++ b/geolocator_apple/example/pubspec.yaml @@ -23,7 +23,8 @@ dependencies: # the parent directory to use the current plugin's version. path: ../ - geolocator_platform_interface: ^4.2.0 + geolocator_platform_interface: + path: ../../geolocator_platform_interface flutter: sdk: flutter diff --git a/geolocator_apple/ios/Classes/Constants/ErrorCodes.h b/geolocator_apple/ios/Classes/Constants/ErrorCodes.h index fa87ca14..b852b86e 100644 --- a/geolocator_apple/ios/Classes/Constants/ErrorCodes.h +++ b/geolocator_apple/ios/Classes/Constants/ErrorCodes.h @@ -8,6 +8,7 @@ FOUNDATION_EXPORT NSString * const GeolocatorErrorLocationUpdateFailure; FOUNDATION_EXPORT NSString * const GeolocatorErrorLocationServicesDisabled; FOUNDATION_EXPORT NSString * const GeolocatorErrorLocationSubscriptionActive; +FOUNDATION_EXPORT NSString * const GeolocatorErrorLocationSubscriptionInactive; FOUNDATION_EXPORT NSString * const GeolocatorErrorPermissionDefinitionsNotFound; FOUNDATION_EXPORT NSString * const GeolocatorErrorPermissionDenied; FOUNDATION_EXPORT NSString * const GeolocatorErrorPermissionRequestInProgress; diff --git a/geolocator_apple/ios/Classes/Constants/ErrorCodes.m b/geolocator_apple/ios/Classes/Constants/ErrorCodes.m index b1ffe7a0..9e9e970d 100644 --- a/geolocator_apple/ios/Classes/Constants/ErrorCodes.m +++ b/geolocator_apple/ios/Classes/Constants/ErrorCodes.m @@ -10,6 +10,7 @@ NSString * const GeolocatorErrorLocationUpdateFailure = @"LOCATION_UPDATE_FAILURE"; NSString * const GeolocatorErrorLocationServicesDisabled = @"LOCATION_SERVICES_DISABLED"; NSString * const GeolocatorErrorLocationSubscriptionActive = @"LOCATION_SUBSCRIPTION_ACTIVE"; +NSString * const GeolocatorErrorLocationSubscriptionInactive = @"LOCATION_SUBSCRIPTION_INACTIVE"; NSString * const GeolocatorErrorPermissionDefinitionsNotFound = @"PERMISSION_DEFINITIONS_NOT_FOUND"; NSString * const GeolocatorErrorPermissionDenied = @"PERMISSION_DENIED"; NSString * const GeolocatorErrorPermissionRequestInProgress = @"PERMISSION_REQUEST_IN_PROGRESS"; diff --git a/geolocator_apple/ios/Classes/GeolocatorPlugin.m b/geolocator_apple/ios/Classes/GeolocatorPlugin.m index 0e3687a9..b8a304f0 100644 --- a/geolocator_apple/ios/Classes/GeolocatorPlugin.m +++ b/geolocator_apple/ios/Classes/GeolocatorPlugin.m @@ -98,6 +98,8 @@ - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { NSString* purposeKey = (NSString *)call.arguments[@"purposeKey"]; [[self createLocationAccuracyHandler] requestTemporaryFullAccuracyWithResult:result purposeKey:purposeKey]; + } else if ([@"updatePositionStream" isEqualToString:call.method]) { + [self onUpdatePositionStreamWithArguments:call.arguments result:result]; } else if ([@"openAppSettings" isEqualToString:call.method]) { [self openSettings:result]; } else if ([@"openLocationSettings" isEqualToString:call.method]) { @@ -168,6 +170,27 @@ - (void)onGetCurrentPositionWithArguments:(id _Nullable)arguments }]; } +- (void)onUpdatePositionStreamWithArguments:(id _Nullable)arguments + result:(FlutterResult)result { + if (![self.createGeolocationHandler listeningForPositionUpdates]) { + result([FlutterError errorWithCode: GeolocatorErrorLocationSubscriptionInactive + message:@"There is no active location stream to update." + details:nil]); + return; + } + + CLLocationAccuracy accuracy = [LocationAccuracyMapper toCLLocationAccuracy:(NSNumber *)arguments[@"accuracy"]]; + CLLocationDistance distanceFilter = [LocationDistanceMapper toCLLocationDistance:(NSNumber *)arguments[@"distanceFilter"]]; + NSNumber* pauseLocationUpdatesAutomatically = arguments[@"pauseLocationUpdatesAutomatically"]; + CLActivityType activityType = [ActivityTypeMapper toCLActivityType:(NSNumber *)arguments[@"activityType"]]; + NSNumber* allowBackgroundLocationUpdates = arguments[@"allowBackgroundLocationUpdates"]; + NSNumber* showBackgroundLocationIndicator = arguments[@"showBackgroundLocationIndicator"]; + + [self.createGeolocationHandler updateListenerWithDesiredAccuracy:accuracy distanceFilter:distanceFilter pauseLocationUpdatesAutomatically:pauseLocationUpdatesAutomatically && [pauseLocationUpdatesAutomatically boolValue] showBackgroundLocationIndicator:showBackgroundLocationIndicator && [showBackgroundLocationIndicator boolValue] activityType:activityType allowBackgroundLocationUpdates:[allowBackgroundLocationUpdates boolValue]]; + + result(nil); +} + - (void)openSettings:(FlutterResult)result { #if TARGET_OS_OSX diff --git a/geolocator_apple/ios/Classes/Handlers/GeolocationHandler.h b/geolocator_apple/ios/Classes/Handlers/GeolocationHandler.h index 43801fa5..0b91c64d 100644 --- a/geolocator_apple/ios/Classes/Handlers/GeolocationHandler.h +++ b/geolocator_apple/ios/Classes/Handlers/GeolocationHandler.h @@ -32,6 +32,16 @@ typedef void (^GeolocatorResult)(CLLocation *_Nullable location); errorHandler:(GeolocatorError _Nonnull)errorHandler; - (void)stopListening; + +- (BOOL)listeningForPositionUpdates; + +- (void)updateListenerWithDesiredAccuracy:(CLLocationAccuracy)desiredAccuracy + distanceFilter:(CLLocationDistance)distanceFilter + pauseLocationUpdatesAutomatically:(BOOL)pauseLocationUpdatesAutomatically + showBackgroundLocationIndicator:(BOOL)showBackgroundLocationIndicator + activityType:(CLActivityType)activityType + allowBackgroundLocationUpdates:(BOOL)allowBackgroundLocationUpdates; + + (BOOL) shouldEnableBackgroundLocationUpdates; @end diff --git a/geolocator_apple/ios/Classes/Handlers/GeolocationHandler.m b/geolocator_apple/ios/Classes/Handlers/GeolocationHandler.m index e3808f18..de6e4015 100644 --- a/geolocator_apple/ios/Classes/Handlers/GeolocationHandler.m +++ b/geolocator_apple/ios/Classes/Handlers/GeolocationHandler.m @@ -13,6 +13,8 @@ @interface GeolocationHandler() +@property(assign, nonatomic) bool isListeningForPositionUpdates; + @property(strong, nonatomic, nonnull) CLLocationManager *locationManager; @property(strong, nonatomic) GeolocatorError errorHandler; @@ -32,6 +34,8 @@ - (id) init { if (!self) { return nil; } + + self.isListeningForPositionUpdates = NO; return self; } @@ -107,6 +111,8 @@ - (void)startListeningWithDesiredAccuracy:(CLLocationAccuracy)desiredAccuracy isListeningForPositionUpdates:YES showBackgroundLocationIndicator:showBackgroundLocationIndicator allowBackgroundLocationUpdates:allowBackgroundLocationUpdates]; + + self.isListeningForPositionUpdates = YES; } - (void)startUpdatingLocationWithDesiredAccuracy:(CLLocationAccuracy)desiredAccuracy @@ -148,11 +154,42 @@ - (void)stopOneTimeLocationListening { } - (void)stopListening { + self.isListeningForPositionUpdates = NO; [[self getLocationManager] stopUpdatingLocation]; self.errorHandler = nil; self.listenerResultHandler = nil; } +- (BOOL)listeningForPositionUpdates { + return self.isListeningForPositionUpdates; +} + +- (void)updateListenerWithDesiredAccuracy:(CLLocationAccuracy)desiredAccuracy + distanceFilter:(CLLocationDistance)distanceFilter + pauseLocationUpdatesAutomatically:(BOOL)pauseLocationUpdatesAutomatically + showBackgroundLocationIndicator:(BOOL)showBackgroundLocationIndicator + activityType:(CLActivityType)activityType + allowBackgroundLocationUpdates:(BOOL)allowBackgroundLocationUpdates +{ + CLLocationManager *locationManager = [self getLocationManager]; + locationManager.desiredAccuracy = desiredAccuracy; + locationManager.distanceFilter = distanceFilter; + if (@available(iOS 6.0, macOS 10.15, *)) { + locationManager.activityType = activityType; + locationManager.pausesLocationUpdatesAutomatically = pauseLocationUpdatesAutomatically; + } + +#if TARGET_OS_IOS + if (@available(iOS 9.0, macOS 11.0, *)) { + locationManager.allowsBackgroundLocationUpdates = allowBackgroundLocationUpdates + && [GeolocationHandler shouldEnableBackgroundLocationUpdates]; + } + if (@available(iOS 11.0, macOS 11.0, *)) { + locationManager.showsBackgroundLocationIndicator = showBackgroundLocationIndicator; + } +#endif +} + - (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations { if (!self.listenerResultHandler && !self.currentLocationResultHandler) return; diff --git a/geolocator_apple/lib/src/geolocator_apple.dart b/geolocator_apple/lib/src/geolocator_apple.dart index 8475bdd2..13920432 100644 --- a/geolocator_apple/lib/src/geolocator_apple.dart +++ b/geolocator_apple/lib/src/geolocator_apple.dart @@ -198,6 +198,22 @@ class GeolocatorApple extends GeolocatorPlatform { }); } + @override + Future updatePositionStream( + {required LocationSettings locationSettings}) async { + if (_positionStream == null) { + throw const NotSubscribedException(); + } + try { + await _methodChannel.invokeMethod( + 'updatePositionStream', locationSettings.toJson()); + } on PlatformException catch (e) { + final error = _handlePlatformException(e); + + throw error; + } + } + @override Future requestTemporaryFullAccuracy({ required String purposeKey, @@ -234,6 +250,8 @@ class GeolocatorApple extends GeolocatorPlatform { return const LocationServiceDisabledException(); case 'LOCATION_SUBSCRIPTION_ACTIVE': return const AlreadySubscribedException(); + case 'LOCATION_SUBSCRIPTION_INACTIVE': + return const NotSubscribedException(); case 'PERMISSION_DEFINITIONS_NOT_FOUND': return PermissionDefinitionsNotFoundException(exception.message); case 'PERMISSION_DENIED': diff --git a/geolocator_apple/pubspec.yaml b/geolocator_apple/pubspec.yaml index 270b13a3..185ba701 100644 --- a/geolocator_apple/pubspec.yaml +++ b/geolocator_apple/pubspec.yaml @@ -2,7 +2,7 @@ name: geolocator_apple description: Geolocation Apple plugin for Flutter. This plugin provides the Apple implementation for the geolocator. repository: https://github.com/baseflow/flutter-geolocator/tree/main/geolocator_apple issue_tracker: https://github.com/baseflow/flutter-geolocator/issues?q=is%3Aissue+is%3Aopen -version: 2.3.8+1 +version: 2.4.0 environment: sdk: ">=2.15.0 <4.0.0" @@ -22,12 +22,13 @@ flutter: dependencies: flutter: sdk: flutter - geolocator_platform_interface: ^4.1.0 + geolocator_platform_interface: + path: ../geolocator_platform_interface dev_dependencies: - async: ^2.8.2 + async: ^2.11.0 flutter_test: sdk: flutter - flutter_lints: ">=3.0.1 <5.0.0" - mockito: ^5.2.0 - plugin_platform_interface: ^2.1.2 + flutter_lints: ">=5.0.0" + mockito: ^5.4.4 + plugin_platform_interface: ^2.1.8 diff --git a/geolocator_linux/example/pubspec.yaml b/geolocator_linux/example/pubspec.yaml index bb0d1c19..52c4ae40 100644 --- a/geolocator_linux/example/pubspec.yaml +++ b/geolocator_linux/example/pubspec.yaml @@ -28,7 +28,8 @@ dependencies: # The following adds the URL Launcher plugin which is used by # the demo application to open the links in the web browser. url_launcher: ^6.0.18 - geolocator_platform_interface: ^4.2.0 + geolocator_platform_interface: + path: ../../geolocator_platform_interface dev_dependencies: flutter_test: diff --git a/geolocator_linux/pubspec.yaml b/geolocator_linux/pubspec.yaml index 897f9026..91e05dfb 100644 --- a/geolocator_linux/pubspec.yaml +++ b/geolocator_linux/pubspec.yaml @@ -21,7 +21,8 @@ dependencies: flutter: sdk: flutter geoclue: ^0.1.1 - geolocator_platform_interface: ^4.1.0 + geolocator_platform_interface: + path: ../geolocator_platform_interface gsettings: ^0.2.5 package_info_plus: "^8.0.0" diff --git a/geolocator_platform_interface/CHANGELOG.md b/geolocator_platform_interface/CHANGELOG.md index 245e8e0a..f9490c95 100644 --- a/geolocator_platform_interface/CHANGELOG.md +++ b/geolocator_platform_interface/CHANGELOG.md @@ -1,3 +1,7 @@ +## 4.3.0 + +- Adds `updatePositionStream` API. + ## 4.2.4 - Correctly handle integer-like numbers when decoding `Position` from JSON. diff --git a/geolocator_platform_interface/lib/src/errors/errors.dart b/geolocator_platform_interface/lib/src/errors/errors.dart index 3bbfcfe5..c0468828 100644 --- a/geolocator_platform_interface/lib/src/errors/errors.dart +++ b/geolocator_platform_interface/lib/src/errors/errors.dart @@ -2,6 +2,7 @@ export 'activity_missing_exception.dart'; export 'already_subscribed_exception.dart'; export 'invalid_permission_exception.dart'; export 'location_service_disabled_exception.dart'; +export 'not_subscribed_exception.dart'; export 'permission_definitions_not_found_exception.dart'; export 'permission_denied_exception.dart'; export 'permission_request_in_progress_exception.dart'; diff --git a/geolocator_platform_interface/lib/src/errors/not_subscribed_exception.dart b/geolocator_platform_interface/lib/src/errors/not_subscribed_exception.dart new file mode 100644 index 00000000..5c593673 --- /dev/null +++ b/geolocator_platform_interface/lib/src/errors/not_subscribed_exception.dart @@ -0,0 +1,12 @@ +/// An exception thrown when trying to update location stream settings without +/// having an active subscription to the location stream. +class NotSubscribedException implements Exception { + /// Constructs the [NotSubscribedException] + const NotSubscribedException(); + + @override + String toString() => + 'The App is not listening to a stream of position updates and thus it is' + 'not possible to update location stream settings. Call ' + 'getPositionStream() first.'; +} diff --git a/geolocator_platform_interface/lib/src/geolocator_platform_interface.dart b/geolocator_platform_interface/lib/src/geolocator_platform_interface.dart index e16e8ff0..8c557de5 100644 --- a/geolocator_platform_interface/lib/src/geolocator_platform_interface.dart +++ b/geolocator_platform_interface/lib/src/geolocator_platform_interface.dart @@ -172,6 +172,17 @@ abstract class GeolocatorPlatform extends PlatformInterface { throw UnimplementedError('getPositionStream() has not been implemented.'); } + /// Updates the parameters of the active position stream. + /// + /// Throws a [NotSubscribedException] when there is no active position stream + /// to update. + Future updatePositionStream({ + required LocationSettings locationSettings, + }) { + throw UnimplementedError( + 'updatePositionStream() has not been implemented.'); + } + /// Asks the user for Temporary Precise location access (iOS 14 or above). /// /// Returns [LocationAccuracyStatus.precise] if the user already gave diff --git a/geolocator_platform_interface/lib/src/implementations/method_channel_geolocator.dart b/geolocator_platform_interface/lib/src/implementations/method_channel_geolocator.dart index fefc185e..346f8b28 100644 --- a/geolocator_platform_interface/lib/src/implementations/method_channel_geolocator.dart +++ b/geolocator_platform_interface/lib/src/implementations/method_channel_geolocator.dart @@ -6,8 +6,8 @@ import '../enums/enums.dart'; import '../errors/errors.dart'; import '../extensions/extensions.dart'; import '../geolocator_platform_interface.dart'; -import '../models/position.dart'; import '../models/location_settings.dart'; +import '../models/position.dart'; /// An implementation of [GeolocatorPlatform] that uses method channels. class MethodChannelGeolocator extends GeolocatorPlatform { @@ -191,6 +191,22 @@ class MethodChannelGeolocator extends GeolocatorPlatform { return _positionStream!; } + @override + Future updatePositionStream( + {required LocationSettings locationSettings}) async { + if (_positionStream == null) { + throw const NotSubscribedException(); + } + try { + await _methodChannel.invokeMethod( + 'updatePositionStream', locationSettings.toJson()); + } on PlatformException catch (e) { + final error = _handlePlatformException(e); + + throw error; + } + } + Stream _wrapStream(Stream incoming) { return incoming.asBroadcastStream(onCancel: (subscription) { subscription.cancel(); @@ -234,6 +250,8 @@ class MethodChannelGeolocator extends GeolocatorPlatform { return const LocationServiceDisabledException(); case 'LOCATION_SUBSCRIPTION_ACTIVE': return const AlreadySubscribedException(); + case 'LOCATION_SUBSCRIPTION_INACTIVE': + return const NotSubscribedException(); case 'PERMISSION_DEFINITIONS_NOT_FOUND': return PermissionDefinitionsNotFoundException(exception.message); case 'PERMISSION_DENIED': diff --git a/geolocator_platform_interface/pubspec.yaml b/geolocator_platform_interface/pubspec.yaml index 736507cc..31a16428 100644 --- a/geolocator_platform_interface/pubspec.yaml +++ b/geolocator_platform_interface/pubspec.yaml @@ -3,22 +3,22 @@ description: A common platform interface for the geolocator plugin. repository: https://github.com/baseflow/flutter-geolocator/tree/main/geolocator_platform_interface # NOTE: We strongly prefer non-breaking changes, even at the expense of a # less-clean API. See https://flutter.dev/go/platform-interface-breaking-changes -version: 4.2.4 +version: 4.3.0 dependencies: flutter: sdk: flutter - plugin_platform_interface: ^2.1.6 + plugin_platform_interface: ^2.1.8 vector_math: ^2.1.4 - meta: ^1.9.1 + meta: ^1.15.0 dev_dependencies: async: ^2.11.0 flutter_test: sdk: flutter - flutter_lints: ">=3.0.1 <5.0.0" - mockito: ^5.4.2 + flutter_lints: ">=5.0.0" + mockito: ^5.4.4 environment: sdk: ">=2.15.0 <4.0.0" diff --git a/geolocator_web/pubspec.yaml b/geolocator_web/pubspec.yaml index c3b8dde2..c10039b8 100644 --- a/geolocator_web/pubspec.yaml +++ b/geolocator_web/pubspec.yaml @@ -17,7 +17,8 @@ dependencies: sdk: flutter flutter_web_plugins: sdk: flutter - geolocator_platform_interface: ^4.2.3 + geolocator_platform_interface: + path: ../geolocator_platform_interface web: ^1.0.0 dev_dependencies: diff --git a/geolocator_windows/example/pubspec.yaml b/geolocator_windows/example/pubspec.yaml index 90fa2905..b506f069 100644 --- a/geolocator_windows/example/pubspec.yaml +++ b/geolocator_windows/example/pubspec.yaml @@ -13,7 +13,8 @@ dependencies: flutter: sdk: flutter - geolocator_platform_interface: ^4.0.7 + geolocator_platform_interface: + path: ../../geolocator_platform_interface geolocator_windows: # When depending on this package from a real application you should use: # geolocator_windows: ^x.y.z diff --git a/geolocator_windows/pubspec.yaml b/geolocator_windows/pubspec.yaml index 5ae18655..8e448c94 100644 --- a/geolocator_windows/pubspec.yaml +++ b/geolocator_windows/pubspec.yaml @@ -18,7 +18,8 @@ flutter: dependencies: flutter: sdk: flutter - geolocator_platform_interface: ^4.1.0 + geolocator_platform_interface: + path: ../geolocator_platform_interface dev_dependencies: flutter_test: