Matthias Nott
2026-04-01 3e19d6917ff245863be43a39a76daa7010ecda6f
feat: auto-reconnect on network change (WiFi/cellular/VPN switch)
7 files modified
changed files
ios/Podfile.lock patch | view | blame | history
lib/services/mqtt_service.dart patch | view | blame | history
macos/Flutter/GeneratedPluginRegistrant.swift patch | view | blame | history
pubspec.lock patch | view | blame | history
pubspec.yaml patch | view | blame | history
windows/flutter/generated_plugin_registrant.cc patch | view | blame | history
windows/flutter/generated_plugins.cmake patch | view | blame | history
ios/Podfile.lock
....@@ -5,6 +5,8 @@
55 - bonsoir_darwin (0.0.1):
66 - Flutter
77 - FlutterMacOS
8
+ - connectivity_plus (0.0.1):
9
+ - Flutter
810 - device_info_plus (0.0.1):
911 - Flutter
1012 - DKImagePickerController/Core (4.3.9):
....@@ -70,6 +72,7 @@
7072 DEPENDENCIES:
7173 - audioplayers_darwin (from `.symlinks/plugins/audioplayers_darwin/darwin`)
7274 - bonsoir_darwin (from `.symlinks/plugins/bonsoir_darwin/darwin`)
75
+ - connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`)
7376 - device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
7477 - file_picker (from `.symlinks/plugins/file_picker/ios`)
7578 - Flutter (from `Flutter`)
....@@ -95,6 +98,8 @@
9598 :path: ".symlinks/plugins/audioplayers_darwin/darwin"
9699 bonsoir_darwin:
97100 :path: ".symlinks/plugins/bonsoir_darwin/darwin"
101
+ connectivity_plus:
102
+ :path: ".symlinks/plugins/connectivity_plus/ios"
98103 device_info_plus:
99104 :path: ".symlinks/plugins/device_info_plus/ios"
100105 file_picker:
....@@ -123,6 +128,7 @@
123128 SPEC CHECKSUMS:
124129 audioplayers_darwin: 835ced6edd4c9fc8ebb0a7cc9e294a91d99917d5
125130 bonsoir_darwin: 29c7ccf356646118844721f36e1de4b61f6cbd0e
131
+ connectivity_plus: cb623214f4e1f6ef8fe7403d580fdad517d2f7dd
126132 device_info_plus: 21fcca2080fbcd348be798aa36c3e5ed849eefbe
127133 DKImagePickerController: 946cec48c7873164274ecc4624d19e3da4c1ef3c
128134 DKPhotoGallery: b3834fecb755ee09a593d7c9e389d8b5d6deed60
lib/services/mqtt_service.dart
....@@ -5,6 +5,7 @@
55 import 'package:crypto/crypto.dart';
66
77 import 'package:bonsoir/bonsoir.dart';
8
+import 'package:connectivity_plus/connectivity_plus.dart';
89 import 'package:flutter/foundation.dart';
910 import 'package:flutter/widgets.dart';
1011 import 'package:path_provider/path_provider.dart' as pp;
....@@ -50,6 +51,8 @@
5051 bool _intentionalClose = false;
5152 String? _clientId;
5253 String? _lastDiscoveredHost;
54
+ StreamSubscription? _connectivitySub;
55
+ List<ConnectivityResult>? _lastConnectivity;
5356 StreamSubscription? _updatesSub;
5457
5558 // Message deduplication
....@@ -103,6 +106,36 @@
103106
104107 _intentionalClose = false;
105108 _setStatus(ConnectionStatus.connecting);
109
+
110
+ // Start listening for network changes (WiFi↔cellular, VPN connect/disconnect)
111
+ _connectivitySub ??= Connectivity().onConnectivityChanged.listen((results) {
112
+ if (_lastConnectivity != null && !_intentionalClose) {
113
+ final changed = results.length != _lastConnectivity!.length ||
114
+ !results.every((r) => _lastConnectivity!.contains(r));
115
+ if (changed) {
116
+ _mqttLog('MQTT: network changed: ${results.map((r) => r.name).join(",")} — forcing reconnect');
117
+ // Force disconnect and reconnect on new network
118
+ final client = _client;
119
+ if (client != null) {
120
+ _intentionalClose = true;
121
+ client.autoReconnect = false;
122
+ try { client.disconnect(); } catch (_) {}
123
+ _client = null;
124
+ _updatesSub?.cancel();
125
+ _updatesSub = null;
126
+ _intentionalClose = false;
127
+ }
128
+ _lastDiscoveredHost = null; // Clear cached discovery — subnet may have changed
129
+ connectedHost = null;
130
+ connectedVia = null;
131
+ _setStatus(ConnectionStatus.reconnecting);
132
+ Future.delayed(const Duration(milliseconds: 500), () {
133
+ if (!_intentionalClose) connect();
134
+ });
135
+ }
136
+ }
137
+ _lastConnectivity = results;
138
+ });
106139
107140 // Load trusted cert fingerprint for TOFU verification
108141 if (_trustedFingerprint == null) await _loadTrustedFingerprint();
....@@ -724,6 +757,8 @@
724757 _intentionalClose = true;
725758 _updatesSub?.cancel();
726759 _updatesSub = null;
760
+ _connectivitySub?.cancel();
761
+ _connectivitySub = null;
727762
728763 try {
729764 _client?.disconnect();
macos/Flutter/GeneratedPluginRegistrant.swift
....@@ -7,6 +7,7 @@
77
88 import audioplayers_darwin
99 import bonsoir_darwin
10
+import connectivity_plus
1011 import device_info_plus
1112 import file_picker
1213 import file_selector_macos
....@@ -20,6 +21,7 @@
2021 func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
2122 AudioplayersDarwinPlugin.register(with: registry.registrar(forPlugin: "AudioplayersDarwinPlugin"))
2223 SwiftBonsoirPlugin.register(with: registry.registrar(forPlugin: "SwiftBonsoirPlugin"))
24
+ ConnectivityPlusPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlusPlugin"))
2325 DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin"))
2426 FilePickerPlugin.register(with: registry.registrar(forPlugin: "FilePickerPlugin"))
2527 FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin"))
pubspec.lock
....@@ -161,6 +161,22 @@
161161 url: "https://pub.dev"
162162 source: hosted
163163 version: "1.19.1"
164
+ connectivity_plus:
165
+ dependency: "direct main"
166
+ description:
167
+ name: connectivity_plus
168
+ sha256: b8fe52979ff12432ecf8f0abf6ff70410b1bb734be1c9e4f2f86807ad7166c79
169
+ url: "https://pub.dev"
170
+ source: hosted
171
+ version: "7.1.0"
172
+ connectivity_plus_platform_interface:
173
+ dependency: transitive
174
+ description:
175
+ name: connectivity_plus_platform_interface
176
+ sha256: "3c09627c536d22fd24691a905cdd8b14520de69da52c7a97499c8be5284a32ed"
177
+ url: "https://pub.dev"
178
+ source: hosted
179
+ version: "2.1.0"
164180 cross_file:
165181 dependency: transitive
166182 description:
....@@ -608,6 +624,14 @@
608624 url: "https://pub.dev"
609625 source: hosted
610626 version: "0.17.6"
627
+ nm:
628
+ dependency: transitive
629
+ description:
630
+ name: nm
631
+ sha256: "2c9aae4127bdc8993206464fcc063611e0e36e72018696cd9631023a31b24254"
632
+ url: "https://pub.dev"
633
+ source: hosted
634
+ version: "0.5.0"
611635 objective_c:
612636 dependency: transitive
613637 description:
pubspec.yaml
....@@ -33,6 +33,7 @@
3333 crypto: ^3.0.7
3434 push: ^3.3.3
3535 flutter_app_badger: ^1.5.0
36
+ connectivity_plus: ^7.1.0
3637
3738 dev_dependencies:
3839 flutter_test:
windows/flutter/generated_plugin_registrant.cc
....@@ -8,6 +8,7 @@
88
99 #include <audioplayers_windows/audioplayers_windows_plugin.h>
1010 #include <bonsoir_windows/bonsoir_windows_plugin_c_api.h>
11
+#include <connectivity_plus/connectivity_plus_windows_plugin.h>
1112 #include <file_selector_windows/file_selector_windows.h>
1213 #include <flutter_secure_storage_windows/flutter_secure_storage_windows_plugin.h>
1314 #include <permission_handler_windows/permission_handler_windows_plugin.h>
....@@ -20,6 +21,8 @@
2021 registry->GetRegistrarForPlugin("AudioplayersWindowsPlugin"));
2122 BonsoirWindowsPluginCApiRegisterWithRegistrar(
2223 registry->GetRegistrarForPlugin("BonsoirWindowsPluginCApi"));
24
+ ConnectivityPlusWindowsPluginRegisterWithRegistrar(
25
+ registry->GetRegistrarForPlugin("ConnectivityPlusWindowsPlugin"));
2326 FileSelectorWindowsRegisterWithRegistrar(
2427 registry->GetRegistrarForPlugin("FileSelectorWindows"));
2528 FlutterSecureStorageWindowsPluginRegisterWithRegistrar(
windows/flutter/generated_plugins.cmake
....@@ -5,6 +5,7 @@
55 list(APPEND FLUTTER_PLUGIN_LIST
66 audioplayers_windows
77 bonsoir_windows
8
+ connectivity_plus
89 file_selector_windows
910 flutter_secure_storage_windows
1011 permission_handler_windows