Firebase phone login verify using Flutter - firebase

I've managed to send an OTP using Firebase. The number also shows up under Authentication -> Users in Firebase Console. What I need to do is that if the number was verified, I want to navigate to next page. How do I do that?
One more thing is that when I enter the number and tap the button 'Verify', it just sends me an OTP message. The app doesn't prompt for the second TextFormField - "Enter OTP". Why is it doing that?
main.dart
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Authenticate().handleAuth(),
);
}
}
authenticate.dart
class Authenticate {
//to handle auth
handleAuth() {
return StreamBuilder(
stream: FirebaseAuth.instance.onAuthStateChanged,
builder: (BuildContext context, snapshot) {
if (snapshot.hasData) {
return DashBoard();
} else {
return SignIn();
}
},
);
}
//for signin
signIn(AuthCredential authCreds) {
FirebaseAuth.instance.signInWithCredential(authCreds);
}
//for manual otp entry
signInViaOTP(smsCode, verId) {
AuthCredential authCredential = PhoneAuthProvider.getCredential(
verificationId: verId, smsCode: smsCode);
signIn(authCredential);
}
signInViaOTPT(smsCode, verId) {
AuthCredential authCredential = PhoneAuthProvider.getCredential(
verificationId: verId, smsCode: smsCode);
signIn(authCredential);
}
//for signout
signout() {
FirebaseAuth.instance.signOut();
}
}
sign_in.dart
class SignIn extends StatefulWidget {
#override
_SignInState createState() => _SignInState();
}
class _SignInState extends State<SignIn> {
final formKey = new GlobalKey<FormState>();
String phoneNo, verId, smsCode;
bool codeSent = false;
Future<void> verifyNum(num) async{
final PhoneVerificationCompleted verified = (AuthCredential authRes){
Authenticate().signIn(authRes);
};
final PhoneVerificationFailed failed = (AuthException authExcep){
};
final PhoneCodeSent smsSent = (String verId, [int forceResend]){
this.verId = verId;
setState(() {
this.codeSent = true;
});
};
final PhoneCodeAutoRetrievalTimeout autoTimeOut = (String verId){
this.verId = verId;
};
await FirebaseAuth.instance.verifyPhoneNumber(
phoneNumber: num,
timeout: const Duration(seconds: 5),
verificationCompleted: verified,
verificationFailed: failed,
codeSent: smsSent,
codeAutoRetrievalTimeout: autoTimeOut
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Form(
key: formKey,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Padding(
padding: EdgeInsets.only(left: 25.0, right: 25.0),
child: TextFormField(
keyboardType: TextInputType.phone,
decoration: InputDecoration(hintText: "Enter Phone Number"),
onChanged: (val) {
setState(() {
this.phoneNo = val;
});
},
),
),
codeSent ? Padding(
padding: EdgeInsets.only(left: 25.0, right: 25.0),
child: TextFormField(
keyboardType: TextInputType.phone,
decoration: InputDecoration(hintText: "Enter OTP"),
onChanged: (val) {
setState(() {
this.smsCode = val;
});
},
),
) : Container(),
Padding(
padding: EdgeInsets.only(left: 25.0, right: 25.0),
child: RaisedButton(
child: Center(
child: codeSent ? Text("Login") : Text("Verify"),
),
onPressed: (){
codeSent? Authenticate().signInViaOTP(smsCode, verId) : verifyNum(phoneNo);
},
),
)
],
),
),
);
}
}

What I need to do is that if the number was verified, I want to navigate to next page. How do I do that?
You'd need to listen for a successful user authentication, then let the user proceed to the next page.
handlePhoneAuth(phoneNumber).then((User user) {
if(user != null) {
NavigateToNextPage();
} else {
// Display errors
}
});
when I enter the number and tap the button 'Verify', it just sends me an OTP message. The app doesn't prompt for the second TextFormField - "Enter OTP". Why is it doing that?
You need to build your own UI for handling the OTP sent to the user's phone number. Related to the above code snippet, here's an example on how you can handle phone authentication using firebase_auth.
Future<User> handlePhoneAuth(String userPhoneNumber) async {
var auth = FirebaseAuth.instance;
auth.verifyPhoneNumber(
phoneNumber: userPhoneNumber,
codeSent: (String verificationId, int resendToken) async {
// Update the UI - wait for the user to enter the SMS code
String smsCode = 'xxxx';
// Create a PhoneAuthCredential with the code
PhoneAuthCredential credential = PhoneAuthProvider.credential(
verificationId: verificationId, smsCode: smsCode);
// Sign the user in (or link) with the credential
var userCredential = await auth.signInWithCredential(credential);
return userCredential.user;
},
verificationFailed: (FirebaseAuthException error) {
debugPrint('Phone Auth failed $error');
return null;
},
verificationCompleted: (PhoneAuthCredential phoneAuthCredential) {
// only available for Android
},
codeAutoRetrievalTimeout: (String verificationId) {
return null;
},
);
}

