Repository: flutter
Issue: Go Router nested navigation state is not fully restored
Labels: platform-android,package,has reproducible steps,P2,p: go_router,team-go_router,triaged-go_router,found in release: 3.24,found in release: 3.25

### What package does this bug report belong to?

go_router

### What target platforms are you seeing this bug on?

Android

### Have you already upgraded your packages?

Yes

### Dependency versions

<details><summary>pubspec.lock</summary>

```lock
# Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile
packages:
  async:
    dependency: transitive
    description:
      name: async
      sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c"
      url: "https://pub.dev"
    source: hosted
    version: "2.11.0"
  boolean_selector:
    dependency: transitive
    description:
      name: boolean_selector
      sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66"
      url: "https://pub.dev"
    source: hosted
    version: "2.1.1"
  characters:
    dependency: transitive
    description:
      name: characters
      sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605"
      url: "https://pub.dev"
    source: hosted
    version: "1.3.0"
  clock:
    dependency: transitive
    description:
      name: clock
      sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf
      url: "https://pub.dev"
    source: hosted
    version: "1.1.1"
  collection:
    dependency: transitive
    description:
      name: collection
      sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a
      url: "https://pub.dev"
    source: hosted
    version: "1.18.0"
  cupertino_icons:
    dependency: "direct main"
    description:
      name: cupertino_icons
      sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6
      url: "https://pub.dev"
    source: hosted
    version: "1.0.8"
  fake_async:
    dependency: transitive
    description:
      name: fake_async
      sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78"
      url: "https://pub.dev"
    source: hosted
    version: "1.3.1"
  flutter:
    dependency: "direct main"
    description: flutter
    source: sdk
    version: "0.0.0"
  flutter_lints:
    dependency: "direct dev"
    description:
      name: flutter_lints
      sha256: "9e8c3858111da373efc5aa341de011d9bd23e2c5c5e0c62bccf32438e192d7b1"
      url: "https://pub.dev"
    source: hosted
    version: "3.0.2"
  flutter_test:
    dependency: "direct dev"
    description: flutter
    source: sdk
    version: "0.0.0"
  flutter_web_plugins:
    dependency: transitive
    description: flutter
    source: sdk
    version: "0.0.0"
  go_router:
    dependency: "direct main"
    description:
      name: go_router
      sha256: "48d03a1e4887b00fe622695139246e3c778ac814eeb32421467b56d23fa64034"
      url: "https://pub.dev"
    source: hosted
    version: "14.2.6"
  leak_tracker:
    dependency: transitive
    description:
      name: leak_tracker
      sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a"
      url: "https://pub.dev"
    source: hosted
    version: "10.0.4"
  leak_tracker_flutter_testing:
    dependency: transitive
    description:
      name: leak_tracker_flutter_testing
      sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8"
      url: "https://pub.dev"
    source: hosted
    version: "3.0.3"
  leak_tracker_testing:
    dependency: transitive
    description:
      name: leak_tracker_testing
      sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3"
      url: "https://pub.dev"
    source: hosted
    version: "3.0.1"
  lints:
    dependency: transitive
    description:
      name: lints
      sha256: cbf8d4b858bb0134ef3ef87841abdf8d63bfc255c266b7bf6b39daa1085c4290
      url: "https://pub.dev"
    source: hosted
    version: "3.0.0"
  logging:
    dependency: transitive
    description:
      name: logging
      sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340"
      url: "https://pub.dev"
    source: hosted
    version: "1.2.0"
  matcher:
    dependency: transitive
    description:
      name: matcher
      sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb
      url: "https://pub.dev"
    source: hosted
    version: "0.12.16+1"
  material_color_utilities:
    dependency: transitive
    description:
      name: material_color_utilities
      sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a"
      url: "https://pub.dev"
    source: hosted
    version: "0.8.0"
  meta:
    dependency: transitive
    description:
      name: meta
      sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136"
      url: "https://pub.dev"
    source: hosted
    version: "1.12.0"
  path:
    dependency: transitive
    description:
      name: path
      sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af"
      url: "https://pub.dev"
    source: hosted
    version: "1.9.0"
  sky_engine:
    dependency: transitive
    description: flutter
    source: sdk
    version: "0.0.99"
  source_span:
    dependency: transitive
    description:
      name: source_span
      sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c"
      url: "https://pub.dev"
    source: hosted
    version: "1.10.0"
  stack_trace:
    dependency: transitive
    description:
      name: stack_trace
      sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b"
      url: "https://pub.dev"
    source: hosted
    version: "1.11.1"
  stream_channel:
    dependency: transitive
    description:
      name: stream_channel
      sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7
      url: "https://pub.dev"
    source: hosted
    version: "2.1.2"
  string_scanner:
    dependency: transitive
    description:
      name: string_scanner
      sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde"
      url: "https://pub.dev"
    source: hosted
    version: "1.2.0"
  term_glyph:
    dependency: transitive
    description:
      name: term_glyph
      sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84
      url: "https://pub.dev"
    source: hosted
    version: "1.2.1"
  test_api:
    dependency: transitive
    description:
      name: test_api
      sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f"
      url: "https://pub.dev"
    source: hosted
    version: "0.7.0"
  vector_math:
    dependency: transitive
    description:
      name: vector_math
      sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803"
      url: "https://pub.dev"
    source: hosted
    version: "2.1.4"
  vm_service:
    dependency: transitive
    description:
      name: vm_service
      sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec"
      url: "https://pub.dev"
    source: hosted
    version: "14.2.1"
sdks:
  dart: ">=3.4.1 <4.0.0"
  flutter: ">=3.19.0"
```

