fix(#291): background the app on back instead of killing it
Back at the root was calling SystemNavigator.pop(). That is not "background the app": on Android it calls finish() on the activity, which tears down the Flutter engine and drops the radio connection. Backing out and reopening therefore landed on a disconnected radio needing a fresh connect. Wrong call for the job, and worse than the behaviour it replaced. Backgrounding without finishing needs moveTaskToBack, which has no Flutter equivalent, so it goes over a method channel: - MainActivity exposes meshcore_open/app_lifecycle with a moveTaskToBack method, alongside the existing USB channel. - AppBackgrounder wraps it and is a no-op off Android, where programmatic backgrounding either does not apply (desktop windows close by their own chrome) or is forbidden (iOS). On those platforms back at the root stays unhandled rather than doing something destructive. - AppShell awaits it instead of calling SystemNavigator.pop(). The activity stays alive, so the connection survives and reopening returns to where the user was. flutter analyze clean, dart format clean, 469 tests pass.integration/290-306-335
parent
c8b36b056d
commit
c7fc4dca7b
@ -0,0 +1,33 @@
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
import 'platform_info.dart';
|
||||
|
||||
/// Sends the app to the background without tearing it down.
|
||||
///
|
||||
/// `SystemNavigator.pop()` is NOT this: on Android it calls `finish()` on the
|
||||
/// activity, which destroys the Flutter engine and drops the radio connection,
|
||||
/// so reopening the app finds itself disconnected. `moveTaskToBack` leaves the
|
||||
/// activity alive and simply hands focus back to whatever was in front before.
|
||||
class AppBackgrounder {
|
||||
static const MethodChannel _channel = MethodChannel(
|
||||
'meshcore_open/app_lifecycle',
|
||||
);
|
||||
|
||||
/// Returns true when the app was actually backgrounded.
|
||||
///
|
||||
/// Android only. Desktop windows are closed by their own chrome and iOS
|
||||
/// forbids programmatic backgrounding, so elsewhere this is a no-op and the
|
||||
/// caller should leave the press unhandled rather than act on it.
|
||||
static Future<bool> moveToBackground() async {
|
||||
if (!PlatformInfo.isAndroid) return false;
|
||||
try {
|
||||
return await _channel.invokeMethod<bool>('moveTaskToBack') ?? false;
|
||||
} on PlatformException catch (_) {
|
||||
// Surface nothing to the user: the fallback is simply that back does
|
||||
// nothing at the root, which is the pre-existing behaviour.
|
||||
return false;
|
||||
} on MissingPluginException catch (_) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in new issue