Related

Flutter : Firebase PhoneAuthentication Problem

I have just developed an app which requires phone authentication. Inside login screen I can able to achieve to login via phone. But my concern is : for the first time when I enter phone number and enter verification number it comes back to login which in reality expected to navigate to homescreen. For the second try system is able to work and navigate to home screen as expected. Here is my code block. I am wondering which part of the code I make mistake since login info pop back again and system is able to navigate to home screen after second try:
My code block :
class _LoginScreenState extends State<LoginScreen> {
String phoneNo, smssent, verificationId;
get verifiedSuccess => null;
Future<void> verifyPhone() async {
final PhoneCodeAutoRetrievalTimeout autoRetrieve = (String verId) {
this.verificationId = verId;
};
final PhoneCodeSent smsCodeSent = (String verId, [int forceCodeResent]) {
this.verificationId = verId;
smsCodeDialoge(context).then((value) {
print("Doğrulama Kodu Gönderildi");
});
};
final PhoneVerificationCompleted verifiedSuccess = (AuthCredential auth) {};
final PhoneVerificationFailed verifyFailed = (AuthException e) {
print('${e.message}');
};
await FirebaseAuth.instance.verifyPhoneNumber(
phoneNumber: phoneNo,
timeout: const Duration(seconds: 5),
verificationCompleted: verifiedSuccess,
verificationFailed: verifyFailed,
codeSent: smsCodeSent,
codeAutoRetrievalTimeout: autoRetrieve,
);
}
Future<bool> smsCodeDialoge(BuildContext context) {
return showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return new AlertDialog(
title: Text('Doğrulama Kodunu Giriniz'),
content: TextField(
onChanged: (value) {
this.smssent = value;
},
),
contentPadding: EdgeInsets.all(10.0),
actions: <Widget>[
FlatButton(
onPressed: () {
FirebaseAuth.instance.currentUser().then((user) {
if (user != null) {
Navigator.of(context).pop();
Navigator.push(
context,
MaterialPageRoute(builder: (context) => HomeScreen()),
);
} else {
Navigator.of(context).pop();
signIn(smssent);
}
});
},
child: Text(
'Doğrulama Yap',
style: TextStyle(color: Colors.blue),
),
),
],
);
});
}
Future<void> signIn(String smsCode) async {
final AuthCredential credential = PhoneAuthProvider.getCredential(
verificationId: verificationId,
smsCode: smsCode,
);
await FirebaseAuth.instance.signInWithCredential(credential).then((user)
{
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => LoginScreen(),
),
);
}).catchError((e) {
print(e);
});
}
if (user != null) {
Navigator.of(context).pop();
Navigator.push(
context,
MaterialPageRoute(builder: (context) => HomeScreen()),
);
Here you are calling pop, which will take you to the previous screen if the user is not null, and after that, you are pushing the HomeScreen into the stack. Try not poping and just pushing, by the way, are you using routes and streamprovider in your code?

Firebase_auth photoURL not working. Returning default person image

flutter 1.20.4;
flutter_login_facebook: ^0.2.1;
firebase_auth: ^0.18.1;
firebase_core: ^0.5.0;
provider: ^4.3.2;
Display name and email is working, but photoURL returns this:
I tried to build it on different devices and tried to login to different facebook account.
https://graph.facebook.com/1757577617747458/picture
My HomeScreen
class _HomeState extends State<Home> {
StreamSubscription<User> homeStateSubscription;
#override
void initState() {
var authBloc = Provider.of<AuthBloc>(context, listen: false);
homeStateSubscription = authBloc.currentUser.listen((fbUser) {
if (fbUser == null) {
Navigator.of(context)
.pushReplacement(MaterialPageRoute(builder: (context) => Login()));
}
});
super.initState();
}
#override
Widget build(BuildContext context) {
var authBloc = Provider.of<AuthBloc>(context);
return Scaffold(
body: Center(
child: StreamBuilder<User>(
stream: authBloc.currentUser,
builder: (context, snapshot) {
if (!snapshot.hasData) return CircularProgressIndicator();
print(snapshot.data.email);
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(snapshot.data.displayName,
style: TextStyle(fontSize: 35.0)),
SizedBox(
height: 20.0,
),
CircleAvatar(
backgroundImage: NetworkImage(
snapshot.data.photoURL + '?width=500&height500'),
radius: 60.0,
),
SizedBox(
height: 100.0,
),
SignInButton(Buttons.Facebook,
text: 'Sign out of Facebook',
onPressed: () => authBloc.logout())
],
);
}),
));
}
}
This is Bloc
class AuthBloc {
final authService = AuthService();
final fb = FacebookLogin();
Stream<User> get currentUser => authService.currentUser;
loginFacebook() async {
final res = await fb.logIn(permissions: [
FacebookPermission.publicProfile,
FacebookPermission.email
]);
switch (res.status) {
case FacebookLoginStatus.Success:
print('It worked');
//Get Token
final FacebookAccessToken fbToken = res.accessToken;
//Convert to Auth Credential
final AuthCredential credential =
FacebookAuthProvider.credential(fbToken.token);
//User Credential to Sign in with Firebase
final result = await authService.signInWithCredentail(credential);
print('${result.user.displayName} is now logged in');
break;
case FacebookLoginStatus.Cancel:
print('The user canceled the login');
break;
case FacebookLoginStatus.Error:
print('There was an error');
break;
}
}
logout() {
authService.logout();
}
}
My Auth Service
class AuthService {
final _auth = FirebaseAuth.instance;
Stream<User> get currentUser => _auth.authStateChanges();
Future<UserCredential> signInWithCredentail(AuthCredential credential) =>
_auth.signInWithCredential(credential);
Future<void> logout() => _auth.signOut();
}
I had the same problem. It seems that FB changed the way to get the profile picture.
Source: https://developers.facebook.com/docs/graph-api/reference/user/picture/
"Apps in Development mode that make tokenless requests on ASIDs will receive a silhouette image in response."
Using the token after sign in with Facebook can be used to retrieve image from graph API. The problem is that I needed to download the image and show up in profile widget.

Navigate to a page after user sign up in the android app in flutter using FirebaseAuth

I am working on phone authentication using flutter and firebase. So, when a user 'Register or Sign Up' for the app an OTP is received and then when he clicks the 'Login' button the phone number gets saved in the firebase but the page does not get loaded. After clicking the Login button, the account gets created but the page doesn't change and I have to close the app and open it again and the main page gets displayed. Please, tell me how to do it.
Code for "Class SignUpView" :
class SignUpView extends StatefulWidget {
#override
_SignUpViewState createState() => _SignUpViewState();
}
class _SignUpViewState extends State<SignUpView> {
final formKey = new GlobalKey<FormState>();
String phoneNo, verificationId, smsCode;
bool codeSent = false;
#override
Widget build(BuildContext context) {
return Scaffold(
body: Form(
key: formKey,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Padding(
padding: EdgeInsets.only(left: 25, right: 25),
child: TextFormField(
keyboardType: TextInputType.phone,
decoration: InputDecoration(hintText: "Enter Phone Number"),
onChanged: (val) {
setState(() {
this.phoneNo = val;
});
},
),
),
codeSent ? Padding(
padding: EdgeInsets.only(left: 25, right: 25),
child: TextFormField(
keyboardType: TextInputType.phone,
decoration: InputDecoration(hintText: "Enter OTP"),
onChanged: (val) {
setState(() {
this.smsCode = val;
});
},
),
): Container(),
Padding(
padding: EdgeInsets.only(left: 25, right: 25),
child: RaisedButton(
child: Center(
child: codeSent ? Text("Login") : Text("Login"),
),
onPressed: () {
codeSent? AuthService().signInWithOTP(smsCode, verificationId):verifyPhone(phoneNo);
},
),
),
],
),
),
);
}
Future<void> verifyPhone(phoneNo) async {
final PhoneVerificationCompleted verified = (AuthCredential authResult) {
AuthService().signIn(authResult);
};
final PhoneVerificationFailed verificationFailed = (
AuthException authException) {
print('${authException.message}');
};
final PhoneCodeSent smsSent = (String verId, [int forceResend]) {
this.verificationId = verId;
setState(() {
this.codeSent = true;
});
};
final PhoneCodeAutoRetrievalTimeout autoTimeOut = (String verId) {
this.verificationId = verId;
};
await FirebaseAuth.instance.verifyPhoneNumber(
phoneNumber: phoneNo,
timeout: const Duration(seconds: 5),
verificationCompleted: verified,
verificationFailed: verificationFailed,
codeSent: smsSent,
codeAutoRetrievalTimeout: autoTimeOut);
}
}
The part of code where I need to add the navigation is:
onPressed: () {
codeSent? AuthService().signInWithOTP(smsCode, verificationId):verifyPhone(phoneNo);
}
There is another part of code- Class AuthService:
class AuthService {
handleAuth() {
return StreamBuilder(
stream: FirebaseAuth.instance.onAuthStateChanged,
builder: (BuildContext context, snapshot) {
if(snapshot.hasData) {
return Home_Page();
}
else {
return first_screen(); //Login();
}
},
);
}
signOut() {
FirebaseAuth.instance.signOut();
}
signIn(AuthCredential authCreds) {
if(authCreds != null){
FirebaseAuth.instance.signInWithCredential(authCreds);
}
}
signInWithOTP(smsCode, verId) {
AuthCredential authCreds = PhoneAuthProvider.getCredential(verificationId: verId, smsCode: smsCode);
signIn(authCreds);
}
}
I tried to add navigation inside:
onPressed: () {
codeSent? AuthService().signInWithOTP(smsCode, verificationId):verifyPhone(phoneNo);
Navigator.of(context).pushReplacementNamed('/create_account');
}
But this didn't worked as the above code would navigate to the page and the account won't be created.
I want that when the user type the OTP and then click the Login button, then his phone number should get verified and account should be created on firebase and then the user should be displayed another page. You can either use: Navigator.of(context).pushReplacementNamed('/create_account'); or Account_setup_page() for displaying the page.
I'd really be thankful for all the help I can get.
First of all you need to setup a stream for authentication changes in your AuthService class, i.e.
Stream<FirebaseUser> get user {
return _auth.onAuthStateChanged;
}
Then in your home screen (better in a wrapper widget) you could listen to that stream, so if the user is logged in, it will be redirected to the home screen, else it will be redirected to the sign in screen. This can be accomplished with the following code:
final user = Provider.of<FirebaseUser>(context); // listener for auth state
if (user == null) {
return SignUpView(); // or the sign in view
} else {
return Home(user: user,);
// home screen with the user as argument for easier access in the future
}
Maybe in the future when you will implement the sign out feature, this piece of code will automatically redirect the user to the sign in page

How to make register using email/password and send Code to the phone in Firebase

I want to allow users to register an account on my app using flutter with email/password, and then send code to the phone. So when user enters their data to Sign up, it should register them with their email and then use their phone to the verity code. Also the user can't complete the registration and go to the Home page Without checking the entered code.
But in my code it's not working what i want do it.
My click function:
dynamic ruselt= await _auth.regsiterwithemail(_emailcontroller.text,_passwordcontroller.text);
if (ruselt!=null){
setState(() async {
loading =false;
verfitycode().verfityphoen(context);
});
}else{
setState(() {
loading=false;
SweetAlert.show(context,
title: "Title",
style: SweetAlertStyle.confirm,
subtitle: "Subtitle");
});
}
My registration function:
Future regsiterwithemail(String email , String password )
async {
try{
AuthResult result =await _auth.createUserWithEmailAndPassword(email: email, password: password);
FirebaseUser user =result.user;
print('oky');
return _userfirebaseUser(user);
}catch(e){
print(e.toString());
return null;
}
}
class for verity code
class verfitycode {
String phonenumber;
String smsCode;
String vialdid;
GlobalKey<FormState>_form;
Future<void> verfityphoen(BuildContext context)async{
final PhoneCodeAutoRetrievalTimeout AutoRetriv =(String verid) {
this.vialdid =verid;
};
final PhoneCodeSent smsCodeset =(String verid,[int forceResendingToken]){
this.vialdid=verid;
smscodeDialog(context);
};
final PhoneVerificationCompleted verfiedcompletd=(AuthCredential user){
print('verfild');
};
final PhoneVerificationFailed verfilederror =(AuthException exception){
print('${exception.message}');
};
await FirebaseAuth.instance.verifyPhoneNumber(
phoneNumber:"+967776523152",
timeout: const Duration(seconds:5),
verificationCompleted: verfiedcompletd,
verificationFailed: verfilederror,
codeSent: smsCodeset,
codeAutoRetrievalTimeout: AutoRetriv
);
}
Future<bool> smscodeDialog(BuildContext context){
Alert(
context: context,
title: "رمز التحقق",
content: Column(
children: <Widget>[
Form(
key: _form,
child: Directionality(
textDirection: TextDirection.rtl,
child:
TextField(
onChanged: (val){
this.smsCode=val;
},
decoration:InputDecoration (
icon: Icon(Icons.supervisor_account),
labelText: 'ادخل رمز التحقق',
),
),
)
)
],
),
buttons: [
DialogButton(
child: Text('خروج', style: TextStyle(color: Colors.white, fontSize:18)), onPressed:()=>Navigator.pop(context)),
DialogButton(
onPressed: (){
// _form.currentState.validate();
FirebaseAuth.instance.currentUser().then((user) {
if (user != null) {
Navigator.pop(context);
Navigator.push(context,
MaterialPageRoute(builder: (context) => Home()));
} else {
Navigator.pop(context);
_testSignlink();
}
});
},
child: Text(
"ادخل الرمز",
style: TextStyle(color: Colors.white, fontSize:18),
),
),
]).show();
}
_testSignlink() async {
FirebaseUser user;
String _smsCodeController;
final AuthCredential credential = PhoneAuthProvider.getCredential(
verificationId: vialdid,
smsCode: smsCode,
);
await user.linkWithCredential(credential).then((user){
print(user.user.uid);
}).catchError((onError){
print(onError.toString());
});
_smsCodeController = '';
return 'signInWithPhoneNumber succeeded: $user';
}
}