</details>


### Steps to reproduce

1. Enable 'Don't keep activities" flag on Android device.
2. Run the code sample on this device.
3. On SignUp page, tap on "Go to Bottom Navigation" button.
4. On Home page tab, tap on "Open nested page" button, so that "Nested home page" is opened.
5. Switch to the second tab via bottom menu.
6. Tap on "Open nested page" button, so that "Nested second tab page" is opened.
7. Tap on the third tab via bottom menu.
8. Put the app to background. Due to 'Don't keep activities" flag the process will be killed.
9. Bring the app back, so that the app restored its state.
10. The third tab is opened.

### Expected results

When switching to the first tab via bottom menu, "Nested home page" is shown, meaning that navigation state of the first StatefulShellBranch is correctly restored.

### Actual results

When switching to the first tab via bottom menu, "Home page" is shown, meaning that navigation state of the first StatefulShellBranch is lost.

### Code sample

<details open><summary>Code sample</summary>

```dart
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';

////////////////////////////////////////////////////////////

void main() {
  NavigationHelper.instance;
  runApp(const App());
}

class App extends StatelessWidget {
  const App({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp.router(
      debugShowCheckedModeBanner: false,
      routerConfig: NavigationHelper.router,
      restorationScopeId: 'app',
    );
  }
}

////////////////////////////////////////////////////////////

const String signUpPageName = 'signup';
const String signUpPagePath = '/$signUpPageName';

class SignUpPage extends StatelessWidget {
  const SignUpPage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Sign Up page'),
      ),
      body: Center(
        child: Column(
          children: [
            const Text("Sign Up page"),
            ElevatedButton(
              onPressed: _onBtnClick,
              child: const Text("Go to Bottom Navigation"),
            )
          ],
        ),
      ),
    );
  }

  void _onBtnClick() {
    NavigationHelper.router.goNamed(homePageName);
  }
}

////////////////////////////////////////////////////////////

class BottomNavigationPage extends StatelessWidget {
  const BottomNavigationPage({
    super.key,
    required this.child,
  });

  final StatefulNavigationShell child;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Bottom Navigator Page'),
      ),
      body: SafeArea(
        child: child,
      ),
      bottomNavigationBar: BottomNavigationBar(
        type: BottomNavigationBarType.fixed,
        currentIndex: child.currentIndex,
        onTap: (index) {
          child.goBranch(
            index,
            initialLocation: index == child.currentIndex,
          );
        },
        items: const [
          BottomNavigationBarItem(
            icon: Icon(Icons.home),
            label: 'home',
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.move_up),
            label: 'second',
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.settings),
            label: 'third',
          ),
        ],
      ),
    );
  }
}

////////////////////////////////////////////////////////////

const String homePageName = 'home';
const String homePagePath = '/$homePageName';

class HomePage extends StatelessWidget {
  const HomePage({super.key});

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Column(
        children: [
          const Text("Home page"),
          ElevatedButton(
            onPressed: _onInternalBtnClick,
            child: const Text("Open nested page"),
          ),
        ],
      ),
    );
  }

  void _onInternalBtnClick() {
    NavigationHelper.router.pushNamed(nestedHomePageName);
  }
}

////////////////////////////////////////////////////////////

const String nestedHomePageName = 'nested_home';
const String nestedHomePagePath = nestedHomePageName;

class NestedHomePage extends StatelessWidget {
  const NestedHomePage({super.key});

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Column(
        children: [
          const Text("Nested home page"),
          ElevatedButton(
            onPressed: () {
              _onBtnClick(context);
            },
            child: const Text("Next page"),
          ),
        ],
      ),
    );
  }

  void _onBtnClick(BuildContext context) {
    GoRouter.of(context).pushNamed(nestedHomePageName);
  }
}

////////////////////////////////////////////////////////////

const String secondTabPageName = 'second_tab';
const String secondTabPagePath = '/$secondTabPageName';

class SecondTabPage extends StatelessWidget {
  const SecondTabPage({super.key});

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Column(
        children: [
          const Text("SecondTab page"),
          ElevatedButton(
            onPressed: _onInternalBtnClick,
            child: const Text("Open nested page"),
          ),
        ],
      ),
    );
  }

  void _onInternalBtnClick() {
    NavigationHelper.router.pushNamed(nestedSecondTabPageName);
  }
}

////////////////////////////////////////////////////////////

const String nestedSecondTabPageName = 'nested_second_tab';
const String nestedSecondTabPagePath = nestedSecondTabPageName;

class NestedSecondTabPage extends StatelessWidget {
  const NestedSecondTabPage({super.key});

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Column(
        children: [
          const Text("Nested Second Tab page"),
          ElevatedButton(
            onPressed: () {
              _onBtnClick(context);
            },
            child: const Text("Next page"),
          ),
        ],
      ),
    );
  }

  void _onBtnClick(BuildContext context) {
    GoRouter.of(context).pushNamed(nestedSecondTabPageName);
  }
}

////////////////////////////////////////////////////////////

const String thirdTabPageName = 'third_tab';
const String thirdTabPagePath = '/$thirdTabPageName';

class ThirdTabPage extends StatelessWidget {
  const ThirdTabPage({super.key});

  @override
  Widget build(BuildContext context) {
    return const Center(
      child: Text("Third Tab page"),
    );
  }
}

////////////////////////////////////////////////////////////

class NavigationHelper {
  static final GlobalKey<NavigatorState> parentNavigatorKey =
  GlobalKey<NavigatorState>();
  static final GlobalKey<NavigatorState> homeTabNavigatorKey =
  GlobalKey<NavigatorState>();
  static final GlobalKey<NavigatorState> secondTabNavigatorKey =
  GlobalKey<NavigatorState>();
  static final GlobalKey<NavigatorState> thirdTabNavigatorKey =
  GlobalKey<NavigatorState>();

  static final NavigationHelper _instance = NavigationHelper._internal();

  static NavigationHelper get instance => _instance;

  factory NavigationHelper() {
    return _instance;
  }

  static late GoRouter router;

  NavigationHelper._internal() {
    final routes = [
      GoRoute(
        name: signUpPageName,
        path: signUpPagePath,
        builder: (
            BuildContext context,
            GoRouterState state,
            ) {
          return const SignUpPage();
        },
      ),
      StatefulShellRoute.indexedStack(
        parentNavigatorKey: parentNavigatorKey,
        restorationScopeId: 'bottom_bar_route',
        branches: [
          StatefulShellBranch(
            navigatorKey: homeTabNavigatorKey,
            restorationScopeId: homePageName,
            routes: [
              GoRoute(
                name: homePageName,
                path: homePagePath,
                routes: [
                  GoRoute(
                    name: nestedHomePageName,
                    path: nestedHomePagePath,
                    builder: (context, GoRouterState state) {
                      return const NestedHomePage();
                    },
                  ),
                ],
                builder: (context, GoRouterState state) {
                  return const HomePage();
                },
              ),
            ],
          ),
          StatefulShellBranch(
            navigatorKey: secondTabNavigatorKey,
            restorationScopeId: secondTabPageName,
            routes: [
              GoRoute(
                name: secondTabPageName,
                path: secondTabPagePath,
                builder: (context, state) {
                  return const SecondTabPage();
                },
                routes: [
                  GoRoute(
                    name: nestedSecondTabPageName,
                    path: nestedSecondTabPagePath,
                    builder: (context, GoRouterState state) {
                      return const NestedSecondTabPage();
                    },
                  ),
                ],
              ),
            ],
          ),
          StatefulShellBranch(
            restorationScopeId: thirdTabPageName,
            navigatorKey: thirdTabNavigatorKey,
            routes: [
              GoRoute(
                name: thirdTabPageName,
                path: thirdTabPagePath,
                builder: (context, state) {
                  return const ThirdTabPage();
                },
              ),
            ],
          ),
        ],
        pageBuilder: (
            BuildContext context,
            GoRouterState state,
            StatefulNavigationShell navigationShell,
            ) {
          return MaterialPage(
            key: state.pageKey,
            child: BottomNavigationPage(
              child: navigationShell,
            ),
            restorationId: 'bottom_bar_page',
          );
        },
      ),
    ];

    router = GoRouter(
      navigatorKey: parentNavigatorKey,
      initialLocation: signUpPagePath,
      routes: routes,
      debugLogDiagnostics: true,
      restorationScopeId: 'go_router',
    );
  }
}
```

