EasyAuth
Widgets and classes that make it easy to add authentication to any Flutter app. Built on top of the package:bloc architecture, it is fully authentication framework agnostic but provides some plug-and-play mechanisms for commonly used frameworks like package:firebase_auth.
Lets take a look at how to integrate a basic Firebase Auth state to your app. For other examples, check the examples folder.
First, we create a basic MaterialApp (or any other app you might use) and initialize the default Firebase App:
// -> main.dart
void main() async {
WidgetsFlutterBinding.ensureInitialized();
GestureBinding.instance!.resamplingEnabled = true;
await Firebase.initializeApp();
runApp(
MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(primarySwatch: Colors.blue),
home: const MyApp(),
),
);
}
We then create our MyApp
widget that extends AuthenticationBasedApp
:
// -> main.dart
class MyApp extends AuthenticationBasedApp<EquatableUser> {
const MyApp({Key? key}) : super(key: key);
@override
BasicFirebaseAuth get repository => BasicFirebaseAuth();
@override
Widget buildState(BuildContext context, AuthStatus status, EquatableUser user) {
switch (status) {
case AuthStatus.uninitialized:
return const SplashScreenView();
case AuthStatus.authenticated:
return const HomeView();
case AuthStatus.newAccount:
return const HomeView.newAccount();
case AuthStatus.authenticating:
case AuthStatus.unauthenticated:
return const LoginView();
}
}
}
That's it, you're done! Now you can use EasyAuth
as you would use FirebaseAuth
and login, signout, create accounts, etc.
Notice that we passed the EquatableUser
class as a generic to AuthenticationBasedApp
, let's talk about why.
This class is used as a default representation of what a user would be. It can easily be extended to add your own parameters.
As it extends Equatable, if you want to include property as part of the == operation, you'll have to add it to the props array.
// -> custom_user.dart
class CustomUser extends EquatableUser {
const CustomUser({required this.birthday}) : super(id: '1', email: '[email protected]');
final String birthday;
@override
List<Object?> get props => [...super.props, birthday];
}
EasyAuth is a utility class that lets you statically access the methods on your AuthenticationRepository
.
ElevatedButton(
child: const Text('Log in'),
onPressed: () {
final provider = EmailPasswordAuth('[email protected]', 'some-password');
EasyAuth.login(context, provider: provider);
},
)
AuthenticationRepository is an abstract class that defines all the methods necessary to add a custom authentication provider.
Note that you do not need to handle errors when overriding any methods in this class!
abstract class AuthenticationRepository<T extends EquatableUser> {
Future<void> login({required EasyAuthProvider provider});
Future<void> register({required T user, required String password});
Future<void> signOut();
Future<void> deleteAccount();
bool isUserNew(T user);
T get currentUser;
Stream<T> get user;
Future<AuthException?> performSafeAuth(Future<void> future, AuthAction action) async {...}
}
The only method that does not need to be re-implemented is performSafeAuth(...)
. It is used to handle any errors that might be thrown by performing an authentication action.
Here's an example of a BasicFirebaseAuth
:
class BasicFirebaseAuth extends AuthenticationRepository<EquatableUser> {
final _firebaseAuth = FirebaseAuth.instance;
@override
Stream<EquatableUser> get user => _firebaseAuth.authStateChanges().map<EquatableUser>((user) {
if (user == null) {
return EquatableUser.empty;
} else {
return EquatableUser(
id: user.uid,
name: user.displayName,
email: user.email,
createdAt: user.metadata.creationTime,
);
}
});
@override
bool isUserNew(EquatableUser user) =>
user.createdAt?.isAfter(DateTime.now().subtract(const Duration(seconds: 5))) ?? false;
@override
EquatableUser get currentUser {
final _user = _firebaseAuth.currentUser!;
return EquatableUser(id: _user.uid, name: _user.displayName, email: _user.email);
}
@override
Future<void> login({required EasyAuthProvider provider}) async {
if (provider is EmailPasswordAuth) {
await _firebaseAuth.signInWithEmailAndPassword(email: provider.email, password: provider.password);
} else if (provider is GoogleAuth) {
//sign in with google
}
}
@override
Future<void> register({required EquatableUser user, required String password}) async {
if (user.email == null) throw FirebaseAuthException(code: 'no-email-registration');
await _firebaseAuth.createUserWithEmailAndPassword(email: user.email!, password: password);
}
@override
Future<void> signOut() => _firebaseAuth.signOut();
@override
Future<void> deleteAccount() => _firebaseAuth.currentUser!.delete();
}
If you need to add more authentication providers than the pre-packaged ones, simply override the EasyAuthProvider
class.
AuthenticationBasedApp is an abstract class that you need to extend to add authentication responsiveness to your app.
There are a couple of methods you need to know about:
/// Rebuilds the state of the app every time the authentication status changes.
/// This is an efficient method due to `T` extending `Equatable` and
/// therefore only rebuilding when necessary
Widget buildState(BuildContext context, AuthStatus status, T user);
Note: this method needs to be overriden.
/// Called when an exception relating to authentication gets thrown.
/// Can be overriden to provide your own custom error-handling logic (e.g. logging, custome snackbar, etc.)
void handleError(BuildContext context, AuthException exception) {...}
Note: this method has a default implementation that prints the action that was performed when the exception was thrown and displays the following Flushbar
from package:another_flushbar.
Flushbar(
icon: const Padding(padding: EdgeInsets.only(left: 14.0), child: Text('😱')),
message: exception.message,
backgroundColor: Theme.of(context).errorColor,
margin: const EdgeInsets.all(8),
borderRadius: BorderRadius.circular(8.0),
flushbarPosition: FlushbarPosition.TOP,
flushbarStyle: FlushbarStyle.FLOATING,
);
EasyAuthBuilder is a Flutter widget which requires a builder
function. EasyAuthBuilder
handles building the widget in response to new authentication states. EasyAuthBuilder
is a simple wrapper around BlocBuilder
from package:bloc. The builder
function will potentially be called many times and should be a pure function that returns a widget in response to the state.
EasyAuthBuilder(
builder: (context, status, user) {
// return widget here based on the current AuthSatus and User
}
)
- Basic Valid/Invalid Authentication - an example of how to create a basic authentication system in any Flutter app.
- Dart 2: >= 2.12
- Flutter: >=1.17.0