From 2aa6ad7a1c05d35b894e6cef515bd9dfa49351dd Mon Sep 17 00:00:00 2001 From: Miguel Beltran Date: Mon, 22 Apr 2024 14:15:17 +0200 Subject: [PATCH] fix(connectivity_plus): Ensure Connectivity on Android is correctly reported when lost (#2836) --- .../connectivity_plus/README.md | 4 +- .../plus/connectivity/Connectivity.java | 72 +++++++++++-------- .../ConnectivityBroadcastReceiver.java | 37 +++++++--- .../connectivity_plus/example/lib/main.dart | 2 + .../lib/connectivity_plus.dart | 9 ++- .../connectivity_plus/pubspec.yaml | 1 + 6 files changed, 80 insertions(+), 45 deletions(-) diff --git a/packages/connectivity_plus/connectivity_plus/README.md b/packages/connectivity_plus/connectivity_plus/README.md index acc674411f..de2c75c763 100644 --- a/packages/connectivity_plus/connectivity_plus/README.md +++ b/packages/connectivity_plus/connectivity_plus/README.md @@ -58,7 +58,9 @@ if (connectivityResult.contains(ConnectivityResult.mobile)) { ``` You can also listen for active connectivity types changes by subscribing to the stream -exposed by the plugin: +exposed by the plugin. + +This method should ensure emitting only distinct values. ```dart import 'package:connectivity_plus/connectivity_plus.dart'; diff --git a/packages/connectivity_plus/connectivity_plus/android/src/main/java/dev/fluttercommunity/plus/connectivity/Connectivity.java b/packages/connectivity_plus/connectivity_plus/android/src/main/java/dev/fluttercommunity/plus/connectivity/Connectivity.java index 2e54d37758..d5e5cbbde2 100644 --- a/packages/connectivity_plus/connectivity_plus/android/src/main/java/dev/fluttercommunity/plus/connectivity/Connectivity.java +++ b/packages/connectivity_plus/connectivity_plus/android/src/main/java/dev/fluttercommunity/plus/connectivity/Connectivity.java @@ -8,6 +8,8 @@ import android.net.Network; import android.net.NetworkCapabilities; import android.os.Build; +import androidx.annotation.NonNull; +import androidx.annotation.RequiresApi; import java.util.ArrayList; import java.util.List; @@ -27,44 +29,54 @@ public Connectivity(ConnectivityManager connectivityManager) { } List getNetworkTypes() { - List types = new ArrayList<>(); if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { Network network = connectivityManager.getActiveNetwork(); - NetworkCapabilities capabilities = connectivityManager.getNetworkCapabilities(network); - if (capabilities == null - || !capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)) { - types.add(CONNECTIVITY_NONE); - return types; - } - if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) - || capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI_AWARE)) { - types.add(CONNECTIVITY_WIFI); - } - if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET)) { - types.add(CONNECTIVITY_ETHERNET); - } - if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_VPN)) { - types.add(CONNECTIVITY_VPN); - } - if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) { - types.add(CONNECTIVITY_MOBILE); - } - if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_BLUETOOTH)) { - types.add(CONNECTIVITY_BLUETOOTH); - } - if (types.isEmpty() - && capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)) { - types.add(CONNECTIVITY_OTHER); - } - if (types.isEmpty()) { - types.add(CONNECTIVITY_NONE); - } + return getCapabilitiesFromNetwork(network); } else { // For legacy versions, return a single type as before or adapt similarly if multiple types // need to be supported return getNetworkTypesLegacy(); } + } + @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) + List getCapabilitiesFromNetwork(Network network) { + NetworkCapabilities capabilities = connectivityManager.getNetworkCapabilities(network); + return getCapabilitiesList(capabilities); + } + + @NonNull + @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) + List getCapabilitiesList(NetworkCapabilities capabilities) { + List types = new ArrayList<>(); + if (capabilities == null + || !capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)) { + types.add(CONNECTIVITY_NONE); + return types; + } + if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) + || capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI_AWARE)) { + types.add(CONNECTIVITY_WIFI); + } + if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET)) { + types.add(CONNECTIVITY_ETHERNET); + } + if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_VPN)) { + types.add(CONNECTIVITY_VPN); + } + if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) { + types.add(CONNECTIVITY_MOBILE); + } + if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_BLUETOOTH)) { + types.add(CONNECTIVITY_BLUETOOTH); + } + if (types.isEmpty() + && capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)) { + types.add(CONNECTIVITY_OTHER); + } + if (types.isEmpty()) { + types.add(CONNECTIVITY_NONE); + } return types; } diff --git a/packages/connectivity_plus/connectivity_plus/android/src/main/java/dev/fluttercommunity/plus/connectivity/ConnectivityBroadcastReceiver.java b/packages/connectivity_plus/connectivity_plus/android/src/main/java/dev/fluttercommunity/plus/connectivity/ConnectivityBroadcastReceiver.java index 07a01796c1..324d3d234d 100644 --- a/packages/connectivity_plus/connectivity_plus/android/src/main/java/dev/fluttercommunity/plus/connectivity/ConnectivityBroadcastReceiver.java +++ b/packages/connectivity_plus/connectivity_plus/android/src/main/java/dev/fluttercommunity/plus/connectivity/ConnectivityBroadcastReceiver.java @@ -15,6 +15,7 @@ import android.os.Handler; import android.os.Looper; import io.flutter.plugin.common.EventChannel; +import java.util.List; /** * The ConnectivityBroadcastReceiver receives the connectivity updates and send them to the UIThread @@ -46,18 +47,31 @@ public void onListen(Object arguments, EventChannel.EventSink events) { new ConnectivityManager.NetworkCallback() { @Override public void onAvailable(Network network) { - sendEvent(); + // onAvailable is called when the phone switches to a new network + // e.g. the phone was offline and gets wifi connection + // or the phone was on wifi and now switches to mobile. + // The plugin sends the current capability connection to the users. + sendEvent(connectivity.getCapabilitiesFromNetwork(network)); } @Override public void onCapabilitiesChanged( Network network, NetworkCapabilities networkCapabilities) { - sendEvent(); + // This callback is called multiple times after a call to onAvailable + // this also causes multiple callbacks to the Flutter layer. + sendEvent(connectivity.getCapabilitiesList(networkCapabilities)); } @Override public void onLost(Network network) { - sendEvent(); + // This callback is called when a capability is lost. + // + // The provided Network object contains information about the + // network capability that has been lost, so we cannot use it. + // + // Instead, post the current network but with a delay long enough + // that we avoid a race condition. + sendCurrentStatusWithDelay(); } }; connectivity.getConnectivityManager().registerDefaultNetworkCallback(networkCallback); @@ -66,7 +80,7 @@ public void onLost(Network network) { } // Need to emit first event with connectivity types without waiting for first change in system // that might happen much later - sendEvent(); + sendEvent(connectivity.getNetworkTypes()); } @Override @@ -92,11 +106,16 @@ public void onReceive(Context context, Intent intent) { } } - private void sendEvent() { + private void sendEvent(List networkTypes) { + Runnable runnable = () -> events.success(networkTypes); + // Emit events on main thread + mainHandler.post(runnable); + } + + private void sendCurrentStatusWithDelay() { Runnable runnable = () -> events.success(connectivity.getNetworkTypes()); - // The dalay is needed because callback methods suffer from race conditions. - // More info: - // https://developer.android.com/develop/connectivity/network-ops/reading-network-state#listening-events - mainHandler.postDelayed(runnable, 100); + // Emit events on main thread + // 500 milliseconds to avoid race conditions + mainHandler.postDelayed(runnable, 500); } } diff --git a/packages/connectivity_plus/connectivity_plus/example/lib/main.dart b/packages/connectivity_plus/connectivity_plus/example/lib/main.dart index d13b94ef69..29927da5dc 100644 --- a/packages/connectivity_plus/connectivity_plus/example/lib/main.dart +++ b/packages/connectivity_plus/connectivity_plus/example/lib/main.dart @@ -86,6 +86,8 @@ class _MyHomePageState extends State { setState(() { _connectionStatus = result; }); + // ignore: avoid_print + print('Connectivity changed: $_connectionStatus'); } @override diff --git a/packages/connectivity_plus/connectivity_plus/lib/connectivity_plus.dart b/packages/connectivity_plus/connectivity_plus/lib/connectivity_plus.dart index c514a81335..cef5ddee13 100644 --- a/packages/connectivity_plus/connectivity_plus/lib/connectivity_plus.dart +++ b/packages/connectivity_plus/connectivity_plus/lib/connectivity_plus.dart @@ -5,6 +5,7 @@ import 'dart:async'; import 'package:connectivity_plus_platform_interface/connectivity_plus_platform_interface.dart'; +import 'package:collection/collection.dart'; // Export enums from the platform_interface so plugin users can use them directly. export 'package:connectivity_plus_platform_interface/connectivity_plus_platform_interface.dart' @@ -40,16 +41,14 @@ class Connectivity { /// status changes, this is a known issue that only affects simulators. /// For details see https://github.com/fluttercommunity/plus_plugins/issues/479. /// - /// On Android, the Stream may emit new values even when - /// the [ConnectivityResult] list remains the same. - /// /// The emitted list is never empty. In case of no connectivity, the list contains /// a single element of [ConnectivityResult.none]. Note also that this is the only /// case where [ConnectivityResult.none] is present. /// - /// This method doesn't filter events, nor it ensures distinct values. + /// This method applies [Stream.distinct] over the received events to ensure + /// only emiting when connectivity changes. Stream> get onConnectivityChanged { - return _platform.onConnectivityChanged; + return _platform.onConnectivityChanged.distinct((a, b) => a.equals(b)); } /// Checks the connection status of the device. diff --git a/packages/connectivity_plus/connectivity_plus/pubspec.yaml b/packages/connectivity_plus/connectivity_plus/pubspec.yaml index dfb5c841b4..64b280822a 100644 --- a/packages/connectivity_plus/connectivity_plus/pubspec.yaml +++ b/packages/connectivity_plus/connectivity_plus/pubspec.yaml @@ -41,6 +41,7 @@ dependencies: web: '>=0.3.0 <=0.6.0' meta: ^1.8.0 nm: ^0.5.0 + collection: ^1.18.0 dev_dependencies: flutter_test: