Milad-Akarie / auto_route_library

Flutter route generator
MIT License
1.58k stars 402 forks source link

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

Closed theweiweiway closed 2 years ago

theweiweiway commented 3 years ago

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 commented 3 years ago

Having same issue. Did u find a fix yet?

theweiweiway commented 3 years ago

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

rvndsngwn commented 3 years ago

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 commented 3 years ago

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

simofilahi commented 3 years ago

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

ArintonAkos commented 3 years ago

Has anyone fixed this issue?

Milad-Akarie commented 3 years ago

@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 commented 3 years ago

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

samu-developments commented 2 years ago

@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 commented 2 years ago

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 commented 2 years ago

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 commented 2 years ago

@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 commented 2 years ago

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 commented 2 years ago

@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 commented 2 years ago

@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 commented 2 years ago

@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

https://github.com/Milad-Akarie/auto_route_library/issues/433#issuecomment-810564956

SashaKryzh commented 2 years ago

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 commented 2 years ago

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

feduke-nukem commented 2 years ago

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 commented 2 years ago

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

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 commented 2 years ago

@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 commented 2 years ago

@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[bot] commented 2 years ago

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 commented 1 year ago

I have the same issue. Any updates here?

ebsangam commented 1 year ago

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),
      ],
    ),
  ],
)
bismus-le-sha commented 4 months ago

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.