0
votes



I'm new to Flutter and I'm trying to build an auth system with login and registration.
I've followed an online tutorial, and everything works fine. But my authentication flow is slightly different, and when I add the extra step the SignIn or Registration screens DO NOT redirect to the Home landing page.

This is the code from the tutorial, which is working fine. You'll notice that, when the app is launched, the first screen to appear is the SignIn called by Wrapper.dart through an IF STATEMENT.

Here the files from the tutorial:
tutorial main.dart

void main() {
  runApp(Main());
}

class Main extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return StreamProvider<User>.value(
        value: AuthService().user,
        child: MaterialApp(
          home: Wrapper(),
          debugShowCheckedModeBanner: false,
        )
      );
  }
}

tutorial wrapper.dart

class Wrapper extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final user = Provider.of<User>(context);

    // return either Home or Auth widget
    if (user == null) {
      return Authenticate();
    } else {
      return Home();
    }
  }
}

tutorial authenticate.dart

class Authenticate extends StatefulWidget {
  @override
  _AuthenticateState createState() => _AuthenticateState();
}

class _AuthenticateState extends State<Authenticate> {
  bool showSignIn = true;
  void toggleView() {
    setState(() => showSignIn = !showSignIn);
  }

  @override
  Widget build(BuildContext context) {
    if (showSignIn) {
      return SignIn(toggleView: toggleView);
    } else {
      return Register(toggleView: toggleView);
    }
  }
}

tutorial signIn.dart

class SignIn extends StatefulWidget {
  final Function toggleView;
  SignIn({this.toggleView});

  @override
  _SignInState createState() => _SignInState();
}

class _SignInState extends State<SignIn> {
  final AuthService _auth = AuthService();
  final _formKey = GlobalKey<FormState>();
  bool loading = false;

  // text Field State
  String email = '';
  String password = '';
  String error = '';

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
            backgroundColor: Colors.brown[400],
            elevation: 0.0,
            title: Text('Sign in'),
            actions: <Widget>[
              FlatButton.icon(
                icon: Icon(Icons.person),
                label: Text('Register'),
                onPressed: () {
                  widget.toggleView();
                },
              )
            ]),
        body: Container(
            padding: EdgeInsets.symmetric(vertical: 20.0, horizontal: 50.0),
            child: Form(
                key: _formKey,
                child: Column(children: <Widget>[
                  SizedBox(height: 20.0),
                  TextFormField(
                    decoration: textInputDecoration.copyWith(hintText: 'Email'),
                    validator: (val) => val.isEmpty ? 'Enter an email' : null,
                    onChanged: (val) {
                      setState(() => email = val);
                    },
                  ),
                  SizedBox(height: 20.0),
                  TextFormField(
                    obscureText: true,
                    decoration:
                        textInputDecoration.copyWith(hintText: 'Password'),
                    validator: (val) => val.length < 6
                        ? 'Password is too short. Min 6 characters long'
                        : null,
                    onChanged: (val) {
                      setState(() => password = val);
                    },
                  ),
                  SizedBox(height: 20.0),
                  RaisedButton(
                    color: Colors.pink[400],
                    child: Text(
                      'Sign in',
                      style: TextStyle(color: Colors.white),
                    ),
                    onPressed: () async {
                      if (_formKey.currentState.validate()) {
                        setState(() => loading = true);

                        dynamic result = await _auth.signInWithEmailAndPassword(
                            email, password);
                        if (result == null) {
                          setState(() {
                            error =
                              'These credentials are invalid. Please check them and try again!';
                            loading = false;
                          });
                        }
                      }
                    },
                  ),
                ]))
            ));
  }
}

The Registration.dart file is similar to SignIn, so I won't show it here.
The last file to make a note, is the service called AuthService.dart

class AuthService {
  final FirebaseAuth _auth = FirebaseAuth.instance;

  // Create user object based on FirebaseUser
  User _userFromFirebaseUser(FirebaseUser user) {
    return user != null ? User(uid: user.uid) : null;
  }

  // Auth change user stream
  Stream<User> get user {
    return _auth.onAuthStateChanged.map(
        _userFromFirebaseUser); // This is the equivalent of .map((FirebaseUser user) => _userFromFirebaseUser(user));
  }

  // SignIn with Email & Password
  Future signInWithEmailAndPassword(String email, String password) async {
    try {
      AuthResult result = await _auth.signInWithEmailAndPassword(
          email: email, password: password);
      FirebaseUser user = result.user;
      return _userFromFirebaseUser(user);
    } catch (e) {
      print(e.toString());
      return null;
    }
  }

}

Let me explain how the above flow works
When the user launches the app, main.dart file calls the wrapper.dart, which checks if the User is null or exist.
If User=null, it means that it's NOT logged in and calls Authenticate.dart which by default opens the SignIn screen.
If User != null, it means the user IS logged in and calls the Home.dart screen.

****** MY ISSUE *****
Now, in my flow (see below), Wrapper calls StartAuthButtons.dart when User=null.
The StartAuthButtons has a few buttons to authenticate the user with different methods, for example Google, Facebook, Apple and by Email/Password.

MY Wrapper.dart:

class Wrapper extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final user = Provider.of<User>(context);

    if (user == null) {
      return StartAuthButtons();
      //return Authenticate(); <-- this was before
    } else {
      return Home();
    }
  }
}

Once the user clicks on Sign In with Email and Password, I use Flutter Navigate to go to the SignIn page. This is the extra step I am doing differently form the tutorial. (please check my comments inside the code below between stars)
My StartAuthButtons.dart

class StartAuthButtons extends StatefulWidget {
  @override
  _StartAuthButtonsState createState() => _StartAuthButtonsState();
}

class _StartAuthButtonsState extends State<StartAuthButtons> {
  final AuthService _auth = AuthService();

  @override
  Widget build(BuildContext context) {
    return Stack(children: [
            AuthLayout(),
            Scaffold(
                backgroundColor: Colors.transparent,
                appBar: AppBar(
                  title: Center(child: Text('Welcome')),
                  backgroundColor: Colors.transparent,
                ),
                body: SingleChildScrollView(
                  child: ConstrainedBox(
                    constraints: BoxConstraints(),
                    child: Center(
                      child: Column(
                        mainAxisAlignment: MainAxisAlignment.start,
                        children: <Widget>[

                          //Email & Password SignIn
                          Padding(
                            padding: EdgeInsets.all(8.0), child: new MaterialButton(
                              child: SignInEmailPasswordBtn(),
                              onPressed: () {
                                ****** I THINK THE ISSUE IS HERE *********
                                Navigator.push(
                                  context,
                                  MaterialPageRoute(builder: (context) => Authenticate()),
                                );

                                *** As you can see above, here is where I call Authenticate() ***
                                ****** But by adding this extra step to reach the SignIn page, when I signin it doesn't redirect to the Home once signed in. It stays on the SignIn screen. If I hard refresh the app, the system is logged in **********

                                
                              }
                            )
                          )
                        ]
                      )
                    )
                  )
            ))]);
    }
  }

Once I've used the Navigate widget to go to the SignIn page, I don't seem to be able to go to the Home screen when I'm logged in successfully.
The authentication works fine, because if I hard refresh the app it shows me the Home screen as a logged in user.

It seems that onPress to authenticate, the IF STATEMENT which chooses between Home and StartAuthButton doesn't get triggered.

Any help is much appreciated. Sorry for the long script. Joe

1
The reason for this behavior is that you are stacking multiple widgets on each other, this will not allow for the Provider to reach it. I'd say all you need to do is pop off of the stack. Take a look at this article: medium.com/flutter-community/flutter-push-pop-push-1bb718b13c31 - Unbreachable
@Frank van Puffelen , I've added Navigator.of(context).maybePop(); to the SignIn button onPress. It does show the Home page once I'm logged in, but when I click to login in the screen flicks back to 'StartAuthButtons` before going to Home. My Loading widget is not even taken under consideration. Any idea to avoid the showing of the StartAuthButtons widget and see the Loading? (below the code as it stands now in the SignIn widget) - Joe
Here the code updated: onPressed: () async { if (_formKey.currentState.validate()) { setState(() => loading = true); Navigator.of(context).maybePop(); etc.... - Joe
It was @Unbreachable who made that comment. I merely edited the tags of your question. - Frank van Puffelen

1 Answers

0
votes

Thanks to @Unbreachable for suggestion to look at the article for pop & push.

I've made a simple change to the SignIn and Register widgets on both submit onPress buttons.

Code before:

onPressed: () async {
   if (_formKey.currentState.validate()) {
     setState(() => loading = true);
                                          
    dynamic result = await _auth.registerWithEmailAndPassword(email, password);

    if (result == null) {
      setState(() {
          error = 'Email or password are invalid.';
          loading = false;
      });
    }
  }
},

and after:

onPressed: () async {
   if (_formKey.currentState.validate()) {
     setState(() => loading = true);
                                          
    dynamic result = await _auth.registerWithEmailAndPassword(email, password);

    if (result == null) {
      setState(() {
          error = 'Email or password are invalid.';
          loading = false;
      });
    } else {
      Navigator.of(context).pop(); <-- here the change. 
      loading = false; <-- here the change.
    }
  }
},

Now the loading works fine and when disappear I can see the home widget.