简体   繁体   中英

In Flutter, Getx package unknownRoute not working

I'm starting to learn Getx in flutter, and using navigation.

I want to set unknownRoute, in case that there is a typo etc in the namedroute, so the app should go to a default page.

I do like this:

 return GetMaterialApp(
        title: 'Named navigation',
        unknownRoute: GetPage(name: '/notfound', page: () => UnknownRoutePage()),
        initialRoute: '/', // this defines with route will be opened first

        getPages: [
          GetPage(name: '/', page: () => MyNavigationNamed()),
          GetPage(name: '/second', page: () => SecondScreenNamed()),
          GetPage(name: '/third', page: () => ThirdParametersScreenNamed()),
          GetPage(
              name: '/third_with_built_param/:someValue',
              page: () => ThirdParametersScreenNamed()),
        ],

I have the widget:

class UnknownRoutePage extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        body: Container(child: Text('UNKNOWN ROUTE')));
  }

}

However, then when I try to test it by making a mistake in the route name, like this:

 ElevatedButton(
                  onPressed: () {
                    Get.toNamed(
                      '/s');     //THIS IS A DUMMY INCORRECT NAME TO TESTING
                  },
                  child: Text('Error in route name, goes to defalt set above.'),
                ),

I expect my UnknownRoutePage() to open.

However I get this message:

The following assertion was thrown building Directionality(textDirection: ltr):
'package:flutter/src/widgets/framework.dart': Failed assertion: line 5033 pos 14: '_dependents.isEmpty': is not true.


Either the assertion indicates an error in the framework itself, or we should provide substantially more information in this error message to help you determine and fix the underlying cause.
In either case, please report this assertion by filing a bug on GitHub:
  https://github.com/flutter/flutter/issues/new?template=2_bug.md

The relevant error-causing widget was: 
  Directionality file:///Users/reuvenberman/Developer/flutter/.pub-cache/hosted/pub.dartlang.org/get-4.3.8/lib/get_navigation/src/root/get_material_app.dart:217:12
When the exception was thrown, this was the stack: 
#2      InheritedElement.debugDeactivated.<anonymous closure> (package:flutter/src/widgets/framework.dart:5033:14)
#3      InheritedElement.debugDeactivated (package:flutter/src/widgets/framework.dart:5035:6)
#4      _InactiveElements._deactivateRecursively.<anonymous closure> (package:flutter/src/widgets/framework.dart:1869:15)
#5      _InactiveElements._deactivateRecursively (package:flutter/src/widgets/framework.dart:1871:6)
#6      ComponentElement.visitChildren (package:flutter/src/widgets/framework.dart:4628:14)
...
====================================================================================================

======== Exception caught by widgets library =======================================================
The following assertion was thrown while finalizing the widget tree:
Duplicate GlobalKey detected in widget tree.

The following GlobalKey was specified multiple times in the widget tree. This will lead to parts of the widget tree being truncated unexpectedly, because the second time a key is seen, the previous instance is moved to the new location. The key was:
- [LabeledGlobalKey<NavigatorState>#56deb Key Created by default]
This was determined by noticing that after the widget with the above global key was moved out of its previous parent, that previous parent never updated during this frame, meaning that it either did not update at all or updated before the widget was moved, in either case implying that it still thinks that it should have a child with that global key.
The specific parent that did not update after having one or more children forcibly removed due to GlobalKey reparenting is:
- Directionality(textDirection: ltr)
A GlobalKey can only be specified on one widget at a time in the widget tree.
When the exception was thrown, this was the stack: 
#0      BuildOwner.finalizeTree.<anonymous closure> (package:flutter/src/widgets/framework.dart:2900:15)
#1      BuildOwner.finalizeTree (package:flutter/src/widgets/framework.dart:2925:8)
#2      WidgetsBinding.drawFrame (package:flutter/src/widgets/binding.dart:877:19)
#3      RendererBinding._handlePersistentFrameCallback (package:flutter/src/rendering/binding.dart:328:5)
#4      SchedulerBinding._invokeFrameCallback (package:flutter/src/scheduler/binding.dart:1144:15)
...

Why isn't this working?

Thanks

I am facing the same problem and also could not find any information surrounding the subject. after doing some testing and reviewing the actual package code I came to the conclusion that GetX only cares if it can match the route at the start of the URI. So if you have a route for "/" then anything after the "/" such as "/non-existent-route/1/2/3/" will still match "/" because it starts with "/". Similarly, if you have a route like "/admin-area" and another for "/admin-area/home" then "/admin-area/non-existent-route/1/2/3/" would still get matched with "/admin-area" and a URI like "/admin-area/home/non-existent-route/1/2/3/" would still match with "/admin-area/home". As this is not the expected behaviour for the web, it can cause problems when using the currentRoute to highlight the current page in your navigation, and considering the documentation for GetX is so poor, I can only assume this is a bug.

The only way I could properly get around this issue is by not using the GetMaterialApp getPages property at all and using onGenerateRoute instead. Get.toNamed still works and the router will only route to exact matches, like this:

  @override
  Widget build(BuildContext context) {
    return GetMaterialApp(
      initialRoute: "/",
      onGenerateRoute: (RouteSettings route) {
        var uri = Uri.parse(route.name!);
        switch (uri.path) {
          case "/":
            return MaterialPageRoute(
              settings: route,
              builder: (context) => const RootPage(),
            );
          default:
            return MaterialPageRoute(
              settings: route,
              builder: (context) => const UnkownPage(),
            );
        }
      },
    );
  }

if you wish, you can also move this logic out of the GetMaterialApp like this:

@override
Widget build(BuildContext context) {
  return GetMaterialApp(
    initialRoute: "/",
    onGenerateRoute: generateRoutes
    },
  );
}

