beamer icon indicating copy to clipboard operation
beamer copied to clipboard

Reactive guards, maybe ?

Open cedvdb opened this issue 3 years ago • 4 comments

I'm not sure if this feature should be included in beamer but I think it's worth discussing.

As of today guards work by allowing or disallowing access to a page at the moment of access. However sometimes you want to react to changes and redirect accordingly.

Take the authentication state for example. Authentication is asynchronous, the authentication state is unknown at first. Then the state becomes either authenticated or unauthenticated. With providers like firebase this can take seconds to resolve.

In the UI that translates to :

  • show loading page when state is unknown
  • show login page when unauthenticated
  • show home page or accessed page (via url) when authenticated

In the current state of affairs that would look like something like this (repository branch here):

abstract class AppRouter {
  static final homeRoute = '/home';
  static final loadingRoute = '/loading';
  static final signInRoute = '/signIn';

  static final routerDelegate = BeamerDelegate(
    guards: [
      BeamGuard(
        pathBlueprints: ['/'],
        check: (ctx, state) => false,
        beamToNamed: homeRoute,
      ),
      // when starting the application, we check if the authenticated user is
      // already set. If not we go to the loading page.
      // however we need to redirect when the user is finally authenticated
      // or unauthenticated. 
      BeamGuard(
        guardNonMatching: true,
        pathBlueprints: [loadingRoute],
        check: (ctx, state) =>
            AuthRepository.instance.authUser$.value is! LoadingAuthUser,
        beamToNamed: loadingRoute,
      )
    ],
    locationBuilder: SimpleLocationBuilder(
      routes: {
        loadingRoute: (context, state) => LoadingPage(),
        homeRoute: (context, state) => HomePage(),
        signInRoute: (context, state) => LoginPage(),
      },
    ),
  );

  static final routerParser = BeamerParser();
}

Here when the user opens the app, beam guards are going to redirect him to the loading page. However there is nothing currently that will redirect him to the login or home page when the auth state resolves.

This of course can be added, it's possible to listen to the Authentication status somewhere and use beamer beamTo. However that becomes hard to manage when there are multiple streams you need to listen to.

What I currently do is that I use a ReactiveGuard widget that is put in the builder of the MaterialApp (repository branch here) :


class ReactiveGuard extends StatelessWidget {
  final AuthRepository authRepository = AuthRepository.instance;
  final Widget child;

  ReactiveGuard({required this.child});

  @override
  Widget build(BuildContext context) {
    return StreamBuilder<AuthUser>(
        stream: authRepository.authUser$,
        builder: (ctx, snap) {
          if (!snap.hasData) return CircularProgressIndicator();
          print(snap.data);
          AuthUser authUser = snap.data!;
          
          // in more complicated systems there is often more than one stream being listened onto and it sort of cascades here. 

          if (authUser is UnknownAuthUser) {
            Beamer.of(context).beamToNamed(AppRouter.loadingRoute);
          }

          if (authUser is AuthenticatedUser) {
            Beamer.of(context).beamToNamed(AppRouter.homeRoute);
          }

          if (authUser is UnauthenticatedUser) {
            Beamer.of(context).beamToNamed(AppRouter.signInRoute);
          }

          return child;
        });
  }
}

However I think this is a feature that is worth integrating more into beamer with the beamer way of doing things, that is, more strandardised.

cedvdb avatar Jun 09 '21 20:06 cedvdb

@cedvdb I like the idea. We should work on that when I get back from the vacation

slovnicki avatar Jun 09 '21 20:06 slovnicki

go_router can do this. There you can pass a Listenable to the router. The router then redirects based on the values of the listenable. (https://gorouter.dev/redirection) Would be great if beamer could support something similar, too.

saibotma avatar Mar 30 '22 08:03 saibotma

Hey @saibotma Beamer also has that 🙂

It is the updateListenable property of BeamerDelegate which will trigger Beamer.of(context).update() when it notifies, which will rerun the guards and beam appropriately.

Indeed, this "reactive guarding" that the issue talks about can now be acomplished with the combination of updateListenable and multiple BeamGuards for each notifier's "state".

However, I always imagined this issue to be about something more compact (similar to provided workaround above) for handling all the cases in a single piece of logic, instead of having to define a separate BeamGuard for each situation. It would also alow reacting to more than one notifier. I haven't revisited this issue for a long time, I'll think about that some more now... 🤔

slovnicki avatar Mar 30 '22 08:03 slovnicki

Can someone post an example of the updateListenable approach? I thought Guards would do this automatically.

amirdt22 avatar Jul 18 '23 19:07 amirdt22