Line data Source code
1 : import 'dart:async'; 2 : 3 : import 'package:flutter/services.dart'; 4 : 5 : import '../../geolocator_platform_interface.dart'; 6 : import '../enums/enums.dart'; 7 : import '../errors/errors.dart'; 8 : import '../extensions/extensions.dart'; 9 : import '../geolocator_platform_interface.dart'; 10 : import '../models/location_options.dart'; 11 : import '../models/position.dart'; 12 : 13 : /// An implementation of [GeolocatorPlatform] that uses method channels. 14 : class MethodChannelGeolocator extends GeolocatorPlatform { 15 : /// The method channel used to interact with the native platform. 16 : static const _methodChannel = 17 : MethodChannel('flutter.baseflow.com/geolocator'); 18 : 19 : /// The event channel used to receive [Position] updates from the native 20 : /// platform. 21 : static const _eventChannel = 22 : EventChannel('flutter.baseflow.com/geolocator_updates'); 23 : 24 : /// The event channel used to receive [LocationServiceStatus] updates from the 25 : /// native platform. 26 : static const _serviceStatusEventChannel = 27 : EventChannel('flutter.baseflow.com/geolocator_service_updates'); 28 : 29 : /// On Android devices you can set [forceAndroidLocationManager] 30 : /// to true to force the plugin to use the [LocationManager] to determine the 31 : /// position instead of the [FusedLocationProviderClient]. On iOS this is 32 : /// ignored. 33 : bool forceAndroidLocationManager = false; 34 : 35 : Stream<Position>? _positionStream; 36 : Stream<ServiceStatus>? _serviceStatusStream; 37 : 38 : @override 39 1 : Future<LocationPermission> checkPermission() async { 40 : try { 41 : // ignore: omit_local_variable_types 42 : final int permission = 43 2 : await _methodChannel.invokeMethod('checkPermission'); 44 : 45 1 : return permission.toLocationPermission(); 46 1 : } on PlatformException catch (e) { 47 1 : _handlePlatformException(e); 48 : 49 : rethrow; 50 : } 51 : } 52 : 53 : @override 54 1 : Future<LocationPermission> requestPermission() async { 55 : try { 56 : // ignore: omit_local_variable_types 57 : final int permission = 58 2 : await _methodChannel.invokeMethod('requestPermission'); 59 : 60 1 : return permission.toLocationPermission(); 61 1 : } on PlatformException catch (e) { 62 1 : _handlePlatformException(e); 63 : 64 : rethrow; 65 : } 66 : } 67 : 68 : @override 69 1 : Future<bool> isLocationServiceEnabled() async => _methodChannel 70 1 : .invokeMethod<bool>('isLocationServiceEnabled') 71 2 : .then((value) => value ?? false); 72 : 73 : @override 74 1 : Future<Position?> getLastKnownPosition({ 75 : bool forceAndroidLocationManager = false, 76 : }) async { 77 : try { 78 1 : final parameters = <String, dynamic>{ 79 : 'forceAndroidLocationManager': forceAndroidLocationManager, 80 : }; 81 : 82 : final positionMap = 83 2 : await _methodChannel.invokeMethod('getLastKnownPosition', parameters); 84 : 85 1 : return positionMap != null ? Position.fromMap(positionMap) : null; 86 1 : } on PlatformException catch (e) { 87 1 : _handlePlatformException(e); 88 : 89 : rethrow; 90 : } 91 : } 92 : 93 : @override 94 1 : Future<Position> getCurrentPosition({ 95 : LocationAccuracy desiredAccuracy = LocationAccuracy.best, 96 : bool forceAndroidLocationManager = false, 97 : Duration? timeLimit, 98 : }) async { 99 1 : final locationOptions = LocationOptions( 100 : accuracy: desiredAccuracy, 101 : forceAndroidLocationManager: forceAndroidLocationManager, 102 : ); 103 : 104 : try { 105 : Future<dynamic> positionFuture; 106 : 107 : if (timeLimit != null) { 108 : positionFuture = _methodChannel 109 1 : .invokeMethod( 110 : 'getCurrentPosition', 111 1 : locationOptions.toJson(), 112 : ) 113 1 : .timeout(timeLimit); 114 : } else { 115 1 : positionFuture = _methodChannel.invokeMethod( 116 : 'getCurrentPosition', 117 1 : locationOptions.toJson(), 118 : ); 119 : } 120 : 121 1 : final positionMap = await positionFuture; 122 1 : return Position.fromMap(positionMap); 123 1 : } on PlatformException catch (e) { 124 1 : _handlePlatformException(e); 125 : 126 : rethrow; 127 : } 128 : } 129 : 130 1 : @override 131 : Stream<ServiceStatus> getServiceStatusStream() { 132 1 : if (_serviceStatusStream != null) { 133 1 : return _serviceStatusStream!; 134 : } 135 : var serviceStatusStream = 136 1 : _serviceStatusEventChannel.receiveBroadcastStream(); 137 : 138 1 : _serviceStatusStream = serviceStatusStream 139 3 : .map((dynamic element) => ServiceStatus.values[element as int]) 140 2 : .handleError((error) { 141 1 : _serviceStatusStream = null; 142 1 : if (error is PlatformException) { 143 1 : _handlePlatformException(error); 144 : } 145 : throw error; 146 : }); 147 : 148 1 : return _serviceStatusStream!; 149 : } 150 : 151 1 : @override 152 : Stream<Position> getPositionStream({ 153 : LocationAccuracy desiredAccuracy = LocationAccuracy.best, 154 : int distanceFilter = 0, 155 : bool forceAndroidLocationManager = false, 156 : int timeInterval = 0, 157 : Duration? timeLimit, 158 : }) { 159 1 : final locationOptions = LocationOptions( 160 : accuracy: desiredAccuracy, 161 : distanceFilter: distanceFilter, 162 : forceAndroidLocationManager: forceAndroidLocationManager, 163 : timeInterval: timeInterval, 164 : ); 165 1 : if (_positionStream != null) { 166 1 : return _positionStream!; 167 : } 168 1 : var originalStream = _eventChannel.receiveBroadcastStream( 169 1 : locationOptions.toJson(), 170 : ); 171 1 : var positionStream = _wrapStream(originalStream); 172 : 173 : if (timeLimit != null) { 174 1 : positionStream = positionStream.timeout( 175 : timeLimit, 176 1 : onTimeout: (s) { 177 2 : s.addError(TimeoutException( 178 : 'Time limit reached while waiting for position update.', 179 : timeLimit, 180 : )); 181 1 : s.close(); 182 1 : _positionStream = null; 183 : }, 184 : ); 185 : } 186 : 187 1 : _positionStream = positionStream 188 2 : .map<Position>((dynamic element) => 189 2 : Position.fromMap(element.cast<String, dynamic>())) 190 1 : .handleError( 191 1 : (error) { 192 1 : _positionStream = null; 193 1 : if (error is PlatformException) { 194 1 : _handlePlatformException(error); 195 : } 196 : throw error; 197 : }, 198 : ); 199 1 : return _positionStream!; 200 : } 201 : 202 1 : Stream<dynamic> _wrapStream(Stream<dynamic> incoming) { 203 : late StreamSubscription subscription; 204 : late StreamController<dynamic> controller; 205 1 : controller = StreamController<dynamic>( 206 1 : onListen: () { 207 1 : subscription = incoming.listen( 208 2 : (item) => controller.add(item), 209 2 : onError: (error) => controller.addError(error), 210 2 : onDone: () => controller.close(), 211 : ); 212 : }, 213 1 : onCancel: () { 214 1 : subscription.cancel(); 215 1 : _positionStream = null; 216 : }, 217 : ); 218 2 : return controller.stream.asBroadcastStream(); 219 : } 220 : 221 : @override 222 1 : Future<bool> openAppSettings() async => _methodChannel 223 1 : .invokeMethod<bool>('openAppSettings') 224 2 : .then((value) => value ?? false); 225 : 226 : @override 227 1 : Future<bool> openLocationSettings() async => _methodChannel 228 1 : .invokeMethod<bool>('openLocationSettings') 229 2 : .then((value) => value ?? false); 230 : 231 1 : void _handlePlatformException(PlatformException exception) { 232 1 : switch (exception.code) { 233 1 : case 'ACTIVITY_MISSING': 234 2 : throw ActivityMissingException(exception.message); 235 1 : case 'LOCATION_SERVICES_DISABLED': 236 1 : throw LocationServiceDisabledException(); 237 1 : case 'LOCATION_SUBSCRIPTION_ACTIVE': 238 1 : throw AlreadySubscribedException(); 239 1 : case 'PERMISSION_DEFINITIONS_NOT_FOUND': 240 2 : throw PermissionDefinitionsNotFoundException(exception.message); 241 1 : case 'PERMISSION_DENIED': 242 2 : throw PermissionDeniedException(exception.message); 243 1 : case 'PERMISSION_REQUEST_IN_PROGRESS': 244 2 : throw PermissionRequestInProgressException(exception.message); 245 1 : case 'LOCATION_UPDATE_FAILURE': 246 2 : throw PositionUpdateException(exception.message); 247 : default: 248 : throw exception; 249 : } 250 : } 251 : }