Home > OS >  BlocListener confusion
BlocListener confusion

Time:04-30

I am trying to make an app using flutter blocs, but I am having troubles with the BlocListener not being called and I can't figure out what I'm doing wrong.

Here is a minimalish code reproducing my issue:


import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

void main() {
  runApp(const App());
}

class App extends StatelessWidget {
  const App({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return BlocProvider(
      create: (_) => AuthBloc(),
      child: const AppView(),
    );
  }
}

/**************** APP VIEW **************/

class AppView extends StatefulWidget {
  const AppView({Key? key}) : super(key: key);

  @override
  State<AppView> createState() => _AppViewState();
}

class _AppViewState extends State<AppView> {
  final _navigatorKey = GlobalKey<NavigatorState>();

  NavigatorState get _navigator => _navigatorKey.currentState!;

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      navigatorKey: _navigatorKey,
      builder: (context, child) {
        print('App builder');
        return BlocListener<AuthBloc, AuthState>(
          listener: (context, state) {
            print('Bloc listener');
            switch (state.status) {
              case AuthStatus.authenticated:
                _navigator.pushAndRemoveUntil<void>(
                  MaterialPageRoute(
                    builder: (context) {
                      return Center(
                        child: Column(
                          children: [
                            const Text('Home'),
                            ElevatedButton(
                              onPressed: () {
                                context.read<AuthBloc>().add(
                                    const AuthStatusChanged(
                                        AuthStatus.unauthenticated));
                              },
                              child: const Text('Log out'),
                            ),
                          ],
                        ),
                      );
                    },
                  ),
                  (route) => false,
                );
                break;
              default:
                _navigator.pushAndRemoveUntil<void>(
                  MaterialPageRoute(
                    builder: (context) {
                      return Center(
                        child: Column(
                          children: [
                            const Text('Login'),
                            ElevatedButton(
                              onPressed: () {
                                context.read<AuthBloc>().add(
                                    const AuthStatusChanged(
                                        AuthStatus.authenticated));
                              },
                              child: const Text('Log in'),
                            ),
                          ],
                        ),
                      );
                    },
                  ),
                  (route) => false,
                );
                break;
            }
          },
          child: child,
        );
      },
      onGenerateRoute: (_) => MaterialPageRoute(
        builder: (context) {
          return const Center(
            child: Text('splash'),
          );
        },
      ),
    );
  }
}

/**************** AUTH BLOC CLASSES **************/

/**************** AUTH State **************/
enum AuthStatus { unknown, unauthenticated, authenticated }

class AuthState extends Equatable {
  final AuthStatus status;

  const AuthState._({
    this.status = AuthStatus.unknown,
  });

  const AuthState.unknown() : this._();

  const AuthState.authenticated() : this._(status: AuthStatus.authenticated);

  const AuthState.unauthenticated()
      : this._(status: AuthStatus.unauthenticated);

  @override
  List<Object?> get props => [status];
}

/**************** AUTH Event **************/
abstract class AuthEvent extends Equatable {
  const AuthEvent();

  @override
  List<Object> get props => [];
}

class AuthStatusChanged extends AuthEvent {
  final AuthStatus status;

  const AuthStatusChanged(this.status);

  @override
  List<Object> get props => [status];
}

/**************** AUTH BLOC **************/
class AuthBloc extends Bloc<AuthEvent, AuthState> {
  AuthBloc() : super(const AuthState.unknown()) {
    print('Bloc constructor');
    on<AuthStatusChanged>(_onAuthStatusChanged);
  }

  _onAuthStatusChanged(
    AuthStatusChanged event,
    Emitter<AuthState> emit,
  ) async {
    switch (event.status) {
      case AuthStatus.unauthenticated:
        return emit(const AuthState.unauthenticated());
      case AuthStatus.authenticated:
        return emit(const AuthState.authenticated());
      default:
        return emit(const AuthState.unknown());
    }
  }
}

When I launch the app I would expect the BlocListener to be called once but instead it sits on the splash page.

I used this tutorial to produce this code : https://bloclibrary.dev/#/flutterlogintutorial

Edit: Thank you all for your insight, I didn't understand that the BlocListener won't fire an event on the initialState (RTFM I guess xD). Looking back at the tutorial I used, this is dealt with by the "Repository" that feeds a stream delayed on creation and the Bloc is listening for that stream to fire a change of state events. Reusing the same concept works for me!

CodePudding user response:

BlocListener only trigger when state has changed. On application load you may want to trigger a bloc event to change the AuthBloc state. This could be achieved by adding a bloc event in the initState() function and placing a breakpoint to see if the listener is being triggered.

https://pub.dev/documentation/flutter_bloc/latest/flutter_bloc/BlocListener-class.html

CodePudding user response:

The listener is only called after a state change, it's not called after AuthBloc is created.

One more tip: context.read<AuthBloc>().add(const AuthStatusChanged(AuthStatus.authenticated)); DON'T do that. Prefer to create methods inside your bloc instance that will fire events, it makes your code easier to test, if add very_good_analysis to your project, which is a linter built by VGV (the same company behind flutter bloc), you will see there's a lint rule against that.

  • Related