//another file
Route<dynamic> generateRoutes(RouteSettings route) {
  var uri = Uri.parse(route.name!);
  switch (uri.path) {
    case "/":
      return MaterialPageRoute(
        settings: route,
        builder: (context) => const UnimplementedPage('Unkown'),
      );
    default:
      return MaterialPageRoute(
        settings: route,
        builder: (context) => const UnimplementedPage('Unkown'),
      );
  }
}

I had the same issue, but I dig into the Get code, and found out there is a matchRoute() method will return "/" for unknownRoute.

--> In short, to make the unknownRoute work, your initial route could not be "/", it can be anything else, such as "/home", "/main", "/init", but it just can't be "/".


I created a PR for it : https://github.com/jonataslaw/getx/pull/2256

I faced a similar problem, but I didn't receive any error messages. Instead, when I tried to navigate to a non-existent page, I always ended up on the start page. I have not found any clarification on this.

My solution was to set something more weighty for initialRoute, for example: initialRoute: '/beginpage'. '/' and '/start' did not work for me, maybe they are some reserved routes. I see the same problem in your code, try changing it as well.

If this does not help, you can look at a ready-made example that I found on github and which helped me identify my problem: https://github.com/ducafecat/getx_quick_start .

I found a simple way that you can skip the main page in "getPage:" because of "initialRoute:" implemented.

