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()));
}
}
RootPage? If you didn't write this, you might want to download the complete source code from github from the guide that you saw and start from there. It seems like you are jumping too much ahead when there are a lot of things going on in these file set. - user482594