Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pages stack can be managed by either the Widget (AutoRouter.declarative) or the (StackRouter) #496

Closed
theweiweiway opened this issue Apr 29, 2021 · 26 comments

Comments

@theweiweiway
Copy link
Collaborator

theweiweiway commented Apr 29, 2021

Hey Milad,

I found a small bug when trying to match the URL with the declarative router. Overall, it works quite well! However there's a small bug that I found that should probably be fixed

Here's my app:
https://github.com/theweiweiway/flutter_uxr/blob/nesting/lib/main.dart

Line 125 has the declarative router part

Basically, I have 3 routes:
/settings
/books/all
/books/new

The /books routes are rendered declaratively as you can see in Line 125

It seems like when hitting a new fresh URL on web, the State will determine what the URL is - which makes sense - this is great. However, on the very first time I hit the /books/new URL, i get the Pages stack can be managed by either the Widget (AutoRouter.declarative) or the (StackRouter) error. Then, if hit that same URL again at /books/new, everything works fine and it redirects me to /books/all because the state takes over

This video basically sums everything up quite nicely:
https://user-images.githubusercontent.com/49566929/116496829-eadd2900-a873-11eb-8cfa-c7dca920539c.mov

Sorry - couldn't do a gif since text was too small

@steve-cahn
Copy link

Having same issue. Did u find a fix yet?

@theweiweiway
Copy link
Collaborator Author

I think 2.1.1 has some additional features to help with this - will test out later today

@rvndsngwn
Copy link

I think 2.1.1 has some additional features to help with this - will test out later today

I'm using 2.2.0 version. Same error, Pages stack can be managed by either the Widget (AutoRouter.declarative) or Router.

[VERBOSE-2:ui_dart_state.cc(199)] Unhandled Exception: 'package:auto_route/src/router/controller/routing_controller.dart': Failed assertion: line 838 pos 7: '!managedByWidget': Pages stack can be managed by either the Widget (AutoRouter.declarative) or Router

@2shrestha22
Copy link

Got the same problem, so I can't push signup page from login page.

@simofilahi
Copy link

simofilahi commented Aug 1, 2021

No one fix that error, I'm facing it also

@ArintonAkos
Copy link

Has anyone fixed this issue?

@Milad-Akarie
Copy link
Owner

@ArintonAkos @simofilahi @2shrestha22 you're seeing this assertion message because you're trying to push routes to a declarative router. routers can either be used imperatively ( push, replace ..etc) or declaratively, you can not use both methods on the same router.

@EdoardoPi
Copy link

I've got the same problem. How did you fix it ?

@samu-developments
Copy link

samu-developments commented Oct 31, 2021

@Milad-Akarie Hi, I'm a bit beginner with AutoRoute but it has been awesome so far. The declarative approach seems perfect for adding onboarding+loading screen to my app, however the rest of my app is using the imperative approach. How do I go about supporting both, I believe I need to use 2 routers or something but not sure.

