Flutter BLoC Authentication Using Firebase: Complete Beginner Guide

Flutter BLoC Authentication Using Firebase: A Beginner’s Guide



This book is for anyone who wants to learn how to use Flutter BLoC Authentication with Firebase, regardless of their level of expertise.

You may have encountered authentication if you have made a couple of Flutter applications. Nearly all apps require users to log in before they can enjoy tailored features.

When I started developing Flutter Apps I was doing authentication in my UI screens. This was ok for small applications but the maintenance of code got harder as the application grew. At this point I started implementing the BLoC pattern and Firebase Authentication.

Let’s discuss the process of integrating Firebase Authentication with BLoC in Flutter and the reasons to learn it for real-world projects.

Why Firebase Authentication?

Authentication can be a tricky feature to develop on your own. Secure password handling, user management, session management and much more are required.

Firebase does all that for us.

Firebase Authentication can help you easily add:

⦁ The website is secured with email and password.
⦁ Google Sign-In
⦁ Phone Authentication
⦁ Apple Sign-In
⦁ Anonymous Authentication

The best part is that it’s secure, reliable and can be integrated seamlessly with Flutter.

Why to Embed Authentication Logic into Widgets?

Many new programmers forget to write all of the authentication code within a screen.

For example:

onPressed: () async {
  await FirebaseAuth.instance
      .signInWithEmailAndPassword(
          email: email,
          password: password);
}

Initially this might seem good enough, but what if you were to expand your app to several screens and authentication methods?

You'll soon have to deal with:

⦁ Repeated code
⦁ Difficult debugging
⦁ Poor maintainability
⦁ Testing challenges

This is where BLoC comes in handy.

What is BLoC? 

BLoC stands for Business Logic Component.

The main idea is straightforward:

Separate business logic from UI.

Your UI should only present information and react to users.

The authentication process should be left to another server.

Separation will result in a cleaner, more testable, and more scalable application.

Project Structure

To use for authentication, I have my project structure like this:

lib/
├── bloc/
│   ├── auth_bloc.dart
│   ├── auth_event.dart
│   └── auth_state.dart
├── repository/
│   └── auth_repository.dart
├── screens/
│   ├── login_screen.dart
│   └── home_screen.dart
└── main.dart

This structure keeps the code in organized manner which helps in scalability, testability and maintainability of code.

Setting Up Firebase

Before you write any authentication code, make sure Firebase is set up properly in your Flutter project.
Install the necessary packages:

dependencies:
  flutter_bloc: ^9.0.0
  firebase_core: ^4.0.0
  firebase_auth: ^6.0.0

In your app's start up initialize Firebase:

void main() async {
  WidgetsFlutterBinding.ensureInitialized();

  await Firebase.initializeApp();

  runApp(const MyApp());
}

Creating States

States basically describe what's happening during authentication at any given moment.
When I first started structuring auth flows, I kept everything tangled inside widgets. Bad idea. Breaking it into states made everything click — you end up with four simple ones that cover almost every scenario:

⦁ Initial — nothing's happened yet
⦁ Loading — waiting on Firebase
⦁ Success — user's in
⦁ Failure — something went wrong, and we need to tell the user what

abstract class AuthState {}

class AuthInitial extends AuthState {}

class AuthLoading extends AuthState {}

class AuthSuccess extends AuthState {}

class AuthFailure extends AuthState {
  final String message;

  AuthFailure(this.message);
}

Simple, but powerful. Your UI now has a clear contract — it just reacts to whatever state comes through.

Putting the BLoC Together

This is where wiring takes place. The BLoC sits between your UI and your repository, listening for events and deciding what state to emit next.

class AuthBloc extends Bloc<AuthEvent, AuthState> {
 final AuthRepository authRepository;

 AuthBloc(this.authRepository) : super(AuthInitial()) {
     on<LoginRequested>((event, emit) async {
         emit(AuthLoading());

        try {
             await authRepository.login(
                 event.email,
                 event.password,
             );
             emit(AuthSuccess());
             } catch (e) {
                 emit(AuthFailure(e.toString()));
             }
         });

         on<LogoutRequested>((event, emit) async {
             await authRepository.logout();
             emit(AuthInitial());
         });
     }
}

Login succeeds → AuthSuccess. Something breaks → AuthFailure with the reason. Clean, predictable and easy to test.

Hooking It Up to the UI

Here's the part I genuinely like — the UI has zero idea Firebase even exists.

ElevatedButton(
  onPressed: () {
    context.read<AuthBloc>().add(
      LoginRequested(
        emailController.text,
        passwordController.text,
      ),
    );
  },
  child: const Text("Login"),
)

That button just fires an event. What happens next is entirely the BLoC's problem.

Reacting to State Changes

BlocConsumer handles both navigation logic and UI rebuilds in one place — which I find cleaner than splitting them across multiple widgets.

BlocConsumer<AuthBloc, AuthState>(
     listener: (context, state) {
         if (state is AuthSuccess) {
            Navigator.pushReplacement(
                 context,
                 MaterialPageRoute(builder: (_) => HomeScreen()),
         );
     }

     if (state is AuthFailure) {
         ScaffoldMessenger.of(context).showSnackBar(
             SnackBar(content: Text(state.message)),
         );
     }
 },
 builder: (context, state) {
     if (state is AuthLoading) {
         return const CircularProgressIndicator();
     }
     return LoginForm();
 },
)

Spinner loading while you wait, snackbar for error, and redirect on success, all in one place. Users will have a great experience without having to handling it all manually.

Things I Regret Not Knowing Early On

From having shipped a few Flutter applications, there are a few lessons learned along the way:

Do not make your Firebase leak into your widgets. Widgets should be concerned only with layout and interactions — period. The minute you write FirebaseAuth.instance in a widget's build method, you'll see that the complexity starts snowballing.

Write a repository even for small applications. It will take roughly 15 minutes more, but it will save countless time later when swapping implementations and testing your code.

Make your exceptions meaningful. No user understands Firebase error codes. Exceptions should always be wrapped and made comprehensible by humans.

Think scalable from the start. I've inherited code bases where authentication was implemented in ten different classes. Clean architecture is overhead in the beginning; it's never an overhead.

Conclusion

There is nothing stopping Firebase Auth from working well with BLoC. Firebase does everything related to authentication for you, and BLoC makes sure your application structure doesn't end up as a spaghetti mess.
In case you're prepping for Flutter job interviews, or building maintainable applications, understanding the above patterns will come in handy as they are implemented in many real-world codebases.
Coming next, I will talk about integrating Google Sign-In, using Dependency Injection through GetIt, and some additional BLoC patterns.

Comments

Popular posts from this blog

Flutter Interview Preparation Guide: 25 Essential Questions