From 601de37f48df9a135ecb131bcc323ae82df57f27 Mon Sep 17 00:00:00 2001 From: Hovirix <184756415+Hovirix@users.noreply.github.com> Date: Sat, 17 Jan 2026 01:25:49 +0100 Subject: [PATCH] fix(connectivity_plus): prevent crash when NetworkManager is unavailable on Linux When NetworkManager is not installed or running on Linux systems, the plugin would crash with an uncaught DBus exception. This affected apps like AppFlowy and others on distributions without NetworkManager or with it disabled. Changes: - Add try-catch blocks to checkConnectivity() and _startListenConnectivity() - Return ConnectivityResult.none when NetworkManager is unavailable - Add explanatory comments about graceful degradation - Add unit tests for NetworkManager unavailable scenarios This ensures the plugin works on all Linux distributions regardless of their network management infrastructure, treating missing NetworkManager as an offline/unknown connectivity state rather than a fatal error. Fixes crashes on NixOS, minimal distributions, and systems with NetworkManager disabled or using alternative network managers (systemd-networkd, ConnMan, etc). --- .../lib/src/connectivity_plus_linux.dart | 41 ++++++--- .../test/connectivity_plus_linux_test.dart | 84 ++++++++++++++----- 2 files changed, 89 insertions(+), 36 deletions(-) diff --git a/packages/connectivity_plus/connectivity_plus/lib/src/connectivity_plus_linux.dart b/packages/connectivity_plus/connectivity_plus/lib/src/connectivity_plus_linux.dart index 31b562aa24..8d676fb351 100644 --- a/packages/connectivity_plus/connectivity_plus/lib/src/connectivity_plus_linux.dart +++ b/packages/connectivity_plus/connectivity_plus/lib/src/connectivity_plus_linux.dart @@ -20,11 +20,19 @@ class ConnectivityPlusLinuxPlugin extends ConnectivityPlatform { /// Checks the connection status of the device. @override Future> checkConnectivity() async { - final client = createClient(); - await client.connect(); - final connectivity = _getConnectivity(client); - await client.close(); - return connectivity; + try { + final client = createClient(); + await client.connect(); + final connectivity = _getConnectivity(client); + await client.close(); + return connectivity; + } catch (e) { + // NetworkManager may not be installed or running on this Linux system. + // Rather than crashing, we gracefully degrade by treating this as + // no connectivity. This ensures the plugin works on all Linux distributions + // regardless of their network management infrastructure. + return [ConnectivityResult.none]; + } } NetworkManagerClient? _client; @@ -69,14 +77,21 @@ class ConnectivityPlusLinuxPlugin extends ConnectivityPlatform { } Future _startListenConnectivity() async { - _client ??= createClient(); - await _client!.connect(); - _addConnectivity(_client!); - _client!.propertiesChanged.listen((properties) { - if (properties.contains('Connectivity')) { - _addConnectivity(_client!); - } - }); + try { + _client ??= createClient(); + await _client!.connect(); + _addConnectivity(_client!); + _client!.propertiesChanged.listen((properties) { + if (properties.contains('Connectivity')) { + _addConnectivity(_client!); + } + }); + } catch (e) { + // NetworkManager may not be installed or running on this Linux system. + // We emit ConnectivityResult.none to the stream to indicate no connectivity + // can be determined, rather than crashing the application. + _controller?.add([ConnectivityResult.none]); + } } void _addConnectivity(NetworkManagerClient client) { diff --git a/packages/connectivity_plus/connectivity_plus/test/connectivity_plus_linux_test.dart b/packages/connectivity_plus/connectivity_plus/test/connectivity_plus_linux_test.dart index b7ecf03cab..6bed064515 100644 --- a/packages/connectivity_plus/connectivity_plus/test/connectivity_plus_linux_test.dart +++ b/packages/connectivity_plus/connectivity_plus/test/connectivity_plus_linux_test.dart @@ -18,8 +18,9 @@ void main() { final linux = ConnectivityPlusLinuxPlugin(); linux.createClient = () { final client = MockNetworkManagerClient(); - when(client.connectivity) - .thenReturn(NetworkManagerConnectivityState.full); + when( + client.connectivity, + ).thenReturn(NetworkManagerConnectivityState.full); when(client.primaryConnectionType).thenReturn('bluetooth'); return client; }; @@ -33,8 +34,9 @@ void main() { final linux = ConnectivityPlusLinuxPlugin(); linux.createClient = () { final client = MockNetworkManagerClient(); - when(client.connectivity) - .thenReturn(NetworkManagerConnectivityState.full); + when( + client.connectivity, + ).thenReturn(NetworkManagerConnectivityState.full); when(client.primaryConnectionType).thenReturn('ethernet'); return client; }; @@ -48,8 +50,9 @@ void main() { final linux = ConnectivityPlusLinuxPlugin(); linux.createClient = () { final client = MockNetworkManagerClient(); - when(client.connectivity) - .thenReturn(NetworkManagerConnectivityState.full); + when( + client.connectivity, + ).thenReturn(NetworkManagerConnectivityState.full); when(client.primaryConnectionType).thenReturn('wireless'); return client; }; @@ -63,8 +66,9 @@ void main() { final linux = ConnectivityPlusLinuxPlugin(); linux.createClient = () { final client = MockNetworkManagerClient(); - when(client.connectivity) - .thenReturn(NetworkManagerConnectivityState.full); + when( + client.connectivity, + ).thenReturn(NetworkManagerConnectivityState.full); when(client.primaryConnectionType).thenReturn('vpn'); return client; }; @@ -78,8 +82,9 @@ void main() { final linux = ConnectivityPlusLinuxPlugin(); linux.createClient = () { final client = MockNetworkManagerClient(); - when(client.connectivity) - .thenReturn(NetworkManagerConnectivityState.full); + when( + client.connectivity, + ).thenReturn(NetworkManagerConnectivityState.full); when(client.primaryConnectionType).thenReturn('wireless,vpn'); return client; }; @@ -93,33 +98,66 @@ void main() { final linux = ConnectivityPlusLinuxPlugin(); linux.createClient = () { final client = MockNetworkManagerClient(); - when(client.connectivity) - .thenReturn(NetworkManagerConnectivityState.none); + when( + client.connectivity, + ).thenReturn(NetworkManagerConnectivityState.none); return client; }; - expect(linux.checkConnectivity(), - completion(equals([ConnectivityResult.none]))); + expect( + linux.checkConnectivity(), + completion(equals([ConnectivityResult.none])), + ); }); test('connectivity changes', () { final linux = ConnectivityPlusLinuxPlugin(); linux.createClient = () { final client = MockNetworkManagerClient(); - when(client.connectivity) - .thenReturn(NetworkManagerConnectivityState.full); + when( + client.connectivity, + ).thenReturn(NetworkManagerConnectivityState.full); when(client.primaryConnectionType).thenReturn('wireless'); when(client.propertiesChanged).thenAnswer((_) { - when(client.connectivity) - .thenReturn(NetworkManagerConnectivityState.none); + when( + client.connectivity, + ).thenReturn(NetworkManagerConnectivityState.none); return Stream.value(['Connectivity']); }); return client; }; expect( - linux.onConnectivityChanged, - emitsInOrder([ - [ConnectivityResult.wifi], - [ConnectivityResult.none] - ])); + linux.onConnectivityChanged, + emitsInOrder([ + [ConnectivityResult.wifi], + [ConnectivityResult.none], + ]), + ); + }); + + test('NetworkManager unavailable - checkConnectivity', () async { + final linux = ConnectivityPlusLinuxPlugin(); + linux.createClient = () { + final client = MockNetworkManagerClient(); + when( + client.connect(), + ).thenThrow(Exception('NetworkManager not available')); + return client; + }; + expect( + linux.checkConnectivity(), + completion(equals([ConnectivityResult.none])), + ); + }); + + test('NetworkManager unavailable - onConnectivityChanged', () { + final linux = ConnectivityPlusLinuxPlugin(); + linux.createClient = () { + final client = MockNetworkManagerClient(); + when( + client.connect(), + ).thenThrow(Exception('NetworkManager not available')); + return client; + }; + expect(linux.onConnectivityChanged, emits([ConnectivityResult.none])); }); }