From 8e831cc3769389fc33cf0dc76fdb544913ef33d3 Mon Sep 17 00:00:00 2001 From: Mehmet Fidanboylu Date: Mon, 18 Sep 2017 17:29:30 -0700 Subject: [PATCH] onConnectionChanged support for Connectivity plugin (#215) * Initial commit of onConnectionChanged support for Connectivity * Documentation updates and version bump * Fix bug for always returning none * Do the breaking change version properly --- packages/connectivity/CHANGELOG.md | 5 ++ packages/connectivity/README.md | 28 ++++++- .../connectivity/ConnectivityPlugin.java | 76 ++++++++++++++----- packages/connectivity/example/lib/main.dart | 16 +++- .../ios/Classes/ConnectivityPlugin.m | 66 ++++++++++++---- packages/connectivity/lib/connectivity.dart | 36 +++++++-- packages/connectivity/pubspec.yaml | 2 +- 7 files changed, 186 insertions(+), 43 deletions(-) diff --git a/packages/connectivity/CHANGELOG.md b/packages/connectivity/CHANGELOG.md index 29f648fd17f1..99543b8da04c 100644 --- a/packages/connectivity/CHANGELOG.md +++ b/packages/connectivity/CHANGELOG.md @@ -1,3 +1,8 @@ +## 0.1.0 + +* Breaking API change: Have a Connectivity class instead of a top level function +* Introduce ability to listen for network state changes + ## 0.0.1 * Initial release diff --git a/packages/connectivity/README.md b/packages/connectivity/README.md index 78b161f61e7b..f6a9807d72db 100644 --- a/packages/connectivity/README.md +++ b/packages/connectivity/README.md @@ -7,12 +7,12 @@ This plugin works for iOS and Android. > Note that on Android, this does not guarantee connection to Internet. For instance, the app might have wifi access but it might be a VPN or a hotel WiFi with no access. -Sample usage: +Sample usage to check current status: ```dart -import 'package:connectivity/connectivity.dart' as connectivity; +import 'package:connectivity/connectivity.dart'; -var connectivityResult = await connectivity.checkConnectivity(); +var connectivityResult = await (new Connectivity().checkConnectivity()); if (connectivityResult == ConnectivityResult.mobile) { // I am connected to a mobile network. } else if (connectivityResult == ConnectivityResult.wifi) { @@ -20,6 +20,28 @@ if (connectivityResult == ConnectivityResult.mobile) { } ``` +> Note that you should not be using the current network status for deciding +whether you can reliably make a network connection. Always guard your app code +against timeouts and errors that might come from the network layer. + +You can also listen for network state changes by subscribing to the stream +exposed by connectivity plugin: + +```dart +import 'package:connectivity/connectivity.dart'; + +initState() { + subscription = new Connectivity().onConnectivityChanged.listen((ConnectivityResult result) { + // Got a new connectivity status! + }) +} + +// Be sure to cancel subscription after you are done +dispose() { + subscription.cancel(); +} +``` + ## Getting Started For help getting started with Flutter, view our online diff --git a/packages/connectivity/android/src/main/java/io/flutter/plugins/connectivity/ConnectivityPlugin.java b/packages/connectivity/android/src/main/java/io/flutter/plugins/connectivity/ConnectivityPlugin.java index 91699a07c036..15fd1bf14ac8 100644 --- a/packages/connectivity/android/src/main/java/io/flutter/plugins/connectivity/ConnectivityPlugin.java +++ b/packages/connectivity/android/src/main/java/io/flutter/plugins/connectivity/ConnectivityPlugin.java @@ -5,9 +5,15 @@ package io.flutter.plugins.connectivity; import android.app.Activity; +import android.content.BroadcastReceiver; import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; import android.net.ConnectivityManager; import android.net.NetworkInfo; +import io.flutter.plugin.common.EventChannel; +import io.flutter.plugin.common.EventChannel.EventSink; +import io.flutter.plugin.common.EventChannel.StreamHandler; import io.flutter.plugin.common.MethodCall; import io.flutter.plugin.common.MethodChannel; import io.flutter.plugin.common.MethodChannel.MethodCallHandler; @@ -15,18 +21,51 @@ import io.flutter.plugin.common.PluginRegistry.Registrar; /** ConnectivityPlugin */ -public class ConnectivityPlugin implements MethodCallHandler { +public class ConnectivityPlugin implements MethodCallHandler, StreamHandler { + private final Activity activity; private final ConnectivityManager manager; + private BroadcastReceiver receiver; /** Plugin registration. */ public static void registerWith(Registrar registrar) { final MethodChannel channel = new MethodChannel(registrar.messenger(), "plugins.flutter.io/connectivity"); - channel.setMethodCallHandler(new ConnectivityPlugin(registrar.activity())); + final EventChannel eventChannel = + new EventChannel(registrar.messenger(), "plugins.flutter.io/connectivity_status"); + ConnectivityPlugin instance = new ConnectivityPlugin(registrar.activity()); + channel.setMethodCallHandler(instance); + eventChannel.setStreamHandler(instance); } private ConnectivityPlugin(Activity activity) { - manager = (ConnectivityManager) activity.getSystemService(Context.CONNECTIVITY_SERVICE); + this.activity = activity; + this.manager = (ConnectivityManager) activity.getSystemService(Context.CONNECTIVITY_SERVICE); + } + + @Override + public void onListen(Object arguments, EventSink events) { + receiver = createReceiver(events); + activity.registerReceiver(receiver, new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)); + } + + @Override + public void onCancel(Object arguments) { + activity.unregisterReceiver(receiver); + receiver = null; + } + + private static String getNetworkType(int type) { + switch (type) { + case ConnectivityManager.TYPE_ETHERNET: + case ConnectivityManager.TYPE_WIFI: + case ConnectivityManager.TYPE_WIMAX: + return "wifi"; + case ConnectivityManager.TYPE_MOBILE: + case ConnectivityManager.TYPE_MOBILE_DUN: + return "mobile"; + default: + return "none"; + } } @Override @@ -34,20 +73,7 @@ public void onMethodCall(MethodCall call, Result result) { if (call.method.equals("check")) { NetworkInfo info = manager.getActiveNetworkInfo(); if (info != null && info.isConnected()) { - switch (info.getType()) { - case ConnectivityManager.TYPE_ETHERNET: - case ConnectivityManager.TYPE_WIFI: - case ConnectivityManager.TYPE_WIMAX: - result.success("wifi"); - break; - case ConnectivityManager.TYPE_MOBILE: - case ConnectivityManager.TYPE_MOBILE_DUN: - result.success("mobile"); - break; - default: - result.success("none"); - break; - } + result.success(getNetworkType(info.getType())); } else { result.success("none"); } @@ -55,4 +81,20 @@ public void onMethodCall(MethodCall call, Result result) { result.notImplemented(); } } + + private BroadcastReceiver createReceiver(final EventSink events) { + return new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + boolean isLost = intent.getBooleanExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY, false); + if (isLost) { + events.success("none"); + return; + } + + int type = intent.getIntExtra(ConnectivityManager.EXTRA_NETWORK_TYPE, -1); + events.success(getNetworkType(type)); + } + }; + } } diff --git a/packages/connectivity/example/lib/main.dart b/packages/connectivity/example/lib/main.dart index ca0b2e3835e1..0983ad0198ca 100644 --- a/packages/connectivity/example/lib/main.dart +++ b/packages/connectivity/example/lib/main.dart @@ -6,7 +6,7 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'package:connectivity/connectivity.dart' as connectivity; +import 'package:connectivity/connectivity.dart'; void main() { runApp(new MyApp()); @@ -37,11 +37,23 @@ class MyHomePage extends StatefulWidget { class _MyHomePageState extends State { String _connectionStatus = 'Unknown'; + final Connectivity _connectivity = new Connectivity(); + StreamSubscription _connectivitySubscription; @override void initState() { super.initState(); initConnectivity(); + _connectivitySubscription = + _connectivity.onConnectivityChanged.listen((ConnectivityResult result) { + setState(() => _connectionStatus = result.toString()); + }); + } + + @override + void dispose() { + _connectivitySubscription.cancel(); + super.dispose(); } // Platform messages are asynchronous, so we initialize in an async method. @@ -49,7 +61,7 @@ class _MyHomePageState extends State { String connectionStatus; // Platform messages may fail, so we use a try/catch PlatformException. try { - connectionStatus = (await connectivity.checkConnectivity()).toString(); + connectionStatus = (await _connectivity.checkConnectivity()).toString(); } on PlatformException catch (e) { print(e.toString()); connectionStatus = 'Failed to get connectivity.'; diff --git a/packages/connectivity/ios/Classes/ConnectivityPlugin.m b/packages/connectivity/ios/Classes/ConnectivityPlugin.m index fdee8603bf16..6516d5b5c2ec 100644 --- a/packages/connectivity/ios/Classes/ConnectivityPlugin.m +++ b/packages/connectivity/ios/Classes/ConnectivityPlugin.m @@ -6,13 +6,37 @@ #import "Reachability/Reachability.h" -@implementation ConnectivityPlugin +@interface ConnectivityPlugin () +@end + +@implementation ConnectivityPlugin { + FlutterEventSink _eventSink; +} + + (void)registerWithRegistrar:(NSObject*)registrar { + ConnectivityPlugin* instance = [[ConnectivityPlugin alloc] init]; + FlutterMethodChannel* channel = [FlutterMethodChannel methodChannelWithName:@"plugins.flutter.io/connectivity" binaryMessenger:[registrar messenger]]; - ConnectivityPlugin* instance = [[ConnectivityPlugin alloc] init]; [registrar addMethodCallDelegate:instance channel:channel]; + + FlutterEventChannel* streamChannel = + [FlutterEventChannel eventChannelWithName:@"plugins.flutter.io/connectivity_status" + binaryMessenger:[registrar messenger]]; + [streamChannel setStreamHandler:instance]; +} + +- (NSString*)statusFromReachability:(Reachability*)reachability { + NetworkStatus status = [reachability currentReachabilityStatus]; + switch (status) { + case NotReachable: + return @"none"; + case ReachableViaWiFi: + return @"wifi"; + case ReachableViaWWAN: + return @"mobile"; + } } - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { @@ -20,22 +44,34 @@ - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { // This is supposed to be quick. Another way of doing this would be to signup for network // connectivity changes. However that depends on the app being in background and the code // gets more involved. So for now, this will do. - NetworkStatus status = - [[Reachability reachabilityForInternetConnection] currentReachabilityStatus]; - switch (status) { - case NotReachable: - result(@"none"); - break; - case ReachableViaWiFi: - result(@"wifi"); - break; - case ReachableViaWWAN: - result(@"mobile"); - break; - } + result([self statusFromReachability:[Reachability reachabilityForInternetConnection]]); } else { result(FlutterMethodNotImplemented); } } +- (void)onReachabilityDidChange:(NSNotification*)notification { + Reachability* curReach = [notification object]; + _eventSink([self statusFromReachability:curReach]); +} + +#pragma mark FlutterStreamHandler impl + +- (FlutterError*)onListenWithArguments:(id)arguments eventSink:(FlutterEventSink)eventSink { + _eventSink = eventSink; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(onReachabilityDidChange:) + name:kReachabilityChangedNotification + object:nil]; + [[Reachability reachabilityForInternetConnection] startNotifier]; + return nil; +} + +- (FlutterError*)onCancelWithArguments:(id)arguments { + [[Reachability reachabilityForInternetConnection] stopNotifier]; + [[NSNotificationCenter defaultCenter] removeObserver:self]; + _eventSink = nil; + return nil; +} + @end diff --git a/packages/connectivity/lib/connectivity.dart b/packages/connectivity/lib/connectivity.dart index cf563ad31c3d..540534f91814 100644 --- a/packages/connectivity/lib/connectivity.dart +++ b/packages/connectivity/lib/connectivity.dart @@ -13,13 +13,39 @@ import 'package:flutter/services.dart'; /// None: Device not connected to any network enum ConnectivityResult { wifi, mobile, none } -const MethodChannel _channel = +const MethodChannel _methodChannel = const MethodChannel('plugins.flutter.io/connectivity'); -/// Checks the connection status of the device. -Future checkConnectivity() async { - final String result = await _channel.invokeMethod('check'); - switch (result) { +const EventChannel _eventChannel = + const EventChannel('plugins.flutter.io/connectivity_status'); + +class Connectivity { + Stream _onConnectivityChanged; + + /// Fires whenever the connectivity state changes. + Stream get onConnectivityChanged { + if (_onConnectivityChanged == null) { + _onConnectivityChanged = _eventChannel + .receiveBroadcastStream() + .map(_stringToConnectivityResult); + } + return _onConnectivityChanged; + } + + /// Checks the connection status of the device. + /// + /// Do not use the result of this function to decide whether you can reliably + /// make a network request. It only gives you the radio status. + /// + /// Instead listen for connectivity changes via [onConnectivityChanged] stream. + Future checkConnectivity() async { + final String result = await _methodChannel.invokeMethod('check'); + return _stringToConnectivityResult(result); + } +} + +ConnectivityResult _stringToConnectivityResult(String state) { + switch (state) { case 'wifi': return ConnectivityResult.wifi; case 'mobile': diff --git a/packages/connectivity/pubspec.yaml b/packages/connectivity/pubspec.yaml index 4dcba9468582..d838d75a2cd9 100644 --- a/packages/connectivity/pubspec.yaml +++ b/packages/connectivity/pubspec.yaml @@ -1,6 +1,6 @@ name: connectivity description: Allows developers to discover the state of the network connectivity. -version: 0.0.1+1 +version: 0.1.0 author: Flutter Team homepage: https://github.com/flutter/plugins/tree/master/packages/connectivity