Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Firebase Login/Logout Example - by Simon Lightfoot
/*
This example uses the following packages:
firebase_auth: 0.14.0+5
google_sign_in: 4.0.7
provider: 3.1.0+1
Make sure you have setup your project with Firebase by following these instructions:
1. Follow Option 1 instructions here up to Step 3
https://firebase.google.com/docs/android/setup#console
Firebase Console: https://console.firebase.google.com
Be sure to configure your SHA-1 or SHA-256 hash in the
Firebase Project Settings for your app.
If you are wondering what your package-name is you can get it from:
<flutter-project>/android/app/build.gradle labelled applicationId.
2. Place the downloaded 'google-services.json' file from Step 1 above in your
projects <flutter-project>/android/app/ directory.
3. Follow the instructions here to enable the Google Services Gradle Plugin.
https://pub.dev/packages/firebase_auth#android-integration
4. Go to the Firebase Console and then to the Authentication section and then
on to the "Sign-in method" tab an enable Email/Password and Google Sign in methods.
5. Run and enjoy! ... luckily you only have to do this once.
*/
// MIT License
//
// Copyright (c) 2019 Simon Lightfoot
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import 'dart:async';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart' show PlatformException;
import 'package:google_sign_in/google_sign_in.dart';
import 'package:provider/provider.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
// Async main methods show the platform splash screen
// before runApp is called to show the flutter UI.
// So this line waits for Auth.create to finish before
// showing the rest of the app.
runApp(App(auth: await Auth.create()));
}
class App extends StatefulWidget {
const App({
Key key,
@required this.auth,
}) : super(key: key);
final Auth auth;
@override
_AppState createState() => _AppState();
}
class _AppState extends State<App> {
final _navigatorKey = GlobalKey<NavigatorState>();
FirebaseUser currentUser;
@override
void initState() {
super.initState();
currentUser = widget.auth.init(_onUserChanged);
}
void _onUserChanged() {
final user = widget.auth.currentUser.value;
// User logged in
if (currentUser == null && user != null) {
_navigatorKey.currentState.pushAndRemoveUntil(Main.route(), (route) => false);
}
// User logged out
else if (currentUser != null && user == null) {
_navigatorKey.currentState.pushAndRemoveUntil(Login.route(), (route) => false);
}
currentUser = user;
}
@override
void dispose() {
widget.auth.dispose(_onUserChanged);
super.dispose();
}
@override
Widget build(BuildContext context) {
return MultiProvider(
providers: <SingleChildCloneableWidget>[
Provider<Auth>.value(value: widget.auth),
ValueListenableProvider<FirebaseUser>.value(value: widget.auth.currentUser),
],
child: MaterialApp(
title: 'Your App',
theme: ThemeData(
brightness: Brightness.light,
primaryColor: Colors.indigo,
accentColor: Colors.pink,
),
navigatorKey: _navigatorKey,
home: currentUser == null ? const Login() : const Main(),
),
);
}
}
/// Logged out view of the app.
class Login extends StatefulWidget {
static Route<dynamic> route() {
return MaterialPageRoute(
builder: (BuildContext context) => const Login(),
);
}
const Login({
Key key,
}) : super(key: key);
@override
_LoginState createState() => _LoginState();
}
class _LoginState extends State<Login> {
final _formKey = GlobalKey<FormState>();
final _email = TextEditingController();
final _password = TextEditingController();
Future _loginFuture;
void _onLoginWithPasswordPressed() {
if (_formKey.currentState.validate()) {
_formKey.currentState.save();
setState(() {
_loginFuture = Auth.of(context).loginWithEmailAndPassword(_email.text, _password.text);
});
}
}
void _onLoginWithGooglePressed() {
setState(() {
_loginFuture = Auth.of(context).loginWithGoogle();
});
}
String _validateEmail(String value) {
if (value == null || value.trim().isEmpty) {
return 'Please enter an email address.';
} else if (value.contains('@') == false) {
return 'Please check your email address is correct.';
}
return null;
}
String _validatePassword(String value) {
if (value == null || value.trim().isEmpty) {
return 'Please enter your password.';
} else if (value.length < 6) {
return 'Your password needs to be at least 6 characters.';
}
return null;
}
@override
void dispose() {
_email.dispose();
_password.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Theme.of(context).primaryColorLight,
body: SafeArea(
child: SizedBox.expand(
child: Column(
children: <Widget>[
const SizedBox(height: 16.0),
Text(
'Login Screen',
style: Theme.of(context).textTheme.title,
textAlign: TextAlign.center,
),
const SizedBox(height: 36.0),
FutureBuilder(
future: _loginFuture,
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(
child: CircularProgressIndicator(),
);
}
return Form(
key: _formKey,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 32.0),
child: Column(
children: <Widget>[
TextFormField(
controller: _email,
decoration: const InputDecoration(
labelText: 'Email',
),
validator: _validateEmail,
),
TextFormField(
controller: _password,
obscureText: true,
decoration: const InputDecoration(
labelText: 'Password',
),
validator: _validatePassword,
),
const SizedBox(height: 16.0),
RaisedButton(
onPressed: _onLoginWithPasswordPressed,
child: const Text('LOGIN'),
),
const SizedBox(height: 16.0),
RaisedButton(
onPressed: _onLoginWithGooglePressed,
child: const Text('LOGIN WITH GOOGLE'),
),
if (snapshot.hasError)
Container(
width: double.infinity,
margin: const EdgeInsets.symmetric(vertical: 36.0),
padding: const EdgeInsets.all(12.0),
decoration: BoxDecoration(
border: Border.all(color: Theme.of(context).errorColor),
borderRadius: const BorderRadius.all(Radius.circular(2.0)),
color: Theme.of(context).errorColor.withOpacity(0.6),
),
child: Text(
snapshot.error.toString(),
style: TextStyle(
color: Colors.white,
),
textAlign: TextAlign.center,
),
),
],
),
),
);
},
),
],
),
),
),
);
}
}
/// Main logged in view of the app.
class Main extends StatefulWidget {
static Route<dynamic> route() {
return MaterialPageRoute(
builder: (BuildContext context) => const Main(),
);
}
const Main({
Key key,
}) : super(key: key);
@override
_MainState createState() => _MainState();
}
class _MainState extends State<Main> {
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Theme.of(context).primaryColorDark,
body: SafeArea(
child: SizedBox.expand(
child: Column(
children: <Widget>[
const SizedBox(height: 16.0),
Text(
'Main Screen',
style: Theme.of(context).textTheme.title,
textAlign: TextAlign.center,
),
const SizedBox(height: 36.0),
const UserAvatar(),
const SizedBox(height: 16.0),
RaisedButton(
onPressed: () {
Auth.of(context).logout();
},
child: const Text('LOGOUT'),
),
],
),
),
),
);
}
}
/// Displays the user's image
class UserAvatar extends StatelessWidget {
const UserAvatar({
Key key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
final user = Provider.of<FirebaseUser>(context, listen: false);
return Material(
elevation: 4.0,
shape: const CircleBorder(
side: BorderSide(color: Colors.blue, width: 6.0),
),
color: Colors.black,
clipBehavior: Clip.antiAlias,
child: SizedBox(
width: 96.0,
height: 96.0,
child: user?.photoUrl != null
? Image.network(user.photoUrl)
: Icon(
Icons.person,
color: Colors.white,
size: 72.0,
),
),
);
}
}
/// Auth backend
class Auth {
static Future<Auth> create() async {
final currentUser = await FirebaseAuth.instance.currentUser();
return Auth._(currentUser);
}
static Auth of(BuildContext context) {
return Provider.of<Auth>(context, listen: false);
}
Auth._(
FirebaseUser currentUser,
) : this.currentUser = ValueNotifier<FirebaseUser>(currentUser);
final ValueNotifier<FirebaseUser> currentUser;
final _googleSignIn = GoogleSignIn();
final _firebaseAuth = FirebaseAuth.instance;
StreamSubscription<FirebaseUser> _authSub;
FirebaseUser init(VoidCallback onUserChanged) {
currentUser.addListener(onUserChanged);
_authSub = _firebaseAuth.onAuthStateChanged.listen((FirebaseUser user) {
currentUser.value = user;
});
return currentUser.value;
}
void dispose(VoidCallback onUserChanged) {
currentUser.removeListener(onUserChanged);
_authSub.cancel();
}
Future<void> loginWithEmailAndPassword(String email, String password) async {
try {
await _firebaseAuth.signInWithEmailAndPassword(email: email, password: password);
} catch (e, st) {
throw _getAuthException(e, st);
}
}
Future<void> loginWithGoogle() async {
try {
final account = await _googleSignIn.signIn();
if (account == null) {
throw AuthException.cancelled;
}
final auth = await account.authentication;
await _firebaseAuth.signInWithCredential(
GoogleAuthProvider.getCredential(idToken: auth.idToken, accessToken: auth.accessToken),
);
} catch (e, st) {
throw _getAuthException(e, st);
}
}
Future<void> logout() async {
try {
await _firebaseAuth.signOut();
await _googleSignIn.signOut();
} catch (e, st) {
FlutterError.reportError(FlutterErrorDetails(exception: e, stack: st));
}
}
AuthException _getAuthException(dynamic e, StackTrace st) {
if (e is AuthException) {
return e;
}
FlutterError.reportError(FlutterErrorDetails(exception: e, stack: st));
if (e is PlatformException) {
switch (e.code) {
case 'ERROR_INVALID_EMAIL':
throw const AuthException('Please check your email address.');
case 'ERROR_WRONG_PASSWORD':
throw const AuthException('Please check your password.');
case 'ERROR_USER_NOT_FOUND':
throw const AuthException('User not found. Is that the correct email address?');
case 'ERROR_USER_DISABLED':
throw const AuthException('Your account has been disabled. Please contact support');
case 'ERROR_TOO_MANY_REQUESTS':
throw const AuthException('You have tried to login too many times. Please try again later.');
}
}
throw const AuthException('Sorry, an error occurred. Please try again.');
}
}
class AuthException implements Exception {
static const cancelled = AuthException('cancelled');
const AuthException(this.message);
final String message;
@override
String toString() => message;
}
@rodydavis

This comment has been minimized.

Copy link

rodydavis commented Oct 17, 2019

😎 awesome!

@miguelpruivo

This comment has been minimized.

Copy link

miguelpruivo commented Oct 18, 2019

Good example! 👌🏻

@slightfoot

This comment has been minimized.

Copy link
Owner Author

slightfoot commented Oct 18, 2019

Good example! 👌🏻

Thanks

@boylenssen

This comment has been minimized.

Copy link

boylenssen commented Oct 18, 2019

firebase_auth: 0.14.0+7 did not work for me, had to change it to firebase_auth: 0.14.0+5

The 'optional' SHA-1 while registering your app in Firebase is mandatory to fill in, otherwise you get an obscure error

@slightfoot

This comment has been minimized.

Copy link
Owner Author

slightfoot commented Oct 18, 2019

firebase_auth: 0.14.0+7 did not work for me, had to change it to firebase_auth: 0.14.0+5

The 'optional' SHA-1 while registering your app in Firebase is mandatory to fill in, otherwise you get an obscure error

What error did you get with firebase_auth: 0.14.0+7? I did mention the SHA-1 hash. https://gist.github.com/slightfoot/6f97d6c1ec4eb52ce880c6394adb1386#file-main-dart-L15

@boylenssen

This comment has been minimized.

Copy link

boylenssen commented Oct 18, 2019

Ow sorry, I skimmed it to fast, missed the SHA-1 part.
Because <my app name> depends on firebase_auth 0.14.0+7 which doesn't match any versions, version solving failed. pub get failed (1)

By the way, thanks a lot for this example! I've implemented it before in Android and Flutter, but this standalone example is very handy for implementing it again in a new app!

@slightfoot

This comment has been minimized.

Copy link
Owner Author

slightfoot commented Oct 19, 2019

Ow sorry, I skimmed it to fast, missed the SHA-1 part.
Because <my app name> depends on firebase_auth 0.14.0+7 which doesn't match any versions, version solving failed. pub get failed (1)

By the way, thanks a lot for this example! I've implemented it before in Android and Flutter, but this standalone example is very handy for implementing it again in a new app!

Thanks.. fixed it. I actually was using my own fixed branch of firebase_auth which is why the version was different.
https://github.com/slightfoot/flutterfire/blob/auth_reauthenticate_fix/packages/firebase_auth/pubspec.yaml

@HemantTeamswork

This comment has been minimized.

Copy link

HemantTeamswork commented Oct 21, 2019

Nice example!
Simon, is it the way you code for your production ready apps? Or this is only for example? Can you please write a post for the way to write code for scalable apps?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.