Skip to content

Instantly share code, notes, and snippets.

@slightfoot
Last active June 1, 2024 11:10
Show Gist options
  • Save slightfoot/6f97d6c1ec4eb52ce880c6394adb1386 to your computer and use it in GitHub Desktop.
Save slightfoot/6f97d6c1ec4eb52ce880c6394adb1386 to your computer and use it in GitHub Desktop.
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
Copy link

😎 awesome!

@miguelpruivo
Copy link

Good example! 👌🏻

@slightfoot
Copy link
Author

Good example! 👌🏻

Thanks

@boylenssen
Copy link

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
Copy link
Author

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
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
Copy link
Author

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

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