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
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