Momentary Flash of Red Error Screen - Getter UID Called on NULL

315

Using an await statement on its own within an async method where it could "hang" the app is discouraged, for obvious UX reasons.

You could use something like below to provide a basic waiting screen while your app is first loading user data and then navigate to the appropriate page depending on results.

class LoginChecker extends StatefulWidget {
  @override
  _LoginCheckerState createState() => _LoginCheckerState();
}

class _LoginCheckerState extends State<LoginChecker> {
  @override
  void initState() {
    super.initState();
    checkLogin();
  }

  Future<void> checkLogin() async {
    // ↓ Example fake loading delay while you grab slow loading resources
    User user = await Future.delayed(Duration(seconds: 2), () => User());
    if (user == null)
      Navigator.push(context, MaterialPageRoute(builder: (context) => MyLoginScreen()));
    else
      Navigator.push(context, MaterialPageRoute(builder: (context) => UserHome()));
  }

  @override
  Widget build(BuildContext context) {
    return Text('loading user info...');
  }
}

class MyLoginScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(child: Text('Login here')),
    );
  }
}

class UserHome extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(child: Text('user home page'),),
    );
  }
}

/* Don't use this if you have firebase_auth importing user.dart which defines a User class.
class User {
  String name;
}*/

In the checkLogin() method, the await statement doesn't block LoginChecker page from loading since initState isn't an async method that is awaited and could block the event loop.

The await only pauses/waits for processing in the local async enclosure.

There's a good video explaining futures by the Flutter team.

And if you're wanting deep details, there's an in-depth explanation of the way Flutter/Dart processes statements in its event loop.

Share:
315
cc976a
Author by

cc976a

Updated on December 27, 2022

Comments

  • cc976a
    cc976a over 1 year

    I am just about to deploy my first app build - but I am stuck on the very last item before I can go live, and really need some help. I think I know why it's happening but don't know how to fix.

    When I first open the app from the device, I see the red error screen for a fraction of a second before the screen loads correctly and the app loads (Log In screen) and runs just fine. This only happens if the user is not logged in. If they are logged in, it runs just fine.

    When the app is opened - main.dart checks to see whether the user is logged in or not. If they are not logged in, it sends them to the login screen (through a Navigator.push). The rest of main.dart assumes the user is logged in and will run queries based on the uid.

    I think what is happening is the main.dart file is trying to load in it's entirety before the redirect to the Login page kicks in. Therefore sees the rest of the scripts calling the uid - where of course it is null.

    My assumption (possibly incorrectly) was that the first script to run is the getCurrentUser below to check the users logged in status. So I 'thought' the app would get to there and send the user off before the rest of the screen loads.

    Is this not the case - and is there a way I can 'wait' for this script to run and complete, and then send the user off to Login if needs be?

    void getCurrentUser() async {
        try {
          final user = _auth.currentUser;
          if (user == null) {
            navigateToSubPageMyLoginScreen(context);
            print('You are not logged in');
          } else if (user != null) {
            if (user.emailVerified == false) {
              navigateToSubPageValideMe(context);
              print('You are not verified');
            } else {
              print('All Good');
            }
          }
        } catch (e) {
          print(e);
        }
      }
     
    

    Thanks in advance!

    The desired behaviour is for the app to load without the red screen showing the error 'Getter 'UID' is null'

  • cc976a
    cc976a over 3 years
    Thanks for replying - and yes makes sense! - I will test this out tonight and reply
  • cc976a
    cc976a over 3 years
    I'm sure this logic is right. It was along the same lines I was thinking (a separate class either caling main page or log in) but I have a clash with User. The error is that User is already defined on the Registration Page through firebase_auth, and now clashes with this User in main.dart - and says I can't have User coming from two places
  • Baker
    Baker over 3 years
    Ah, I think I understand the error you're seeing, i.e. User class defined in two different places and both referenced within the same file. I defined a User class above at the bottom, which was just for illustration (since I don't use & didn't have firebase_auth imported). I'll delete that and change user.name == null to just user. Should fix it I think.
  • cc976a
    cc976a over 3 years
    Well actually User is defined in different places but in two different files. When I updated Main.dart with your code, the Register.dart field error'd in the Console because - and I quote the Console - 'User is defined in both firebase.auth and main.dart'
  • cc976a
    cc976a over 3 years
    Also where => User()) ..... it errors saying 'The class User doesn't have a default constructor'
  • Baker
    Baker over 3 years
    Yes, there's a clash because I defined a User class (a total throwaway example class) in my example code above, at the bottom. If you look again, I've commented it out after you noted the error. You can't have identical class names from two sources being used in a single file. Just to be clear, the code above was only meant as an illustration or guide, not intended as a copy/paste solution into your code. The takeaway should be how you could use initState and checkLogin to perform async navigation logic.
  • Baker
    Baker over 3 years
    haha, ok, hang on there bud. That code is just an example. You'll have to adjust it to do actual proper logic. The above Future.delayed... was just to illustrate a slow async function. You shouldn't actually be using that verbatim. You would do your async load of your user data, from wherever you're getting it, disk storage, remote Firebase service, etc.
  • cc976a
    cc976a over 3 years
    Haha yeah I do understand. I'm about three months in to learning dart, flutter etc... and building, and although I now finally have a fully functioning app built from scratch with Firebase authentication, user input, controls, catchalls etc... - and have spent countless nights understanding and learning how to get around certain issues and headaches - I still have lots to learn I'm afraid
  • cc976a
    cc976a over 3 years
    This async and await I still need to get my head around. After developing websites for countless years the change over still requires getting used to
  • Baker
    Baker over 3 years
    It is a bit of a different lens to look through, when developing with a physical device in mind, that runs on batteries and expects high levels of physical interactivity / responsiveness. The videos in the Flutter in Focus playlist are great if you have a chance: youtube.com/playlist?list=PLjxrf2q8roU2HdJQDjJzOeO6J3FoFLWr2 They give a good overview of many areas of Flutter you'll likely use in your journey.