</details>


### Screenshots or Videos

<details open>
<summary>Screenshots / Video demonstration</summary>

https://drive.google.com/file/d/1-mGtMUa5krlO2aSdvyZthoHdnoyyLM2t/view?usp=sharing

</details>


### Logs

<details open><summary>Logs</summary>

```console
[Paste your logs here]
```

</details>


### Flutter Doctor output

<details open><summary>Doctor output</summary>

```console
[√] Flutter (Channel stable, 3.22.1, on Microsoft Windows [Version 10.0.22631.4037], locale en-US)
[√] Windows Version (Installed version of Windows is version 10 or higher)
[!] Android toolchain - develop for Android devices (Android SDK version 34.0.0)
    X cmdline-tools component is missing
      Run `path/to/sdkmanager --install "cmdline-tools;latest"`
      See https://developer.android.com/studio/command-line for more details.
    X Android license status unknown.
      Run `flutter doctor --android-licenses` to accept the SDK licenses.
      See https://flutter.dev/docs/get-started/install/windows#android-setup for more details.
[√] Chrome - develop for the web
[X] Visual Studio - develop Windows apps
    X Visual Studio not installed; this is necessary to develop Windows apps.
      Download at https://visualstudio.microsoft.com/downloads/.
      Please install the "Desktop development with C++" workload, including all of its default components
[√] Android Studio (version 2024.1)
[√] Connected device (4 available)
[√] Network resources
```

