Matthias Nott
2026-03-24 deec1d6faf0361569121937701e5835f78d4cd9c
fix: clean force-reconnect on resume with intentionalClose flag
1 files modified
changed files
lib/services/mqtt_service.dart patch | view | blame | history
lib/services/mqtt_service.dart
....@@ -516,34 +516,22 @@
516516 case AppLifecycleState.resumed:
517517 if (_intentionalClose) break;
518518 _mqttLog('MQTT: app resumed, status=$_status');
519
- if (_status != ConnectionStatus.connected) {
520
- // Already knows it's disconnected — just reconnect
521
- connect();
522
- } else {
523
- // Thinks it's connected — verify by sending a ping command.
524
- // If the connection is dead, the publish will fail or we won't
525
- // get a pong back. Set a watchdog timer.
526
- _mqttLog('MQTT: sending health check...');
527
- _publish('pailot/control/in', {
528
- 'type': 'command',
529
- 'command': 'ping',
530
- 'msgId': DateTime.now().millisecondsSinceEpoch.toString(),
531
- 'ts': DateTime.now().millisecondsSinceEpoch,
532
- }, MqttQos.atLeastOnce);
533
- // If no pong within 3s, force reconnect
534
- Future.delayed(const Duration(seconds: 3), () {
535
- if (_status == ConnectionStatus.connected) {
536
- // Check if client is still actually connected
537
- final client = _client;
538
- if (client == null || client.connectionStatus?.state != MqttConnectionState.connected) {
539
- _mqttLog('MQTT: health check failed, reconnecting...');
540
- _client = null;
541
- _setStatus(ConnectionStatus.reconnecting);
542
- connect();
543
- }
544
- }
545
- });
519
+ // iOS kills TCP sockets during suspend. Always force a clean
520
+ // reconnect to avoid the "looks connected but dead" state.
521
+ final resumeClient = _client;
522
+ if (resumeClient != null) {
523
+ _intentionalClose = true; // Prevent _onDisconnected from cascading
524
+ resumeClient.autoReconnect = false;
525
+ resumeClient.disconnect();
526
+ _client = null;
527
+ _updatesSub?.cancel();
528
+ _updatesSub = null;
529
+ _intentionalClose = false;
546530 }
531
+ _setStatus(ConnectionStatus.reconnecting);
532
+ Future.delayed(const Duration(milliseconds: 300), () {
533
+ if (!_intentionalClose) connect();
534
+ });
547535 case AppLifecycleState.paused:
548536 break;
549537 default: