Home > Software design >  Flutter - how to access the value of a future inside a streambuilder, with Firebase Auth
Flutter - how to access the value of a future inside a streambuilder, with Firebase Auth

Time:04-10

I am using Firebase Authentication for a subscription-based app that I am building and using cloud functions and custom claims, I am ensuring that the user can only log in if they have an active subscription.

My app has a landing page, which checks to see if the user is logged in and if they are, they are taken to the app home page. If not, they are shown the login screen. This is the code for it:

class LandingPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final auth = Provider.of<AuthBase>(context, listen: false);
    return StreamBuilder<User?>(
        stream: auth.authStateChanges(),
        builder: (context, snapshot) {
          if (snapshot.connectionState == ConnectionState.active) {
            User? user = snapshot.data;
            user?.reload();
            user = snapshot.data;

            if (user == null || user.emailVerified == false) {
              return SignInHomePage.create(context);
            }
            return Provider<User?>.value(
              value: user,
              child: DisclaimerAcceptanceHomeScreen(
                userName: user.displayName,
              ),
            );
          } else {
            return Scaffold(
              body: Center(
                child: PlatformCircularProgressIndicator(),
              ),
            );
          }
        });
  }
}

This works perfectly to get the user data and show the relevant screen. The only problem is, I don't know how to add the user's subscription status to this, to ensure only a user with an active subscription will be directed to the app's home page. The subscription status custom claim is returned as a future. In theory I would want to use something like this:

  final idTokenResult =
      await FirebaseAuth.instance.currentUser?.getIdTokenResult(true);

and then:

idTokenResult?.claims!['status']

The ideal condition that would return a user to the login screen would be:

user == null || user.emailVerified == false || idTokenResult?.claims!['status'] != 'active'

This does work inside an async/await function but you'll see that my landing page is a stateless widget and is not asynchronous.

How can I modify the landing page code, to allow me to get the Future 'status' custom claim value? I would appreciate any suggestions on this. Thank you!

CodePudding user response:

Did you consider using a FutureBuilder around the call to getIdTokenResult?

Alternatively you can use a StreamBuilder on FirebaseAuth.instance.idTokenChanges() to get notified of all updates to the ID token.

CodePudding user response:

Based on Frank's comment, I modified my StreamBuilder to include FutureBuilder, where the future was the getIdTokenResult(true) function. This is what the above code looks like, modified as described:

class LandingPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final auth = Provider.of<AuthBase>(context, listen: false);
    return StreamBuilder<User?>(
        stream: auth.authStateChanges(),
        builder: (context, snapshot) {
          if (snapshot.connectionState == ConnectionState.active) {
            User? user = snapshot.data;
            user?.reload();
            user = snapshot.data;

            return FutureBuilder<IdTokenResult>(
              future: user?.getIdTokenResult(true),
              builder: (
                BuildContext context,
                AsyncSnapshot<IdTokenResult> snapshotData,
              ) {
                if (user == null ||
                    user.emailVerified == false ||
                    snapshotData.data?.claims!['status'] != 'active') {
                  return SignInHomePage.create(context);
                } else {
                  return Provider<User?>.value(
                    value: user,
                    child: DisclaimerAcceptanceHomeScreen(
                      userName: user.displayName,
                    ),
                  );
                }
              },
            );
          } else {
            return Scaffold(
              body: Center(
                child: PlatformCircularProgressIndicator(),
              ),
            );
          }
        });
  }
}

This now allows me to check the user's subscription status before letting them have access to the app.

  • Related