Flutter/GoRouter: reset StatefullShellRoute branches to initial tabs

905 Views Asked by At

I'm using GoRouter to handle navigation in my project. To manage navigation app bar I use StatefulShellRoute:

List<RouteBase> get routes => [
        
        StatefulShellRoute.indexedStack(
          builder: (context, state, navigationShell) =>
              TabBarPage(navigationShell: navigationShell),
          branches: [
            StatefulShellBranch(
              navigatorKey: _rootNavigatorHome,
              initialLocation: HomePage.path,
              routes: [
                _homeTab,
              ],
            ),
            StatefulShellBranch(
              navigatorKey: _rootNavigatorCatalog,
              initialLocation: CatalogPage.path,
              routes: [
                _catalogTab,
              ],
            ),
            StatefulShellBranch(
              navigatorKey: _rootNavigatorCart,
              routes: [
                _cartTab,
              ],
            ),
            StatefulShellBranch(
              navigatorKey: _rootNavigatorOrders,
              routes: [
                _ordersTab,
              ],
            ),
            StatefulShellBranch(
              navigatorKey: _rootNavigatorProfile,
              routes: [
                _profileTab,
              ],
            ),
          ],
        ),
      ];

Sometimes app user changes current office. Whenever the user changes office I need all my tabs (branches) go to its initial routes. I handle this change with riverpod:

   ref.listen(currentOfficeProvider, (prev, current) {
      if (prev == current) return;
      final branches = navigationShell.route.branches;
      for (final branch in branches) {
        branch.navigatorKey.currentState?.popUntil((route) => route.isFirst);
      }
  });

But every time I got this error:

Unhandled Exception: 'package:go_router/src/match.dart': Failed assertion: line 265 pos 12: 'index != -1': is not true.
#0      _AssertionError._doThrowNew (dart:core-patch/errors_patch.dart:51:61)
#1      _AssertionError._throwNew (dart:core-patch/errors_patch.dart:40:5)
#2      RouteMatchList.remove (package:go_router/src/match.dart:265:12)
#3      GoRouterDelegate._handlePopPageWithRouteMatch (package:go_router/src/delegate.dart:105:49)
#4      _PagePopContext.onPopPage (package:go_router/src/builder.dart:572:35)
#5      NavigatorState.pop (package:flutter/src/widgets/navigator.dart:5045:28)
#6      NavigatorState.popUntil (package:flutter/src/widgets/navigator.dart:5085:7)
#7      TabBarPage.build.<anonymous closure> (package:yulsun_app/feachers/tab_bar/tab_bar_page.dart:59:43)
#8      _RootZone.runBinaryGuarded (dart:async/zone.dart:1606:10)
#9      ProviderElementBase._notifyListeners.<anonymous closure> (package:riverpod/src/framework/element.dart:537:24)
#10     ResultData.map (package:riverpod/src/result.dart:74:16)
#11     ProviderElementBase._notifyListeners (package:riverpod/src/framework/element.dart:534:14)
#12     ProviderElementBase._performBuild (package:riverpod/src/framework/element.dart:374:7)
#13     ProviderElementBase.flush (package:riverpod/src/framework/element.dart:325:7)
#14     _ProviderScheduler._performRefresh (package:riverpod/src/framework/scheduler.dart:62:41)
#15     _ProviderScheduler._task (package:riverpod/src/framework/scheduler.dart:50:5)
#16     _UncontrolledProviderScopeElement.build (package:flutter_riverpod/src/framework.dart:392:12)
#17     ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5297:15)
#18     Element.rebuild (package:flutter/src/widgets/framework.dart:5016:7)
#19     BuildOwner.buildScope (package:flutter/src/widgets/framework.dart:2779:19)
#20     WidgetsBinding.drawFrame (package:flutter/src/widgets/binding.dart:916:21)
#21     RendererBinding._handlePersistentFrameCallback (package:flutter/src/rendering/binding.dart:360:5)
#22     SchedulerBinding._invokeFrameCallback (package:flutter/src/scheduler/binding.dart:1297:15)
#23     SchedulerBinding.handleDrawFrame (package:flutter/src/scheduler/binding.dart:1227:9)
#24     SchedulerBinding._handleDrawFrame (package:flutter/src/scheduler/binding.dart:1085:5)
#25     _invoke (dart:ui/hooks.dart:170:13)
#26     PlatformDispatcher._drawFrame (dart:ui/platform_dispatcher.dart:401:5)
#27     _drawFrame (dart:ui/hooks.dart:140:31)

What's wrong? I guess Flutter is unable to redraw screens that are not currently presented. But in this case how to reset all the branches?

1

There are 1 best solutions below

1
On

This is how I solved it:

 ref.listen(currentOfficeProvider, (prev, current) {
      if (prev == current) return;
      final branches = navigationShell.route.branches;
      for (final branch in branches) {
        try {
          branch.navigatorKey.currentState?.popUntil((route) => route.isFirst);
        } catch (e) {
          continue;
        }
      }
    });

As I understood, the reason was the following: the loop was getting every branch and try to go to the first route of each.

The routes where the current route was matching the first route for some reason were still trying to pop to the first route and this was throwing exception. The method branch.navigatorKey.currentState?.canPop() for some reason returns true for each of these routes.

I will be grateful if you give me any explanations for this behavior.