3

I am using GoRouter for navigation and GetX for multiple things, including updating the app's locale.

It is updating the language perfectly fine, but the problem is that somehow my navigation stack is getting reset after the call.

I've made a Screenvideo for a better understanding but you can also test it yourself here.

I update my locale by simply calling:

  Locale locale = Locale(languageCode);
  await Get.updateLocale(locale);

And my routing looks like this:

final rootNavigatorKey = GlobalKey<NavigatorState>();

class AppRouter {
  static final _shellNavigatorKey = GlobalKey<NavigatorState>();

  static final router = GoRouter(
    initialLocation: IntroView.path,
    debugLogDiagnostics: true,
    navigatorKey: rootNavigatorKey,
    routes: [
      ShellRoute(
        navigatorKey: _shellNavigatorKey,
        pageBuilder: (context, state, child) {
          return NoTransitionPage(
            child: ScaffoldView(
              child: child,
            ),
          );
        },
        routes: [
          GoRoute(
            name: ProjectsView.name,
            path: '/projects',
            parentNavigatorKey: _shellNavigatorKey,
            pageBuilder: (context, state) {
              return const FadePageTransition(
                page: ProjectsView(),
              );
            },
            routes: [
              GoRoute(
                parentNavigatorKey: rootNavigatorKey,
                path: ':projectTitle',
                pageBuilder: (context, state) {
                  return FadePageTransition(
                    page: ProjectDetailScaffold(
                      project: Projects.values.byName(
                        state.params['projectTitle']!,
                      ),
                    ),
                  );
                },
              ),
            ],
          ),

I am already passing the router as instance and I am using GetMaterialApp.router:

class App extends StatelessWidget {
  final GoRouter router;
  const App({
    super.key,
    required this.router,
  });

  @override
  Widget build(BuildContext context) {
    return MultiProvider(
      providers: [
       ...
      ],
      child: GetMaterialApp.router(
        routeInformationProvider: router.routeInformationProvider,
        routeInformationParser: router.routeInformationParser,
        routerDelegate: router.routerDelegate,
        translationsKeys: translationsKeys,
        locale: const Locale(LocaleConfiguration.baseLanguageCode),
        fallbackLocale: const Locale(LocaleConfiguration.baseLanguageCode),
      ),
    );
  }
}

What am I missing here?

Let me know if you need any more info!

Chris
  • 1,828
  • 6
  • 40
  • 108
  • Why are you using GoRouter when there is a navigator offered by GetX ? – Deep Shah May 20 '23 at 13:35
  • @DeepShah I use GoRouter because it seems to be the best router for flutter web. At least from my experience. Especially useful for things like nested routing & parameterized routing – Chris May 20 '23 at 20:32
  • @RandalSchwartz I agree. GetX should definitely be used with caution. However I think it comes in quite handy for some helper functions. I like the way translation works and I never had any problems until now. – Chris May 20 '23 at 20:33
  • You might have to use named routing in GetX. For parameterizing, you can pass query params using GetX. – Deep Shah May 22 '23 at 16:22

2 Answers2

0

For now I found a workaround that is not very clean but it works for me:

Right after I call await Get.updateLocale(locale); I am calling this:

  if (mounted) {
    while (context.canPop()) {
      context.pop();
    }
    context.pushReplacement('/projects/localizeIt');
  }

The problem is that GoRouter loses the state because updateLocale forces the app to rebuild. As workaround, I am popping all views and going to the location where I am calling updateLocale from.

Like I said, this is no ideal and more of a workaround. If anyone know a better solution, please let me know.

Chris
  • 1,828
  • 6
  • 40
  • 108
0

Your current workaround involves essentially restarting the navigation stack and redirecting to a specific page after changing the locale, and while this might work, it does seem a bit rough and could potentially lead to user experience issues.

In Flutter, the locale change does indeed cause a rebuild of the widget tree. However, it seems unusual that the navigation stack is getting cleared in this process. Generally, the navigation stack should be maintained across rebuilds unless explicitly cleared or modified.

The issue could potentially be related to how GetX and GoRouter interact with each other.

  • GetX is a state management solution, and it seems to be causing a full rebuild of the widget tree when the locale changes.
  • GoRouter on the other hand, manages navigation and should maintain its state across widget tree rebuilds.

So you can try and ensure the GoRouter instance is not being recreated on each rebuild. The router should be created once and maintained for the duration of the app's lifetime.

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  final router = AppRouter.router;

  @override
  Widget build(BuildContext context) {
    return MaterialApp.router(
      routerDelegate: router.routerDelegate,
      routeInformationParser: router.routeInformationParser,
    );
  }
}

And consider separating your localization and routing concerns.
Instead of calling Get.updateLocale directly, you could create a separate service that wraps this call and also preserves your navigation state. This service could listen for locale changes and update the locale without causing a full rebuild of the widget tree.

class LocaleService extends GetxController {
  void updateLocale(String languageCode) async {
    Locale locale = Locale(languageCode);
    Get.updateLocale(locale);
    update();
  }
}

class LocaleSwitcher extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return GetBuilder<LocaleService>(
      init: LocaleService(),
      builder: (controller) {
        return DropdownButton<String>(
          value: Get.locale?.languageCode,
          items: <String>['en', 'fr', 'de'].map((String value) {
            return DropdownMenuItem<String>(
              value: value,
              child: Text(value),
            );
          }).toList(),
          onChanged: (newValue) {
            controller.updateLocale(newValue!);
          },
        );
      },
    );
  }
}

LocaleService is a service that wraps the Get.updateLocale call.
LocaleSwitcher is a widget that uses GetBuilder to listen for changes in LocaleService, and rebuilds itself when the locale is updated.
This way, only the LocaleSwitcher widget is rebuilt when the locale changes, not the entire widget tree.


Or: consider using GetMaterialApp which integrates GetX with the Flutter MaterialApp widget (as illustrated here). This might help with preserving the navigation state when changing locale.
See also "What is the difference between GetMaterialApp of GetX and MaterialApp of Flutter?".
That would also mean you are relying on GetX for routing instead of GoRouter. If you want to stick with GoRouter for routing, the first two points are enough.
However, if you are okay with transitioning to GetX for routing, using GetMaterialAp.

VonC
  • 1,262,500
  • 529
  • 4,410
  • 5,250
  • Thanks for your help! I already implemented the first solution. I updated my question. And I just tried the 2nd approach but this is giving me the same result. The router is still reset. – Chris May 23 '23 at 08:37
  • @Chris OK. The underlying problem might lie within the interaction between `GetX` and `GoRouter` when the widget tree is rebuilt. – VonC May 23 '23 at 08:54