MaterialApp.router(
          debugShowCheckedModeBanner: false,
          routerDelegate: appRouter.declarativeDelegate(
            routes: (_) => [
              if (!widget.doneOnboarding)
                OnboardingRoute(onFinishedOnboarding: onFinishedOnboarding)
              else if (!ref.watch(isAuthenticatedProvider).state) LoadingRoute()
              else HomeRoute(),    <-- using rcontext.router.push() here fails
            ],
          ),
          ...
@AdaptiveAutoRouter(
  replaceInRouteName: 'Page,Route',
  routes: <AutoRoute>[
    AutoRoute(path: '/onboarding', page: OnboardingPage),
    AutoRoute(path: '/loading', page: LoadingPage),
    AutoRoute(path: '/', page: HomePage),
    AutoRoute(path: '/favorite', page: FavoritePage),
   ...
context.router.push(FavoriteRoute());

Thanks

@rd-martin-jaeger
Copy link

Same here. Using a declarative route for auth:

routes: (_) => [
          if (authState == const SignedIn())
            const HomeRoute()
          else
            const AuthWrapperRoute()
        ],

My home route screen is a stateless widget with a scaffold and a tab bar:

  @override
  Widget build(BuildContext context) => AutoTabsRouter(
        routes: _routes,
        builder: (context, child, animation) {
          final tabsRouter = AutoTabsRouter.of(context);

          return DefaultTabController(
            length: _routes.length,
            child: Scaffold(
              appBar: AppBar(
                backgroundColor: Colors.white,
                actions: [
                  IconButton(
                    icon: Icon(
                      Icons.settings_outlined,
                      color: AppColors.darkGrey,
                    ),
                    onPressed: () =>
                        context.router.push(SettingsRoute()).  -> FAILS HERE
                  )
                ],
                bottom: TabBar(
                  onTap: tabsRouter.setActiveIndex,
                  indicatorColor: AppColors.green,
                  tabs: [
                    Tab(
                      icon: Text("Tab1),
                    ),
                    Tab(
                      icon: Text("Tab2"),
                    ),
                  ],
                ),
              ),
              body: child,
            ),
          );
        },
      );

Seems to be a common issue where you mix declarative and stack router for authentication and the content area.
Some assistance from the author would be welcome

@sijav
Copy link

sijav commented Jan 18, 2022

So I checked the documentation there's a helper that take the delegate auto router in the context
final router = AutoRouterDelegate.of(context);
you can use it to navigate between pages by
router.controller.navigateNamed('/route/path')
or
router.controller.navigate(SomeRouter())

@feduke-nukem
Copy link

@samu-developments @Milad-Akarie I got the same problem.

My entire application is using imperative way, but I need to strongly check if user is logged in, so I must use AutoRouterDelegate.declarative and this prevents me from using my app correctly as I did.

Is there any other way to implement Auth check?

@rafael-mq
Copy link

I'm kinda frustrated with this. Just followed the official tutorials and I find out this issue was opened on Apr 2021 and there isn't any definitive explanation on how to solve it so far.
@Milad-Akarie A solution on the problem would be very much appreciated. Thanks :)

@Milad-Akarie
Copy link
Owner

@rafael-mq This is not a bug, this an assertion message, you can't push routes to a declarative router...it gets it's stack from the widget only.

@rafael-mq
Copy link

rafael-mq commented Mar 17, 2022

@Milad-Akarie Although this is an open issue I get it's not a bug. But is there any possible solution for using both declarative and imperative routing? Maybe having two routers dedicated to each routing approaches just like @samu-developments suggested.
Also the docs could make it clear that both approaches are not compatible.
Alll in all, I congratulate the package mantainers for the outstanding work. 💯

@kevin-bog
Copy link

kevin-bog commented Mar 18, 2022

@rafael-mq from what I understand, you can not have your declarative routing at the root of your application, unless you change your states to trigger "a push" to another route.
Or if all you application is under the same AutoRoute parent as the documentation shows https://autoroute.vercel.app/advanced/authentication .

I think this design is perfect for subscription flow, when you will need to bind your state to the routing : https://autoroute.vercel.app/advanced/declarative_routing

Hope this link could help you to manage authentication

#433 (comment)

@SashaKryzh
Copy link

My solution

AuthenticatedStackPage uses declerative routing. Also the AuthenticatedStackRouter (EmptyRouterPage) is also placed inside declerative RootStackPage.

AutoRoute(
          name: 'AuthenticatedStackRouter',
          // Provides router for push, pop, etc.
          // If we use only declerative routing we can't use these methods.
          page: EmptyRouterPage,
          children: [
            // Authenticated root (declerative routing)
            AutoRoute(
              initial: true,
              page: AuthenticatedStackPage,
              children: [
                AutoRoute(page: SetupPassPage),
                AutoRoute(page: RegistrationFlowPage),
                AutoRoute(page: RegistrationAdditionalStepsPage),
                AutoRoute(page: HomePage),
              ],
            ),
            // Other pages that could be opened only when authenticated
            AutoRoute(page: SearchLocationPage),
            AutoRoute(page: DiseasesListPage),
            AutoRoute(page: OtherDiseasePage),
            AutoRoute(page: HypertensionMedicationInfoPage),
            AutoRoute(page: VaccinationInfoPage),
            AutoRoute(page: VaccinationsListPage),
            AutoRoute(page: ServicesListPage),
            AutoRoute(page: ServiceSubcategoriesPage),
          ],
        ),

@Tom1901
Copy link

Tom1901 commented Apr 1, 2022

just try it like this guys:
routerDelegate: _appRouter.delegate( initialRoutes: isLoggedIn ? [const StartRoute()] : [const LoginRoute()])

@feduke-nukem
Copy link

My decision was to get initialDeepLink like:

if (!isAccessAllowed) {
    initialDeepLink = '/app-update';
  } else if (!isAuthorized) {
    initialDeepLink = '/authorization';
  }
  
return AutoRouterDelegate(
    getIt<AppRouter>(),
    initialDeepLink: initialDeepLink,
  );

Then I wrapped my app widget with BlocConsumer, which will replace stack to AuthRoute or to MainRoute when user logged in/logged out:

 return BlocConsumer<AppCubit, IAppState>(
             listener: (context, state) async {
               if (state is AuthorizedState) {
                 await getIt<AppRouter>()
                     .replaceAll([const NotificationsRootRoute()]);
               } else if (state is UnathorizedState) {
                 await getIt<AppRouter>().replaceAll([AuthorizationRoute()]);
               }
             }

@dehypnosis
Copy link

dehypnosis commented Apr 15, 2022

Try to embed stacked routers children into root declarative route.
like this.

  • I am using mobx for state management.
class RootWrapperPage extends StatelessWidget {
  RootWrapperPage({Key? key}) : super(key: key);
  final authProvider = ioc.get<AuthProvider>();

  @override
  Widget build(BuildContext context) {
    return Observer(builder: (_) {
      var signedIn = authProvider.signedIn;
      return AutoRouter.declarative(routes: (_) => [
        if (signedIn) MainWrapperRoute(),
        if (!signedIn) GuestWrapperRoute(),
      ]);
    });
  }
}

@AdaptiveAutoRouter(
  replaceInRouteName: 'Page,Route',
  routes: <AutoRoute>[
    AdaptiveRoute(
      initial: true,
      path: '/',
      page: RootWrapperPage,
      children: [
        AdaptiveRoute(
          path: 'guest',
          page: GuestWrapperPage,
          children: [
            AdaptiveRoute(
              initial: true,
              path: 'home',
              page: GuestHomePage,
            ),
            AdaptiveRoute(
              path: 'login',
              page: GuestLoginPage,
            ),
            AdaptiveRoute(
              path: 'register',
              page: GuestRegisterPage,
            ),
          ],
        ),
        AdaptiveRoute(
          initial: true,
          path: 'main',
          page: MainWrapperPage,
          children: [
            AdaptiveRoute(
              initial: true,
              path: 'home',
              page: MainHomePage,
            ),
            ...
          ],
        ),
      ],
    ),
    RedirectRoute(
      path: "*",
      redirectTo: '/',
    ),
  ],
)
class AppRouter extends _$AppRouter {
  AppRouter(): super() {
     ...
  }
}

@mavinis
Copy link

mavinis commented May 8, 2022

@dehypnosis i'm trying your approach but i still get the AssertionError when I try to push new pages.
How does it work inside GuestWrapperPage and MainWrapperPage?
Did you leave the AutoRouter.declarative in the MaterialApp.router?

@huynhmytuan
Copy link

huynhmytuan commented May 18, 2022

@dehypnosis i'm trying your approach but i still get the AssertionError when I try to push new pages. How does it work inside GuestWrapperPage and MainWrapperPage? Did you leave the AutoRouter.declarative in the MaterialApp.router?

With 'GuestWrapperPage' also 'MainWrapperPage', you can create an empty page like this:

class GuestWrapperPage extends StatelessWidget {
  GuestWrapperPage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return AutoRouter();
    }
}

or using EmptyRouterPage build-in auto_route package in your route stack

@MaterialAutoRouter(
  replaceInRouteName: 'Page,Route',
  routes: <AutoRoute>[
    AutoRoute<dynamic>(
      initial: true,
      path: '/',
      page: RootWrapperPage,
      children: [
        AutoRoute<dynamic>(
          path: 'authenticate',
          name: 'AuthenticateRouter',
          page: EmptyRouterPage,
          children: [
            AutoRoute<dynamic>(
              initial: true,
              path: 'login',
              page: LoginPage,
            ),
            AutoRoute<dynamic>(
              path: 'register',
              page: RegisterPage,
            ),
          ],
        ),
        AutoRoute<dynamic>(
          path: 'main',
          name: 'MainWrapperRouter',
          page: EmptyRouterPage,
          children: [
             ....

@github-actions
Copy link

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions

@kuba-asanov
Copy link

I have the same issue. Any updates here?

@ebsangam
Copy link

ebsangam commented Mar 20, 2023

Here is how it is works EmptyRouterPage:

If you are inside any of the OnboardWrapper, HomeWrapper or SplashRoute routes you can only push their children. For example you can't push HomePage from LoginPage because HomePage is from HomeWrapper and LoginPage is from OnboardWrapper route. Hope it make you clear about this.

routerDelegate: AutoRouterDelegate.declarative(
  appRouter,
  routes: (_) {
    return authState.when(
      authenticated: (_) {
        logger.i('Routing to HomeWrapper');
        return [const HomeWrapper()];
      },
      unauthenticated: () {
        logger.i('Routing to OnboardWrapper');
        return [const OnboardWrapper()];
      },
      unknown: () {
        logger.i('Routing to SplashRoute');
        return [const SplashRoute()];
      },
    );
  },
),
@AdaptiveAutoRouter(
  replaceInRouteName: 'Page,Route',
  routes: <AutoRoute>[
    AutoRoute(initial: true, page: SplashPage),
    AutoRoute(
      page: EmptyRouterPage,
      name: 'OnboardWrapper',
      children: [
        AutoRoute(page: OnboardPage, initial: true),
        AutoRoute(page: LoginPage),
        AutoRoute(page: EmailSignupPage),
        AutoRoute(page: EmailOtpLoginPage),
        AutoRoute(page: PhoneOtpLoginPage),
      ],
    ),
    AutoRoute(
      name: 'HomeWrapper',
      page: EmptyRouterPage,
      children: [
        AutoRoute(page: HomePage, initial: true),
      ],
    ),
  ],
)

@spermobak
Copy link

I have a strange problem.

When I use AutoRouterDelegate.declarative and create an AppWrapperRoute to solve the problem The page stack can be managed by either a widget (AutoRouter.declarative) or (StackRouter) , my implementation of auth checking stops working and my start page is always AuthNavigationRoute.

routerDelegate: AutoRouterDelegate.declarative(di.sl<AppRouter>(),
                  navigatorObservers: () =>
                      [TalkerRouteObserver(di.sl<Talker>())],
                  routes: (_) => [
                        (state is SignedInPageState)
                            ? const HomeNavigationRoute()
                            : const AuthNavigationRoute()
                      ]));

If I abandon the AppWrapperRoute idea, then the auth check works correctly and if the user has already been auth, HomeNavigationRoute opens immediately.
Also, when I try to use di.sl<AppRouter>().delegate, my routing stops working completely and all I see is a white screen.

I would be very happy if someone could explain to me how di.sl<AppRouter>().delegate works and what can cause the above problems.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests