Skip to content

Commit

Permalink
fix(connectivity_plus): Ensure Connectivity on Android is correctly r…
Browse files Browse the repository at this point in the history
…eported when lost (#2836)
  • Loading branch information
miquelbeltran authored Apr 22, 2024
1 parent ad06a23 commit 2aa6ad7
Show file tree
Hide file tree
Showing 6 changed files with 80 additions and 45 deletions.
4 changes: 3 additions & 1 deletion packages/connectivity_plus/connectivity_plus/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -27,44 +29,54 @@ public Connectivity(ConnectivityManager connectivityManager) {
}

List<String> getNetworkTypes() {
List<String> 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<String> getCapabilitiesFromNetwork(Network network) {
NetworkCapabilities capabilities = connectivityManager.getNetworkCapabilities(network);
return getCapabilitiesList(capabilities);
}

@NonNull
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
List<String> getCapabilitiesList(NetworkCapabilities capabilities) {
List<String> 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;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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);
Expand All @@ -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
Expand All @@ -92,11 +106,16 @@ public void onReceive(Context context, Intent intent) {
}
}

private void sendEvent() {
private void sendEvent(List<String> 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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,8 @@ class _MyHomePageState extends State<MyHomePage> {
setState(() {
_connectionStatus = result;
});
// ignore: avoid_print
print('Connectivity changed: $_connectionStatus');
}

@override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -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<List<ConnectivityResult>> get onConnectivityChanged {
return _platform.onConnectivityChanged;
return _platform.onConnectivityChanged.distinct((a, b) => a.equals(b));
}

/// Checks the connection status of the device.
Expand Down
1 change: 1 addition & 0 deletions packages/connectivity_plus/connectivity_plus/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down

0 comments on commit 2aa6ad7

Please sign in to comment.