Repository navigation
Conversation
On Android the Flutter engine outlives the Activity (it is cached and never destroyed, so background Bluetooth keeps running). Swiping the app away and opening it again from the launcher attached a new Activity to the same Navigator, so the app reopened inside whatever screen was on top, e.g. Settings (OpenStrap#650). On iOS the process ends and a relaunch starts on the saved tab with nothing above it. MainActivity now notes whether the engine already existed before ensureEngine, and when it did, with no savedInstanceState and the launcher's ACTION_MAIN/CATEGORY_LAUNCHER intent, sends `relaunched` on a new openstrap/activity channel. The shell handles it by popping to the first route. The saved tab is kept: restoring it is intentional (restoredShellTab). System restores, notification taps and widget taps are untouched; rotation and dark mode never reach onCreate (configChanges).
Reviewer's GuideAPIDefine the Android-to-Dart relaunch method channel and handle relaunches by popping routes until the first route or a route that refuses to pop.
Uses of the APIDetect launcher relaunches on Android and notify Dart, then register and remove the shell’s navigator handler with its lifecycle.
TestsExercise launcher relaunch navigation, non-relaunch calls, missing navigators, and handler ownership replacement through the method channel.
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
|
Warning Review limit reachedYou've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. Next included review available in 34 minutes. View limit details
No actionable comments were generated in the recent review. Walkthrough
Merge Risk:
|
There was a problem hiding this comment.
Hey - I've found 1 issue
Prompt for AI Agents
Please address the comments from this code review:
## Individual Comments
### Comment 1
<location path="lib/platform/activity_relaunch.dart" line_range="24" />
<code_context>
+ _navigator = navigator;
+ channel.setMethodCallHandler((call) async {
+ if (call.method != 'relaunched') return;
+ _navigator?.call()?.popUntil((route) => route.isFirst);
+ });
+ }
</code_context>
<issue_to_address>
**Launcher relaunch freezes during active capture**
When the launcher relaunch arrives while CalmBreathing is running or ECG capture is busy, `popUntil` repeatedly tries to pop the guarded route, but its `PopScope` rejects the pop; the loop cannot advance while the route remains topmost, so the UI isolate hangs and the app freezes.
Close guarded screens asynchronously before popping the remaining routes, or otherwise make relaunch stack clearing advance without retrying a pop that `PopScope` rejects.
</issue_to_address>Sourcery assessment
Approval pending. 1 finding to address first.
Blocking findings: lib/platform/activity_relaunch.dart:24
popUntil pops without asking, so reopening from the launcher during a capture closed it. It now stops at a route whose PopScope says no; screens above it are still closed.
Fixes the Android half of #650.
Why
On Android, closing the app (swiping it from recents) and opening it again from the launcher lands back on whatever screen was on top, e.g. inside Settings. The cause is deliberate plumbing:
MainActivityattaches to the engineEdgeApplicationcaches and never destroys (shouldDestroyEngineWithHost() = false), so background Bluetooth keeps running. The Activity goes, the Dart side and its Navigator stay, and the next Activity attaches to the same stack.On iOS the process ends, so a relaunch starts fresh on the saved tab with nothing pushed on top. This makes Android do the same.
Scope note: the issue asks to always open on Home. Reopening on the last tab is intentional on both platforms (
restoredShellTab()/Prefs.shellTabV2, "so an upgrade lands where the user was"), so this PR keeps it and only closes the screens pushed above the tab. If you'd rather always land on Today, that's a one-line follow-up in_ShellState.What changes
MainActivity.onCreate: notes whether the cached engine already existed beforeensureEngine. When it did, there is nosavedInstanceState, and the intent is the launcher's (ACTION_MAIN+CATEGORY_LAUNCHER), it invokesrelaunchedon a newopenstrap/activitychannel.savedInstanceState != null): Android recreated the Activity and the user expects the screen back.SELECT_NOTIFICATION, flutter_local_notifications 18.0.1), home_widget taps (HOME_WIDGET_LAUNCH_ACTION), or our ownStrapWidgetstap (no action): those keep routing as before.onCreate(configChangesin the manifest).lib/platform/activity_relaunch.dart(new): handlesrelaunchedbypopUntil(isFirst)on the root navigator.stop()only clears its own owner's handler, so a replaced owner'sdispose(which runs after its successor'sinitState) can't silence the successor.lib/app.dart(_ShellState): listens ininitState, stops indispose. The tab is untouched. A running workout stays reachable through the existing_LiveSessionBarabove every tab.Tests
test/activity_relaunch_test.dart(4 widget tests, driven through the real method channel):relaunchedcloses every pushed screen back to the tab; any other method does nothing; a disposed owner is a no-op; a replaced owner'sstopdoesn't remove its successor's handler.flutter analyze: no issues.flutter test --concurrency=1: 7047 passed, 28 skipped, 1 failed. The failure ishealth_workout_export_delete_gate_test, which fails on any macOS host on current main and is fixed by Pin the Health Connect delete-gate test to Health Connect (fails on macOS) #685 (it pins that test to Health Connect). Two files (synthetic_oura_day_test,whoop_fallback_night_banked_test) reported "did not complete" in that run while other tests were running on the same machine; re-run on their own, all 10 of their tests pass.Verified on an Android emulator (API 37, arm64, debug build; no band paired, onboarding skipped). The Activity was ended with
am stack remove <task>, which destroys it while the process (and the cached engine) stays alive, the same state as swiping the app away while the foreground service keeps the process. The PID was checked before and after each step.main@ 69341a6MAIN/LAUNCHER,LaunchState: WARM, same PID)act=SELECT_NOTIFICATION(the action flutter_local_notifications uses; no payload)The Kotlin compiles and runs. Not covered: a real notification tap end to end (no band, so the app posted none; the third row exercises the intent gate it depends on), and a physical device.
🤖 Generated with Claude Code
Summary by Sourcery
Reset stale Android navigation screens on launcher relaunches without changing intentional tab restoration or other launch flows.
New Features:
Bug Fixes:
Enhancements:
Tests:
Summary by CodeRabbit