-
Notifications
You must be signed in to change notification settings - Fork 9.6k
[Connectivity] add a method to request location on iOS (for iOS 13) #1999
Changes from 5 commits
5c8d7f3
7201c6f
bb199d4
0fd8cd9
17f69cf
69ab075
50a0ffb
169c5e6
782c472
0634d62
c7666d6
3a68b15
ef2dcfb
c0d9783
26b2874
666821b
e577181
12efe99
f908024
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -60,17 +60,39 @@ var wifiIP = await (Connectivity().getWifiIP());network | |||||
| var wifiName = await (Connectivity().getWifiName());wifi network | ||||||
| ``` | ||||||
|
|
||||||
| ### Known Issues | ||||||
| ### iOS 12 | ||||||
|
|
||||||
| #### iOS 13 | ||||||
| To use `.getWifiBSSID()` and `.getWifiName()` on iOS >= 12, the `Access WiFi information capability` in XCode must be enabled. Otherwise, both method will return null. | ||||||
|
|
||||||
| ### iOS 13 | ||||||
|
|
||||||
| The methods `.getWifiBSSID()` and `.getWifiName()` utilize the [CNCopyCurrentNetworkInfo](https://developer.apple.com/documentation/systemconfiguration/1614126-cncopycurrentnetworkinfo) function on iOS. | ||||||
|
cyanglaz marked this conversation as resolved.
Outdated
|
||||||
|
|
||||||
| As of iOS 13, Apple announced that these APIs will no longer return valid information by default and will instead return the following: | ||||||
| > SSID: "Wi-Fi" or "WLAN" ("WLAN" will be returned for the China SKU) | ||||||
| > BSSID: "00:00:00:00:00:00" | ||||||
| As of iOS 13, Apple announced that these APIs will no longer return valid information. | ||||||
| An App linked against iOS 12 or earlier receives pseudo-values such as: | ||||||
|
cyanglaz marked this conversation as resolved.
Outdated
|
||||||
|
|
||||||
| * SSID: "Wi-Fi" or "WLAN" ("WLAN" will be returned for the China SKU). | ||||||
|
|
||||||
| * BSSID: "00:00:00:00:00:00" | ||||||
|
|
||||||
| An App linked against iOS 13 or later receives null. | ||||||
|
cyanglaz marked this conversation as resolved.
Outdated
|
||||||
|
|
||||||
| The [CNCopyCurrentNetworkInfo] will work for Apps that: | ||||||
|
cyanglaz marked this conversation as resolved.
Outdated
|
||||||
|
|
||||||
| * The app uses Core Location, and has the user’s authorization to use location information. | ||||||
|
|
||||||
| * The app uses the NEHotspotConfiguration API to configure the current Wi-Fi network. | ||||||
|
|
||||||
| * The app has active VPN configurations installed. | ||||||
|
|
||||||
| If your app falls into the last two categories, it will work as it is. If your app doesn't fall into the last two categories, | ||||||
| and you still need to access the wifi information, you should request user's authorization to use location information. | ||||||
|
|
||||||
| There is a helper method provided in this plugin to request the location authorization: `requestLocationServiceAuthorizationIfUndetermined`. | ||||||
| To request location authorization, make sure to add the following keys to your _Info.plist_ file, located in `<project root>/ios/Runner/Info.plist`: | ||||||
|
|
||||||
| You can follow issue [#37804](https://github.com/flutter/flutter/issues/37804) for the changes required to return valid SSID and BSSID values with iOS 13. | ||||||
| * `NSLocationAlwaysAndWhenInUseUsageDescription` - describe why the app needs access to the user’s location information all the time (foreground and background). _Privacy - Location Always and When In Use Usage Description_ in the visual editor. | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I might be wrong here but I think it should just be Privacy - Location Always Usage Description
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That's what I thought before writing this PR too :) Appearently the name has been changed.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you add "This is called" from my suggestion above? |
||||||
| * `NSLocationWhenInUseUsageDescription` - describe why the app needs access to the user’s location information when the app is running in the foreground. This is called _Privacy - Location When In Use Usage Description_ in the visual editor. | ||||||
|
|
||||||
| ## Getting Started | ||||||
|
|
||||||
|
|
||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -22,6 +22,10 @@ | |
| <string>1</string> | ||
| <key>LSRequiresIPhoneOS</key> | ||
| <true/> | ||
| <key>NSLocationAlwaysAndWhenInUseUsageDescription</key> | ||
| <string>Location service</string> | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think you should put an appropriate English sentence here since it's an example app. |
||
| <key>NSLocationWhenInUseUsageDescription</key> | ||
| <string>locaiton service in use</string> | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Typo, also I think you should put an English sentence here |
||
| <key>UILaunchStoryboardName</key> | ||
| <string>LaunchScreen</string> | ||
| <key>UIMainStoryboardFile</key> | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| <?xml version="1.0" encoding="UTF-8"?> | ||
| <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> | ||
| <plist version="1.0"> | ||
| <dict> | ||
| <key>com.apple.developer.networking.wifi-info</key> | ||
| <true/> | ||
| </dict> | ||
| </plist> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7,12 +7,18 @@ | |
| #import "Reachability/Reachability.h" | ||
|
|
||
| #import "SystemConfiguration/CaptiveNetwork.h" | ||
| #import <CoreLocation/CoreLocation.h> | ||
|
|
||
|
|
||
| #include <ifaddrs.h> | ||
|
|
||
| #include <arpa/inet.h> | ||
|
|
||
| @interface FLTConnectivityPlugin () <FlutterStreamHandler> | ||
| @interface FLTConnectivityPlugin () <FlutterStreamHandler, CLLocationManagerDelegate> | ||
|
|
||
| @property (strong, nonatomic) CLLocationManager* locationManager; | ||
| @property (copy, nonatomic) FlutterResult result; | ||
|
|
||
| @end | ||
|
|
||
| @implementation FLTConnectivityPlugin { | ||
|
|
@@ -97,6 +103,41 @@ - (NSString*)statusFromReachability:(Reachability*)reachability { | |
| } | ||
| } | ||
|
|
||
| - (void)requestLocationServiceAuthorizationIfUndetermined:(FlutterResult)result always:(NSNumber *)always { | ||
| CLAuthorizationStatus status = CLLocationManager.authorizationStatus; | ||
| switch (status) { | ||
| case kCLAuthorizationStatusNotDetermined: { | ||
| self.result = result; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think there's a race condition here where two calls to |
||
| if (always.boolValue) { | ||
| [self.locationManager requestAlwaysAuthorization]; | ||
| } else { | ||
| [self.locationManager requestWhenInUseAuthorization]; | ||
| } | ||
| return; | ||
| } | ||
| case kCLAuthorizationStatusRestricted: { | ||
| result(@"restricted"); | ||
| break; | ||
| } | ||
| case kCLAuthorizationStatusDenied: { | ||
| result(@"denied"); | ||
| break; | ||
| } | ||
| case kCLAuthorizationStatusAuthorizedAlways: { | ||
| result(@"authorizedAlways"); | ||
| break; | ||
| } | ||
| case kCLAuthorizationStatusAuthorizedWhenInUse: { | ||
| result(@"authorizedWhenInUse"); | ||
| break; | ||
| } | ||
| default: { | ||
| result(@"unknown"); | ||
| break; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { | ||
| if ([call.method isEqualToString:@"check"]) { | ||
| // This is supposed to be quick. Another way of doing this would be to | ||
|
|
@@ -111,6 +152,9 @@ - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { | |
| result([self getBSSID]); | ||
| } else if ([call.method isEqualToString:@"wifiIPAddress"]) { | ||
| result([self getWifiIP]); | ||
| } else if ([call.method isEqualToString:@"requestLocationServiceAuthorizationIfUndetermined"]) { | ||
| NSArray *arguments = call.arguments; | ||
| [self requestLocationServiceAuthorizationIfUndetermined:result always:arguments.firstObject]; | ||
| } else { | ||
| result(FlutterMethodNotImplemented); | ||
| } | ||
|
|
@@ -140,4 +184,18 @@ - (FlutterError*)onCancelWithArguments:(id)arguments { | |
| return nil; | ||
| } | ||
|
|
||
| #pragma mark - CLLocationManagerDelegate | ||
|
|
||
| - (CLLocationManager *)locationManager { | ||
| if (!_locationManager) { | ||
| _locationManager = [CLLocationManager new]; | ||
| _locationManager.delegate = self; | ||
| } | ||
| return _locationManager; | ||
| } | ||
|
|
||
| - (void)locationManager:(CLLocationManager *)manager didChangeAuthorizationStatus:(CLAuthorizationStatus)status { | ||
| [self requestLocationServiceAuthorizationIfUndetermined:self.result always:nil]; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should probably set
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I added a new class |
||
| } | ||
|
|
||
| @end | ||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -3,6 +3,7 @@ | |||||
| // found in the LICENSE file. | ||||||
|
|
||||||
| import 'dart:async'; | ||||||
| import 'dart:io'; | ||||||
|
|
||||||
| import 'package:flutter/services.dart'; | ||||||
| import 'package:meta/meta.dart'; | ||||||
|
|
@@ -93,6 +94,66 @@ class Connectivity { | |||||
| Future<String> getWifiIP() async { | ||||||
| return await methodChannel.invokeMethod<String>('wifiIPAddress'); | ||||||
| } | ||||||
|
|
||||||
| /// Request to authorize the location service. Only on iOS. | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
I think Dartdoc comments are supposed to have tight first sentence with no other sentences on the first line. https://dart.dev/guides/language/effective-dart/documentation |
||||||
| /// | ||||||
| /// Returns a [LocationAuthorizationStatus] if the location service has already been authorized or user authorized the location on | ||||||
| /// This request. | ||||||
|
cyanglaz marked this conversation as resolved.
Outdated
|
||||||
| /// | ||||||
| /// if the location information needs to be accessible all the time, set `requestAlwaysLocationUsage` to true. Note that the status | ||||||
| /// returned might not be [LocationAuthorizationStatus.authorizedAlways] even you requested it. User might have already chosen a location authorization | ||||||
|
cyanglaz marked this conversation as resolved.
Outdated
|
||||||
| /// to this app. | ||||||
| /// | ||||||
| /// It will show a platform standard window of requesting a location service. | ||||||
| /// | ||||||
| /// If the user declined the location service, it will never show the window to request the authorization again. | ||||||
| /// The user has to go to the setting app in the phone to enable authorization. | ||||||
|
cyanglaz marked this conversation as resolved.
Outdated
|
||||||
| /// | ||||||
| /// This method is a helper to get the location authorization that is necessary for certain functionalities in this plugin. | ||||||
|
cyanglaz marked this conversation as resolved.
Outdated
|
||||||
| /// It can be replaced with other permission handling code/plugin if preferred. | ||||||
| /// To request location authorization, make sure to add the following keys to your _Info.plist_ file, located in `<project root>/ios/Runner/Info.plist`: | ||||||
| /// * `NSLocationAlwaysAndWhenInUseUsageDescription` - describe why the app needs access to the user’s location information | ||||||
| /// all the time (foreground and background). This is called _Privacy - Location Always and When In Use Usage Description_ in the visual editor. | ||||||
| /// * `NSLocationWhenInUseUsageDescription` - describe why the app needs access to the user’s location information when the app is | ||||||
| /// running in the foreground. This is called _Privacy - Location When In Use Usage Description_ in the visual editor. | ||||||
| /// | ||||||
| /// Starting from iOS 13, `getWifiBSSID` and `getWifiIP` will only work properly if: | ||||||
| /// | ||||||
| /// * The app uses Core Location, and has the user’s authorization to use location information. | ||||||
| /// * The app uses the NEHotspotConfiguration API to configure the current Wi-Fi network. | ||||||
| /// * The app has active VPN configurations installed. | ||||||
| /// | ||||||
| /// If the app falls into the first category, call this method before calling `getWifiBSSID` or `getWifiIP`. | ||||||
| /// For example, | ||||||
| /// ```dart | ||||||
| /// if (Platform.isIOS) { | ||||||
| /// LocationAuthorizationStatus status = await _connectivity.requestLocationServiceAuthorizationIfUndetermined(); | ||||||
| /// if (status == LocationAuthorizationStatus.authorizedAlways || status == LocationAuthorizationStatus.authorizedWhenInUse) { | ||||||
| /// wifiBSSID = await _connectivity.getWifiBSSID(); | ||||||
| /// } else { | ||||||
| /// print('location service is not authorized'); | ||||||
| /// } | ||||||
| /// } | ||||||
| /// ``` | ||||||
| /// This method will throw on Android. | ||||||
| Future<LocationAuthorizationStatus> requestLocationServiceAuthorizationIfUndetermined({bool requestAlwaysLocationUsage = false}) async { | ||||||
| //Just `assert(Platform.isIOS)` will disable us to do dart side unit testing. | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
| assert(!Platform.isAndroid); | ||||||
| final String result = await methodChannel | ||||||
| .invokeMethod<String>('requestLocationServiceAuthorizationIfUndetermined', <bool>[requestAlwaysLocationUsage]); | ||||||
| switch (result) { | ||||||
| case 'restricted': | ||||||
| return LocationAuthorizationStatus.restricted; | ||||||
| case 'denied': | ||||||
| return LocationAuthorizationStatus.denied; | ||||||
| case 'authorizedAlways': | ||||||
| return LocationAuthorizationStatus.authorizedAlways; | ||||||
| case 'authorizedWhenInUse': | ||||||
| return LocationAuthorizationStatus.authorizedWhenInUse; | ||||||
| default: | ||||||
| return LocationAuthorizationStatus.unknown; | ||||||
| } | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| ConnectivityResult _parseConnectivityResult(String state) { | ||||||
|
|
@@ -106,3 +167,22 @@ ConnectivityResult _parseConnectivityResult(String state) { | |||||
| return ConnectivityResult.none; | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| /// The status of the location service authorization. | ||||||
| enum LocationAuthorizationStatus { | ||||||
|
|
||||||
| /// This app is not authorized to use location. | ||||||
| restricted, | ||||||
|
|
||||||
| /// User explicitly denied the location service. | ||||||
| denied, | ||||||
|
|
||||||
| /// User authorized the app to access the location at any time. | ||||||
| authorizedAlways, | ||||||
|
|
||||||
| /// User authorized the app to access the location when the app is visible to them. | ||||||
| authorizedWhenInUse, | ||||||
|
|
||||||
| /// Status unknown. | ||||||
| unknown | ||||||
| } | ||||||
Uh oh!
There was an error while loading. Please reload this page.