</details>


I did some investigations on this issue and found a possible reason and fix. `StatefulNavigationShellState `stores the state of the nested navigation. To restore it after state restoration on Android, it saves the nested navigation stack in `_RestorableRouteMatchList`:
```
  final Map<StatefulShellBranch, _RestorableRouteMatchList> _branchLocations =
      <StatefulShellBranch, _RestorableRouteMatchList>{};
```
When `_RestorableRouteMatchList `is created, it is supposed to be registered as restorable:
```
  _RestorableRouteMatchList _branchLocation(StatefulShellBranch branch,
      [bool register = true]) {
    return _branchLocations.putIfAbsent(branch, () {
      final _RestorableRouteMatchList branchLocation =
          _RestorableRouteMatchList(_router.configuration);
      if (register) {
        registerForRestoration(
            branchLocation, _branchLocationRestorationScopeId(branch));
      }
      return branchLocation;
    });
  }
```

Normally `registerForRestoration` should be called for all branches. But it doesn't happen to the initial branch. This happens because `_branchLocation(branch, false)` is called inside `_updateCurrentBranchStateFromWidget()`, which is called from `initState` method:
```
  @override
  void initState() {
    super.initState();
    _updateCurrentBranchStateFromWidget();
  }
```
```
void _updateCurrentBranchStateFromWidget() {
...
    final _RestorableRouteMatchList branchLocation =
        _branchLocation(branch, false);
...
}
```
After that _branchLocation is called in `restoreState` for each branch:
```
  @override
  void restoreState(RestorationBucket? oldBucket, bool initialRestore) {
    route.branches.forEach(_branchLocation);
  }
```
At this point `registerForRestoration` is not called for the active branch, because it was already initialized earlier, during `initState`.

I suggest the following fix to this logic:
```
  _RestorableRouteMatchList _branchLocation(StatefulShellBranch branch,
      [bool register = true]) {
    final branchLocation = _branchLocations.putIfAbsent(branch, () {
      return _RestorableRouteMatchList(_router.configuration);
    });

    if (register) {
      registerForRestoration(
          branchLocation, _branchLocationRestorationScopeId(branch));
    }

    return branchLocation;
  }
```
This way `registerForRestoration` is properly called once for each branch. I have tested this fix and it worked in my code sample.