Skip to content
This repository was archived by the owner on Feb 22, 2023. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions packages/connectivity/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 0.4.4

* Add `requestLocationServiceAuthorizationIfUndetermined` to get location information on iOS.

## 0.4.3+7

* Update README with the updated information about CNCopyCurrentNetworkInfo on iOS 13.
Expand Down
34 changes: 28 additions & 6 deletions packages/connectivity/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Comment thread
cyanglaz marked this conversation as resolved.
Outdated

### iOS 13

The methods `.getWifiBSSID()` and `.getWifiName()` utilize the [CNCopyCurrentNetworkInfo](https://developer.apple.com/documentation/systemconfiguration/1614126-cncopycurrentnetworkinfo) function on iOS.
Comment thread
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:
Comment thread
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.
Comment thread
cyanglaz marked this conversation as resolved.
Outdated

The [CNCopyCurrentNetworkInfo] will work for Apps that:
Comment thread
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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* `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.
* `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 Usage Description_ in the visual editor.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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

Expand Down
4 changes: 4 additions & 0 deletions packages/connectivity/example/ios/Runner/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@
<string>1</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>Location service</string>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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>
Expand Down
8 changes: 8 additions & 0 deletions packages/connectivity/example/ios/Runner/Runner.entitlements
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>
25 changes: 23 additions & 2 deletions packages/connectivity/example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// found in the LICENSE file.

import 'dart:async';
import 'dart:io';

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
Expand Down Expand Up @@ -90,14 +91,34 @@ class _MyHomePageState extends State<MyHomePage> {
String wifiName, wifiBSSID, wifiIP;

try {
wifiName = await _connectivity.getWifiName();
if (Platform.isIOS) {
LocationAuthorizationStatus status = await _connectivity.requestLocationServiceAuthorizationIfUndetermined();
if (status == LocationAuthorizationStatus.authorizedAlways || status == LocationAuthorizationStatus.authorizedWhenInUse) {
wifiBSSID = await _connectivity.getWifiName();
} else {
print('location service is not authorized, the data might not be correct');
wifiBSSID = await _connectivity.getWifiName();
}
} else {
wifiBSSID = await _connectivity.getWifiName();
}
} on PlatformException catch (e) {
print(e.toString());
wifiName = "Failed to get Wifi Name";
}

try {
wifiBSSID = await _connectivity.getWifiBSSID();
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, the data might not be correct');
wifiBSSID = await _connectivity.getWifiBSSID();
}
} else {
wifiBSSID = await _connectivity.getWifiBSSID();
}
} on PlatformException catch (e) {
print(e.toString());
wifiBSSID = "Failed to get Wifi BSSID";
Expand Down
60 changes: 59 additions & 1 deletion packages/connectivity/ios/Classes/ConnectivityPlugin.m
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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;

@collinjackson collinjackson Aug 23, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think there's a race condition here where two calls to requestLocationServiceAuthorizationIfUndetermined in quick succession could lead to result being clobbered before being invoked. Consider returning an error if self.result != null or handling the case gracefully.

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
Expand All @@ -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);
}
Expand Down Expand Up @@ -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];

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should probably set self.result to nil here so we don't accidentally notify it more than once.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added a new class FLTConnectivityLocationHandler to handle all the location related stuff so now the result is inline.
FLTConnectivityLocationHandler handles the race condition that you mentioned by returning notDetermined if a request is in process.

}

@end
80 changes: 80 additions & 0 deletions packages/connectivity/lib/connectivity.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// Request to authorize the location service. Only on iOS.
/// Request to authorize the location service (only on iOS).

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.
Comment thread
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
Comment thread
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.
Comment thread
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.
Comment thread
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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
//Just `assert(Platform.isIOS)` will disable us to do dart side unit testing.
// Just `assert(Platform.isIOS)` will prevent us from doing Dart side unit testing.

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) {
Expand All @@ -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
}
2 changes: 1 addition & 1 deletion packages/connectivity/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ description: Flutter plugin for discovering the state of the network (WiFi &
mobile/cellular) connectivity on Android and iOS.
author: Flutter Team <flutter-dev@googlegroups.com>
homepage: https://github.com/flutter/plugins/tree/master/packages/connectivity
version: 0.4.3+7
version: 0.4.4

flutter:
plugin:
Expand Down
16 changes: 16 additions & 0 deletions packages/connectivity/test/connectivity_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ void main() {
return 'c0:ff:33:c0:d3:55';
case 'wifiIPAddress':
return '127.0.0.1';
case 'requestLocationServiceAuthorizationIfUndetermined':
return 'authorizedAlways';
default:
return null;
}
Expand Down Expand Up @@ -92,6 +94,20 @@ void main() {
);
});

test('requestLocationServiceAuthorizationIfUndetermined', () async {
final LocationAuthorizationStatus result = await Connectivity().requestLocationServiceAuthorizationIfUndetermined();
expect(result, LocationAuthorizationStatus.authorizedAlways);
expect(
log,
<Matcher>[
isMethodCall(
'requestLocationServiceAuthorizationIfUndetermined',
arguments: null,
),
],
);
});

test('checkConnectivity', () async {
final ConnectivityResult result =
await Connectivity().checkConnectivity();
Expand Down