flutter firebase google sign out not working

I'm trying to implement google sign in/ sign out, but my sign out's not working. I get an error signOut() was called on null. When I print out user after the user signs in, I do get all the proper information, but when I print out in my signout function, it says it's null. Custom firebase user does work. Here's my auth.dart file:
import 'dart:async';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:google_sign_in/google_sign_in.dart';
abstract class BaseAuth {
Future<String> signIn(String email, String password);
Future<String> signUp(String email, String password);
Future<FirebaseUser> getCurrentUser();
Future<void> sendEmailVerification();
Future<void> signOut();
Future<bool> isEmailVerified();
Future<String> signInWithGoogle();
}
class Auth implements BaseAuth {
final FirebaseAuth _firebaseAuth = FirebaseAuth.instance;
final GoogleSignIn googleSignIn = GoogleSignIn();
FirebaseUser user;
Future<String> signIn(String email, String password) async {
user = (await _firebaseAuth.signInWithEmailAndPassword(email: email, password: password)).user;
return user.email;
}
Future<String> signUp(String email, String password) async {
FirebaseUser user = (await _firebaseAuth.createUserWithEmailAndPassword(
email: email, password: password)).user;
return user.uid;
}
Future<FirebaseUser> getCurrentUser() async {
user = await _firebaseAuth.currentUser();
return user;
}
signOut() async {
//print("signed in user: ${authService.user}");
await _firebaseAuth.signOut();
}
Future<void> sendEmailVerification() async {
FirebaseUser user = await _firebaseAuth.currentUser();
user.sendEmailVerification();
}
Future<bool> isEmailVerified() async {
FirebaseUser user = await _firebaseAuth.currentUser();
return user.isEmailVerified;
}
Future<String> signInWithGoogle() async {
final GoogleSignInAccount googleSignInAccount = await googleSignIn.signIn();
final GoogleSignInAuthentication googleSignInAuthentication =
await googleSignInAccount.authentication;
final AuthCredential credential = GoogleAuthProvider.getCredential(
accessToken: googleSignInAuthentication.accessToken,
idToken: googleSignInAuthentication.idToken,
);
user = (await _firebaseAuth.signInWithCredential(credential)).user;
assert(!user.isAnonymous);
assert(await user.getIdToken() != null);
final FirebaseUser currentUser = await _firebaseAuth.currentUser();
assert(user.uid == currentUser.uid);
return 'signInWithGoogle succeeded: $user';
}
}
Something else strange is if I boot up the app and am already logged in (as google), i hit signout and it appears to work. Nothing in my console, but it goes back to login screen. Then if I log back in as google and signout, error starts happening. Another weird thing is if I happened to already be logged in and I get it to log out after one click, without changing anything if I restart my app in android studio it takes me back to the screen where I'm supposedly already logged in. This is only happening on Google Sign Out (not firebase log out).
Any idea what I might be doing wrong? Thanks
pubspec.yaml
dependencies:
firebase_auth: ^0.14.0+5
firebase_database: ^3.0.7
google_sign_in: ^4.0.7
firebase_storage:
image_picker:
cloud_firestore:
shared_preferences:
fluttertoast:
cached_network_image:
intl:
one page where I use it: (I have many, but implemented similar to this and it doesn't work in any of my pages)
import 'package:flutter/material.dart';
import 'package:pet_helper/chat.dart';
import 'package:pet_helper/lost_and_found.dart';
import 'package:pet_helper/pet_adoption.dart';
import 'authentication.dart';
class HomePage extends StatefulWidget {
HomePage({Key key, this.auth, this.userId, this.onSignedOut})
: super(key: key);
final BaseAuth auth;
final VoidCallback onSignedOut;
final String userId;
#override
State<StatefulWidget> createState() => new _HomePageState();
}
class _HomePageState extends State<HomePage> {
final GlobalKey<FormState> formKey = GlobalKey<FormState>();
int _currentIndex = 0;
#override
void initState() {
super.initState();
}
final List<Widget> _children = [
new LostAndFoundPage(),
new PetAdoptionPage(),
new ChatPage(),
];
_signOut() async {
try {
await widget.auth.signOut();
widget.onSignedOut();
} catch (e) {
print(e);
}
}
onTabTapped(int index) {
setState(() {
_currentIndex = index;
});
}
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
automaticallyImplyLeading:false,
title: new Text('Pet Helper'),
actions: <Widget>[
new FlatButton(
child: new Text('Logout',
style: new TextStyle(fontSize: 17.0, color: Colors.white)),
onPressed: _signOut)
],
),
body: _children[_currentIndex], // new
bottomNavigationBar: BottomNavigationBar(
onTap: onTabTapped, // new
currentIndex: _currentIndex, // new
items: [
new BottomNavigationBarItem(
icon: Icon(Icons.home),
title: Text('Lost & Found'),
),
new BottomNavigationBarItem(
icon: Icon(Icons.pets),
title: Text('Pet Adoption'),
),
new BottomNavigationBarItem(
icon: Icon(Icons.person), title: Text('Chat'))
],
));
}
}
and for completion, here's my login page:
import 'package:flutter/material.dart';
import 'package:pet_helper/home_page.dart';
import 'authentication.dart';
class LoginSignUpPage extends StatefulWidget {
LoginSignUpPage({this.auth, this.onSignedIn});
final BaseAuth auth;
final VoidCallback onSignedIn;
#override
State<StatefulWidget> createState() => new _LoginSignUpPageState();
}
enum FormMode { LOGIN, SIGNUP }
class _LoginSignUpPageState extends State<LoginSignUpPage> {
final _formKey = new GlobalKey<FormState>();
String _email;
String _password;
String _errorMessage;
// Initial form is login form
FormMode _formMode = FormMode.LOGIN;
bool _isIos;
bool _isLoading;
// Check if form is valid before perform login or signup
bool _validateAndSave() {
final form = _formKey.currentState;
if (form.validate()) {
form.save();
return true;
}
return false;
}
// Perform login or signup
void _validateAndSubmit() async {
setState(() {
_errorMessage = "";
_isLoading = true;
});
if (_validateAndSave()) {
String userId = "";
try {
if (_formMode == FormMode.LOGIN) {
userId = await widget.auth.signIn(_email, _password);
print('Signed in: $userId');
} else {
userId = await widget.auth.signUp(_email, _password);
widget.auth.sendEmailVerification();
_showVerifyEmailSentDialog();
print('Signed up user: $userId');
}
setState(() {
_isLoading = false;
});
if (userId.length > 0 && userId != null && _formMode == FormMode.LOGIN) {
widget.onSignedIn();
}
} catch (e) {
print('Error: $e');
setState(() {
_isLoading = false;
if (_isIos) {
_errorMessage = e.details;
} else
_errorMessage = 'Incorrect user or password';
});
}
}
}
#override
void initState() {
_errorMessage = "";
_isLoading = false;
super.initState();
}
void _changeFormToSignUp() {
_formKey.currentState.reset();
_errorMessage = "";
setState(() {
_formMode = FormMode.SIGNUP;
});
}
void _changeFormToLogin() {
_formKey.currentState.reset();
_errorMessage = "";
setState(() {
_formMode = FormMode.LOGIN;
});
}
#override
Widget build(BuildContext context) {
_isIos = Theme.of(context).platform == TargetPlatform.iOS;
return new Scaffold(
appBar: new AppBar(
title: new Text('Pet Helper'),
),
body: Stack(
children: <Widget>[
_showBody(),
_showCircularProgress(),
],
));
}
Widget _showCircularProgress(){
if (_isLoading) {
return Center(child: CircularProgressIndicator());
} return Container(height: 0.0, width: 0.0,);
}
void _showVerifyEmailSentDialog() {
showDialog(
context: context,
builder: (BuildContext context) {
// return object of type Dialog
return AlertDialog(
title: new Text("Verify your account"),
content: new Text("Link to verify account has been sent to your email"),
actions: <Widget>[
new FlatButton(
child: new Text("Dismiss"),
onPressed: () {
_changeFormToLogin();
Navigator.of(context).pop();
},
),
],
);
},
);
}
Widget _showBody(){
return new Container(
padding: EdgeInsets.all(16.0),
child: new Form(
key: _formKey,
child: new ListView(
shrinkWrap: true,
children: <Widget>[
_showLogo(),
_showEmailInput(),
_showPasswordInput(),
_showErrorMessage(),
_showPrimaryButton(),
_showSecondaryButton(),
_googleSignInButton(),
],
),
));
}
Widget _showErrorMessage() {
if (_errorMessage.length > 0 && _errorMessage != null) {
return new Text(
_errorMessage,
style: TextStyle(
fontSize: 13.0,
color: Colors.red,
height: 1.0,
fontWeight: FontWeight.w300),
);
} else {
return new Container(
height: 0.0,
);
}
}
Widget _showLogo() {
return new Hero(
tag: 'hero',
child: Padding(
padding: EdgeInsets.fromLTRB(0.0, 30.0, 0.0, 0.0),
child: CircleAvatar(
backgroundColor: Colors.transparent,
radius: 120.0,
child: Image.asset('assets/babies.png'),
),
),
);
}
Widget _showEmailInput() {
return Padding(
padding: const EdgeInsets.fromLTRB(0.0, 25.0, 0.0, 0.0),
child: new TextFormField(
maxLines: 1,
keyboardType: TextInputType.emailAddress,
autofocus: false,
decoration: new InputDecoration(
hintText: 'Email',
icon: new Icon(
Icons.mail,
color: Colors.grey,
)),
validator: (String value) {
if (value.isEmpty) {
_isLoading = false;
return 'Email can\'t be empty';
}
else{
return null;
}
},
onSaved: (value) => _email = value.trim(),
),
);
}
Widget _showPasswordInput() {
return Padding(
padding: const EdgeInsets.fromLTRB(0.0, 15.0, 0.0, 0.0),
child: new TextFormField(
maxLines: 1,
obscureText: true,
autofocus: false,
decoration: new InputDecoration(
hintText: 'Password',
icon: new Icon(
Icons.lock,
color: Colors.grey,
)),
validator: (String value) {
if (value.isEmpty) {
_isLoading = false;
return 'Password can\'t be empty';
}
else{
return null;
}
},
onSaved: (value) => _password = value.trim(),
),
);
}
Widget _showSecondaryButton() {
return new FlatButton(
child: _formMode == FormMode.LOGIN
? new Text('Create an account',
style: new TextStyle(fontSize: 18.0, fontWeight: FontWeight.w300))
: new Text('Have an account? Sign in',
style:
new TextStyle(fontSize: 18.0, fontWeight: FontWeight.w300)),
onPressed: _formMode == FormMode.LOGIN
? _changeFormToSignUp
: _changeFormToLogin,
);
}
Widget _showPrimaryButton() {
return new Padding(
padding: EdgeInsets.fromLTRB(0.0, 35.0, 0.0, 0.0),
child: SizedBox(
height: 40.0,
child: new RaisedButton(
elevation: 5.0,
shape: new RoundedRectangleBorder(borderRadius: new BorderRadius.circular(30.0)),
color: Colors.blue,
child: _formMode == FormMode.LOGIN
? new Text('Login',
style: new TextStyle(fontSize: 20.0, color: Colors.white))
: new Text('Create account',
style: new TextStyle(fontSize: 20.0, color: Colors.white)),
onPressed: _validateAndSubmit,
),
));
}
void submitGoogleLogin() async{
setState(() {
_errorMessage = "";
_isLoading = true;
});
String userId = "";
userId = await widget.auth.signInWithGoogle().whenComplete(() {
widget.onSignedIn();
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) {
return new HomePage();
},
),
);
});
print('Signed in: $userId');
}
Widget _googleSignInButton() {
return OutlineButton(
splashColor: Colors.grey,
onPressed: submitGoogleLogin,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(40)),
highlightElevation: 0,
borderSide: BorderSide(color: Colors.grey),
child: Padding(
padding: const EdgeInsets.fromLTRB(0, 10, 0, 10),
child: Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Image(image: AssetImage("assets/google_logo.png"), height: 30.0),
Padding(
padding: const EdgeInsets.only(left: 10),
child: Text(
'Sign in with Google',
style: TextStyle(
fontSize: 15,
color: Colors.grey,
),
),
)
],
),
),
);
}
}
Oh and root page changes the state of the user:
import 'package:flutter/material.dart';
import 'login_signup_page.dart';
import 'authentication.dart';
import 'home_page.dart';
class RootPage extends StatefulWidget {
RootPage({this.auth});
final BaseAuth auth;
#override
State<StatefulWidget> createState() => new _RootPageState();
}
enum AuthStatus {
NOT_DETERMINED,
NOT_LOGGED_IN,
LOGGED_IN,
}
class _RootPageState extends State<RootPage> {
AuthStatus authStatus = AuthStatus.NOT_DETERMINED;
String _userId = "";
#override
void initState() {
super.initState();
widget.auth.getCurrentUser().then((user) {
setState(() {
if (user != null) {
_userId = user?.uid;
}
authStatus =
user?.uid == null ? AuthStatus.NOT_LOGGED_IN : AuthStatus.LOGGED_IN;
});
});
}
void _onLoggedIn() {
widget.auth.getCurrentUser().then((user){
setState(() {
_userId = user.uid.toString();
});
});
setState(() {
authStatus = AuthStatus.LOGGED_IN;
});
}
void _onSignedOut() {
setState(() {
authStatus = AuthStatus.NOT_LOGGED_IN;
_userId = "";
});
}
Widget _buildWaitingScreen() {
return Scaffold(
body: Container(
alignment: Alignment.center,
child: CircularProgressIndicator(),
),
);
}
#override
Widget build(BuildContext context) {
switch (authStatus) {
case AuthStatus.NOT_DETERMINED:
return _buildWaitingScreen();
break;
case AuthStatus.NOT_LOGGED_IN:
return new LoginSignUpPage(
auth: widget.auth,
onSignedIn: _onLoggedIn,
);
break;
case AuthStatus.LOGGED_IN:
if (_userId.length > 0 && _userId != null) {
return new HomePage(
userId: _userId,
auth: widget.auth,
onSignedOut: _onSignedOut,
);
} else return _buildWaitingScreen();
break;
default:
return _buildWaitingScreen();
}
}
}
initializing root page
Main.dart
void main() async{
runApp(new MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Pet Helper',
debugShowCheckedModeBanner: false,
theme: new ThemeData(
primarySwatch: Colors.blue,
),
home: new RootPage(auth: new Auth()));
}
}
abstract class BaseAuth {
Future<String> signIn(String email, String password);
Future<String> signUp(String email, String password);
Future<FirebaseUser> getCurrentUser();
Future<void> sendEmailVerification();
Future<void> signOut();
Future<bool> isEmailVerified();
Future<String> signInWithGoogle();
void signOutGoogle();
}
class Auth implements BaseAuth {
final FirebaseAuth _firebaseAuth = FirebaseAuth.instance;
final GoogleSignIn googleSignIn = GoogleSignIn();
FirebaseUser user;
Future<String> signIn(String email, String password) async {
user = (await _firebaseAuth.signInWithEmailAndPassword(email: email, password: password)).user;
return user.email;
}
Future<String> signUp(String email, String password) async {
FirebaseUser user = (await _firebaseAuth.createUserWithEmailAndPassword(
email: email, password: password)).user;
return user.uid;
}
Future<FirebaseUser> getCurrentUser() async {
user = await _firebaseAuth.currentUser();
return user;
}
signOut() async {
print("signed in user: $user");
await _firebaseAuth.signOut();
}
Future<void> sendEmailVerification() async {
FirebaseUser user = await _firebaseAuth.currentUser();
user.sendEmailVerification();
}
Future<bool> isEmailVerified() async {
FirebaseUser user = await _firebaseAuth.currentUser();
return user.isEmailVerified;
}
Future<String> signInWithGoogle() async {
final GoogleSignInAccount googleSignInAccount = await googleSignIn.signIn();
final GoogleSignInAuthentication googleSignInAuthentication =
await googleSignInAccount.authentication;
final AuthCredential credential = GoogleAuthProvider.getCredential(
accessToken: googleSignInAuthentication.accessToken,
idToken: googleSignInAuthentication.idToken,
);
user = (await _firebaseAuth.signInWithCredential(credential)).user;
assert(!user.isAnonymous);
assert(await user.getIdToken() != null);
final FirebaseUser currentUser = await _firebaseAuth.currentUser();
assert(user.uid == currentUser.uid);
return 'signInWithGoogle succeeded: $user';
}
}
final Auth authService = Auth(); // add this to the bottom outside the class
Using a global variable
So let me explain. Each time you call Auth(), it creates a new instance of that class. So in one instance, you might sign the user in. In another instance, you might sign the user out. The variables will contain different values for the variables. So if you use a different instance for signing users out than you did for signing them in, the user variable will be null, therefore not allowing you to sign them out and printing null.
Solution
The solution is a global variable to access the Auth class once. Ex. var authService = Auth();
Put the variable outside the Auth class so it can be accessed in any class, anywhere
Thanks everyone for you answers and support. I found the problem. When signing in google, widget.onSignedIn redirects me to home page (it's in root page). I was redirecting to homepage in the google sign in button after calling on SignedIn which is how I was losing the scope of the user variable. Thanks everyone!
in my case I was getting the Error:"Null check operator used on null value" whenever I call the signout function through the button which was wraped with GetBuilder.
so the solution was that I had to initiate the controller before I call it like:
GetBuilder<LoginController>(
init: LoginController(),// **I was missing this part**
builder: (controller) {
return Column(...)});

Resources