From 3e19d6917ff245863be43a39a76daa7010ecda6f Mon Sep 17 00:00:00 2001
From: Matthias Nott <mnott@mnsoft.org>
Date: Wed, 01 Apr 2026 18:35:25 +0200
Subject: [PATCH] feat: auto-reconnect on network change (WiFi/cellular/VPN switch)
---
windows/flutter/generated_plugins.cmake | 1
windows/flutter/generated_plugin_registrant.cc | 3 +
ios/Podfile.lock | 6 +++
macos/Flutter/GeneratedPluginRegistrant.swift | 2 +
lib/services/mqtt_service.dart | 35 +++++++++++++++++
pubspec.lock | 24 ++++++++++++
pubspec.yaml | 1
7 files changed, 72 insertions(+), 0 deletions(-)
diff --git a/ios/Podfile.lock b/ios/Podfile.lock
index 251c69f..f5d7ae0 100644
--- a/ios/Podfile.lock
+++ b/ios/Podfile.lock
@@ -5,6 +5,8 @@
- bonsoir_darwin (0.0.1):
- Flutter
- FlutterMacOS
+ - connectivity_plus (0.0.1):
+ - Flutter
- device_info_plus (0.0.1):
- Flutter
- DKImagePickerController/Core (4.3.9):
@@ -70,6 +72,7 @@
DEPENDENCIES:
- audioplayers_darwin (from `.symlinks/plugins/audioplayers_darwin/darwin`)
- bonsoir_darwin (from `.symlinks/plugins/bonsoir_darwin/darwin`)
+ - connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`)
- device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
- file_picker (from `.symlinks/plugins/file_picker/ios`)
- Flutter (from `Flutter`)
@@ -95,6 +98,8 @@
:path: ".symlinks/plugins/audioplayers_darwin/darwin"
bonsoir_darwin:
:path: ".symlinks/plugins/bonsoir_darwin/darwin"
+ connectivity_plus:
+ :path: ".symlinks/plugins/connectivity_plus/ios"
device_info_plus:
:path: ".symlinks/plugins/device_info_plus/ios"
file_picker:
@@ -123,6 +128,7 @@
SPEC CHECKSUMS:
audioplayers_darwin: 835ced6edd4c9fc8ebb0a7cc9e294a91d99917d5
bonsoir_darwin: 29c7ccf356646118844721f36e1de4b61f6cbd0e
+ connectivity_plus: cb623214f4e1f6ef8fe7403d580fdad517d2f7dd
device_info_plus: 21fcca2080fbcd348be798aa36c3e5ed849eefbe
DKImagePickerController: 946cec48c7873164274ecc4624d19e3da4c1ef3c
DKPhotoGallery: b3834fecb755ee09a593d7c9e389d8b5d6deed60
diff --git a/lib/services/mqtt_service.dart b/lib/services/mqtt_service.dart
index e50adcf..841182d 100644
--- a/lib/services/mqtt_service.dart
+++ b/lib/services/mqtt_service.dart
@@ -5,6 +5,7 @@
import 'package:crypto/crypto.dart';
import 'package:bonsoir/bonsoir.dart';
+import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
import 'package:path_provider/path_provider.dart' as pp;
@@ -50,6 +51,8 @@
bool _intentionalClose = false;
String? _clientId;
String? _lastDiscoveredHost;
+ StreamSubscription? _connectivitySub;
+ List<ConnectivityResult>? _lastConnectivity;
StreamSubscription? _updatesSub;
// Message deduplication
@@ -103,6 +106,36 @@
_intentionalClose = false;
_setStatus(ConnectionStatus.connecting);
+
+ // Start listening for network changes (WiFi↔cellular, VPN connect/disconnect)
+ _connectivitySub ??= Connectivity().onConnectivityChanged.listen((results) {
+ if (_lastConnectivity != null && !_intentionalClose) {
+ final changed = results.length != _lastConnectivity!.length ||
+ !results.every((r) => _lastConnectivity!.contains(r));
+ if (changed) {
+ _mqttLog('MQTT: network changed: ${results.map((r) => r.name).join(",")} — forcing reconnect');
+ // Force disconnect and reconnect on new network
+ final client = _client;
+ if (client != null) {
+ _intentionalClose = true;
+ client.autoReconnect = false;
+ try { client.disconnect(); } catch (_) {}
+ _client = null;
+ _updatesSub?.cancel();
+ _updatesSub = null;
+ _intentionalClose = false;
+ }
+ _lastDiscoveredHost = null; // Clear cached discovery — subnet may have changed
+ connectedHost = null;
+ connectedVia = null;
+ _setStatus(ConnectionStatus.reconnecting);
+ Future.delayed(const Duration(milliseconds: 500), () {
+ if (!_intentionalClose) connect();
+ });
+ }
+ }
+ _lastConnectivity = results;
+ });
// Load trusted cert fingerprint for TOFU verification
if (_trustedFingerprint == null) await _loadTrustedFingerprint();
@@ -724,6 +757,8 @@
_intentionalClose = true;
_updatesSub?.cancel();
_updatesSub = null;
+ _connectivitySub?.cancel();
+ _connectivitySub = null;
try {
_client?.disconnect();
diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift
index 0a6522c..96555f3 100644
--- a/macos/Flutter/GeneratedPluginRegistrant.swift
+++ b/macos/Flutter/GeneratedPluginRegistrant.swift
@@ -7,6 +7,7 @@
import audioplayers_darwin
import bonsoir_darwin
+import connectivity_plus
import device_info_plus
import file_picker
import file_selector_macos
@@ -20,6 +21,7 @@
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
AudioplayersDarwinPlugin.register(with: registry.registrar(forPlugin: "AudioplayersDarwinPlugin"))
SwiftBonsoirPlugin.register(with: registry.registrar(forPlugin: "SwiftBonsoirPlugin"))
+ ConnectivityPlusPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlusPlugin"))
DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin"))
FilePickerPlugin.register(with: registry.registrar(forPlugin: "FilePickerPlugin"))
FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin"))
diff --git a/pubspec.lock b/pubspec.lock
index 60df114..f3d42d4 100644
--- a/pubspec.lock
+++ b/pubspec.lock
@@ -161,6 +161,22 @@
url: "https://pub.dev"
source: hosted
version: "1.19.1"
+ connectivity_plus:
+ dependency: "direct main"
+ description:
+ name: connectivity_plus
+ sha256: b8fe52979ff12432ecf8f0abf6ff70410b1bb734be1c9e4f2f86807ad7166c79
+ url: "https://pub.dev"
+ source: hosted
+ version: "7.1.0"
+ connectivity_plus_platform_interface:
+ dependency: transitive
+ description:
+ name: connectivity_plus_platform_interface
+ sha256: "3c09627c536d22fd24691a905cdd8b14520de69da52c7a97499c8be5284a32ed"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.1.0"
cross_file:
dependency: transitive
description:
@@ -608,6 +624,14 @@
url: "https://pub.dev"
source: hosted
version: "0.17.6"
+ nm:
+ dependency: transitive
+ description:
+ name: nm
+ sha256: "2c9aae4127bdc8993206464fcc063611e0e36e72018696cd9631023a31b24254"
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.5.0"
objective_c:
dependency: transitive
description:
diff --git a/pubspec.yaml b/pubspec.yaml
index ef8929f..c084091 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -33,6 +33,7 @@
crypto: ^3.0.7
push: ^3.3.3
flutter_app_badger: ^1.5.0
+ connectivity_plus: ^7.1.0
dev_dependencies:
flutter_test:
diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc
index 5b79aa2..4d9149a 100644
--- a/windows/flutter/generated_plugin_registrant.cc
+++ b/windows/flutter/generated_plugin_registrant.cc
@@ -8,6 +8,7 @@
#include <audioplayers_windows/audioplayers_windows_plugin.h>
#include <bonsoir_windows/bonsoir_windows_plugin_c_api.h>
+#include <connectivity_plus/connectivity_plus_windows_plugin.h>
#include <file_selector_windows/file_selector_windows.h>
#include <flutter_secure_storage_windows/flutter_secure_storage_windows_plugin.h>
#include <permission_handler_windows/permission_handler_windows_plugin.h>
@@ -20,6 +21,8 @@
registry->GetRegistrarForPlugin("AudioplayersWindowsPlugin"));
BonsoirWindowsPluginCApiRegisterWithRegistrar(
registry->GetRegistrarForPlugin("BonsoirWindowsPluginCApi"));
+ ConnectivityPlusWindowsPluginRegisterWithRegistrar(
+ registry->GetRegistrarForPlugin("ConnectivityPlusWindowsPlugin"));
FileSelectorWindowsRegisterWithRegistrar(
registry->GetRegistrarForPlugin("FileSelectorWindows"));
FlutterSecureStorageWindowsPluginRegisterWithRegistrar(
diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake
index 2d751af..2f3a067 100644
--- a/windows/flutter/generated_plugins.cmake
+++ b/windows/flutter/generated_plugins.cmake
@@ -5,6 +5,7 @@
list(APPEND FLUTTER_PLUGIN_LIST
audioplayers_windows
bonsoir_windows
+ connectivity_plus
file_selector_windows
flutter_secure_storage_windows
permission_handler_windows
--
Gitblit v1.3.1