0

I'm developing a web app using flutter web.

Everytime I refresh a page, the onAuthStateChanges gets triggered and execute the functions that should be triggered only when a change in the auth state happens.

Is this behaviour intended?

To subscribe to the onAuthStateChanges, I wrapped my main widget with a stateful widget and listened to the onAuthStateChanges, as suggested here: Flutter Stream Builder Triggered when Navigator Pop or Push is Called

The following is my code:

my wrapper:

/// Wrapper for stateful functionality to provide onInit calls in stateles widget
class StatefulWrapper extends StatefulWidget {
  final Function onInit;
  final Widget child;
  const StatefulWrapper({@required this.onInit, @required this.child});
  @override
  _StatefulWrapperState createState() => _StatefulWrapperState();
}

class _StatefulWrapperState extends State<StatefulWrapper> {
  @override
  void initState() {
    if (widget.onInit != null) {
      widget.onInit();
    }
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return widget.child;
  }
}

my widget in main.dart:

Widget build(BuildContext context) {
    var router = MyRouter.Router();
    var authenticationService = locator<AuthenticationService>();
    var sharedPreferences = locator<SharedPreferencesService>();

    return StatefulWrapper(
      onInit: () {
        authenticationService.listenToAuthChanges();
      },
      child: FutureBuilder(
          future: sharedPreferences.getSharedPreferences('ruolo'),
          builder: (context, snapshot) {
            return MaterialApp(
              builder: ExtendedNavigator<MyRouter.Router>(
                initialRoute: MyRouter.Routes.startupView,
                guards: [AuthGuard(), AdminGuard()],
                router: router,
                navigatorKey: locator<NavigationService>().navigatorKey,
              ),
              title: 'Flutter Demo',
              theme: ThemeData(
                primarySwatch: Colors.blue,
                visualDensity: VisualDensity.adaptivePlatformDensity,
              ),
              onGenerateRoute: router.onGenerateRoute,
            );
          }),
    );
  }

Here is the method from the authenticationService:

  void listenToAuthChanges() {
    FirebaseAuth.instance.authStateChanges().listen((firebaseUser) async {
      if (firebaseUser == null) {
        navigationService.navigateTo(Routes.login);
      } else {
         navigationService.replaceWith(Routes.home);
      }
    });
  }

What happens is that when I refresh my page in the browser, the listener of this function gets triggered, and it navigate to the login if I'm not logged, on the home otherwise.

Is this intended? For me a listener should fire only when there is a change in what it listens to.

Frank van Puffelen
  • 565,676
  • 79
  • 828
  • 807
xcsob
  • 837
  • 1
  • 12
  • 27
  • If you refresh the page, you're essentially restarting the whole application. It is intended, otherwise you can't get the auth state on startup. – Christopher Moore Sep 10 '20 at 17:06
  • Okay, but why the handler of the function is triggered? For me it doesn't make any sense per for me. – xcsob Sep 10 '20 at 17:16
  • I mean, the handler of a listener should be trriggered only if that event happens. When I refresh, my login status doesn't change, it remains the same as before. – xcsob Sep 10 '20 at 17:17
  • It would be impossible to determine the auth state on app startup if they removed this. When you refresh a webpage, it doesn't "remember" the previous auth state. You're restarting the *whole* app. – Christopher Moore Sep 10 '20 at 17:17

1 Answers1

1

When the app loads, Firebase checks the authentication status of the user. At the start of that process is calls any active auth state listener with a null value. If the user can be authenticated, it then calls the listener with the user profile object.

So what you're seeing is indeed working as intended.

Frank van Puffelen
  • 565,676
  • 79
  • 828
  • 807
  • I have another question. With the code I shown here, it seems that the trigger is fired more than once. I suppose there should be a bug somewhere, because this can't be intended, right? – xcsob Sep 10 '20 at 18:17
  • 1
    Your callback will usually be called with `null` when the page loads, and then with the actual user a few moments later. – Frank van Puffelen Sep 10 '20 at 19:33
  • Thank you for your explanation. However I notice a behaviour different from the one you are exposing me. When I login and logout, the listener is called only once. When I refresh my page, the listener is called many times, but if I'm logged in, it's called many times with the value of the user. While if I'm not logged in it is called with null multiple times. Following an example refreshing the page: Fired the listener - Timestamp: 2020-09-11 09:17:53.741 Email is:mail@mail.com Fired the listener - Timestamp: 2020-09-11 09:17:53.755 Email is: mail@mail.com – xcsob Sep 11 '20 at 07:20
  • I've used firebase on mobile and I love it, but this behaviour on web makes me have hard time. Since I need to save my user auth state, I would like to use the listener to redirect him to the right page. However the listener called multiple times, makes the web app having several useless and annoying redirect. Which is the standard way to solve this? Up to now I don't have any redirect in the listener, but I sed a shared preferences (loggedIn), and I've a startup screen that checks this variable and redirect accordingly to it. But I feel this is a workaround and not the right way. – xcsob Sep 11 '20 at 07:25
  • another solution that I found is checking the timestamp in the onAuthStateChange. It the timestamp is greater than 500 ms, then do the navigation. But I still see this like a dirty workaround than a solution. – xcsob Sep 11 '20 at 07:54
  • I haven't seen the listener fire too frequently once the user is signed in, but the initial null-to-user transition is expected on both web and iOS. Only on Android is there some weird-but-convenient workaround in place. A neat trick I learnt from team mate Michael Bleigh is to store a value in synchronous local storage if the user was succesfully authenticated last time and then use this value as a shortcut to navigate tot he right page the next time the page load. You may have to course correct the navigation if the local storage value is wrong, but that'll be fairly uncommon. – Frank van Puffelen Sep 11 '20 at 15:20