return GetMaterialApp(
    title: 'Named navigation',
    unknownRoute: GetPage(name: '/notfound', page: () => UnknownRoutePage()),
    initialRoute: '/',

    getPages: [

      GetPage(name: '/second', page: () => SecondScreenNamed()),
      GetPage(name: '/third', page: () => ThirdParametersScreenNamed()),
      GetPage(
          name: '/third_with_built_param/:someValue',
          page: () => ThirdParametersScreenNamed()),
    ],

Try it.

I solved

notWorking UnknownRoute

First AppInformationParser put inside GetMaterialApp.router

routeInformationParser: AppInformationParser()

   return GetMaterialApp.router(
      title: 'ACRI',
      debugShowCheckedModeBanner: false,
      theme: AppTheme.appTheme,
      themeMode: ThemeMode.light,
      locale: Get.locale ?? const Locale('tr'),
      translations: AppLocalization(),
      routerDelegate: Get.createDelegate(
        backButtonPopMode: PopMode.Page,
        notFoundRoute: AppPages.pageNotFound,
      ),
      localizationsDelegates: AppLocalizationDelegate.appDelegates,
      supportedLocales: AppLocalizationDelegate.supportedLocales,
      routeInformationParser: AppInformationParser(),
      backButtonDispatcher: AppBackButtonDispatcher(),
      getPages: AppPages.pages,
      initialBinding: AccountBinding(),
      unknownRoute: AppPages.pageNotFound,
    );

and create AppInformationParser class

  class AppInformationParser extends RouteInformationParser<GetNavConfig> {
  /// [initialRoute] => [/]
  AppInformationParser({
    String? initialRoute,
  })  : initialRoute = initialRoute ?? '/',
        super();

  /// Initial route
  /// default '/'
  final String initialRoute;

  @override
  Future<GetNavConfig> parseRouteInformation(
    RouteInformation routeInformation,
  ) {
    String? location = routeInformation.location; // => [/]
    if (location == '/') {
      if (!Get.routeTree.routes.any((e) => e.name == '/')) {
        location = initialRoute;
      }
    }

    // if (!Get.routeTree.routes.any((e) => false)) {
    //   location = AppRoutes.notFound;
    // }

    final matchResult = Get.routeTree.matchRoute(location ?? initialRoute);
    String? matchResultLocation = matchResult.route?.name;

    log("App Information Parser location : $location");
    log("Match Result Parser location : ${matchResult.route?.name}");

    if (matchResultLocation != location) {
      location = AppRoutes.notFound;
    } else if (matchResultLocation == AppRoutes.navigation) {
      location = AppRoutes.home;
    } else if (matchResultLocation == AppRoutes.list) {
      location = AppRoutes.vehicles;
    } else if (matchResultLocation == AppRoutes.report) {
      location = AppRoutes.vehicleReport;
    } else if (matchResultLocation == AppRoutes.map) {
      location = AppRoutes.mapVehicles;
    }

    final result = Get.routeTree.matchRoute(location ?? initialRoute);

    return SynchronousFuture(
      GetNavConfig(
        currentTreeBranch: result.treeBranch,
        location: location,
        state: routeInformation.state,
      ),
    );
  }

  @override
  RouteInformation? restoreRouteInformation(GetNavConfig configuration) {
    return RouteInformation(
      location: configuration.location,
      state: configuration.state,
    );
  }
}

This gives to you entred router name

 final matchResult = Get.routeTree.matchRoute(location ?? initialRoute);
 String? matchResultLocation = matchResult.route?.name;

and compare your location router

   if (matchResultLocation != location) {
      location = AppRoutes.notFound;
    }

if matchResultLocation not compare location so it is unknown router.

And finally matchRoute methods added again for GetNavConfig and solved UnknownRoute

    final result = Get.routeTree.matchRoute(location ?? initialRoute);

    return SynchronousFuture(
      GetNavConfig(
        currentTreeBranch: result.treeBranch,
        location: location,
        state: routeInformation.state,
      ),
    );

Solved: First i used getPages property from GetMaterialApp :

    return GetMaterialApp(
      localizationsDelegates: context.localizationDelegates,
      supportedLocales: context.supportedLocales,
      locale: context.locale,
      debugShowCheckedModeBanner: false,
      title: Constants.appName,
      //Rutas fluro
      // onGenerateRoute: Flurorouter.router.generator,
      // onGenerateRoute: (RouteSettings route){
      //   var uri = Uri.parse(route.name!);
      //   switch (uri.path) {
      //     case "/":
      //       return GetMaterialPageView(
      //         settings: route,
      //         builder: (context) => const RootPage(),
      //       );
      //     default:
      //       return MaterialPageRoute(
      //         settings: route,
      //         builder: (context) => const UnkownPage(),
      //       );
      //   }  
      // },
      initialBinding: InitialBindings(),
      initialRoute: AppRoutes.initialRoute,
      getPages: AppPages.pages,
      unknownRoute: GetPage(name: '/notfound', page: () => const Screen404()),
      // routes: AppRoutes.getAppRoutes(),
      theme: AppTheme.lightTheme,
    );

Inside of AppPages.Pages i have all the pages of my project, each page looks like this:

    GetPage(
      name: AppRoutes.SPLASH,
      page: () => AppRoutes.routes.contains(Get.currentRoute)
        ?SplashScreen()
        :const Screen404(),
    ),

before return a page i use this validation on page property to return the current route and validate if the route is the same what i defined on AppRoutes if isn't the same i return an Screen 404.

In case the route has parameters like this:

    GetPage(  
      name: "${AppRoutes.CANCEL_RESERVATION}/:id_reservation",
      page: (){
        //TODO VERIFICAR QUE ID EXISTA EN LISTADO DE AMENIDADES
        print(Get.parameters);
        print(Get.currentRoute);
        final id = int.tryParse(Get.parameters['id_reservation']!);
        final controller = Get.find<MyReservationsController>();
        controller.getReservationSelectedToCancel(id);
        if(controller.myReservationToCanel != null){
          return CancelReservationScreen(
            reservation: id,
          ); 
        }else{
          return const Screen404();
        }
      },
      binding: MyReservationsBinding(),
    ),

On this way we can use bindings too.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM