Monitoring authentication status

The logic to continuously monitor the user’s auth status. This snippet is using Firebase for user authentication and the bloc package.

Dependencies

Add the following packages in the pubspec.yaml first.

  1. cloud_firestore: ^version
  2. firebase_auth: ^version
  3. flutter_bloc: ^version
  4. equatable: ^version

and run the pub get command from the terminal.

auth_repo.dart

class AuthenticationRepository {
  /// {@macro authentication_repository}
  AuthenticationRepository({firebase_auth.FirebaseAuth firebaseAuth})
      : _firebaseAuth = firebaseAuth ?? firebase_auth.FirebaseAuth.instance;

  final firebase_auth.FirebaseAuth _firebaseAuth;

  /// Stream of [FirebaseUser] which will emit the current user when
  /// the authentication state changes.
  ///
  /// Emits [FirebaseUser.empty] if the user is not authenticated.
  Stream<User> get user {
    return _firebaseAuth.authStateChanges().map((firebaseUser) {
      return firebaseUser == null ? User.empty : firebaseUser.toUser;
    });
  }

  ...
}

auth_bloc.dart

class AuthenticationBloc
    extends Bloc<AuthenticationEvent, AuthenticationState> {
  AuthenticationBloc()
      : _authenticationRepository = AuthenticationRepository(),
        super(const AuthenticationState.unknown()) {
    _userSubscription = _authenticationRepository.user.listen(
      (user) => add(AuthenticationUserChanged(user)),
    );
  }

  final AuthenticationRepository _authenticationRepository;
  StreamSubscription<User> _userSubscription;

  @override
  Stream<AuthenticationState> mapEventToState(
    AuthenticationEvent event,
  ) async* {
    if (event is AuthenticationUserChanged) {
      yield _mapAuthenticationUserChangedToState(event);
    } else if (event is AuthenticationLogoutRequested) {
      unawaited(_authenticationRepository.logOut());
    }
  }

  @override
  Future<void> close() {
    _userSubscription?.cancel();
    return super.close();
  }

  AuthenticationState _mapAuthenticationUserChangedToState(
    AuthenticationUserChanged event,
  ) {
    return event.user != User.empty
        ? AuthenticationState.authenticated(event.user)
        : const AuthenticationState.unauthenticated();
  }
}

auth_event.dart

abstract class AuthenticationEvent extends Equatable {
  const AuthenticationEvent();

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

class AuthenticationUserChanged extends AuthenticationEvent {
  const AuthenticationUserChanged(this.user);

  final User user;

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

class AuthenticationLogoutRequested extends AuthenticationEvent {}

auth_state.dart

enum AuthenticationStatus { authenticated, unauthenticated, unknown }

class AuthenticationState extends Equatable {
  const AuthenticationState._({
    this.status = AuthenticationStatus.unknown,
    this.user = User.empty, /// Empty user which represents an unauthenticated user.
  });

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

  const AuthenticationState.authenticated(User user)
      : this._(status: AuthenticationStatus.authenticated, user: user);

  const AuthenticationState.unauthenticated()
      : this._(status: AuthenticationStatus.unauthenticated);

  final AuthenticationStatus status;
  final User user;

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

Authentication repository and bloc initialization

Future<void> main() async {
  await init();
  runApp(
    MyApp(),
  );
}

void init() async {
  HydratedBloc.storage = await HydratedStorage.build();
  await Firebase.initializeApp();
}

class MyApp extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    return RepositoryProvider.value(
      // The Repository is the parent class.
        value: Repository(
            MockRepository(), RemoteRepository(), AuthenticationRepository()),
        child: _buildMainChild(context));
  }

  _buildMainChild(BuildContext context) {
    return MultiBlocProvider(
      providers: [
        ... 
        BlocProvider<AuthenticationBloc>(
            create: (context) => AuthenticationBloc()),
        BlocProvider<LoginBloc>(create: (context) => LoginBloc())
        ],
      child: MaterialApp(home: _buildRoot),
    );
  }

  _buildRoot() {
    return BlocBuilder<AuthenticationBloc, AuthenticationState>(
      cubit: BlocProvider.of<AuthenticationBloc>(context),
      builder: (context, state) { ... ... };
  }
}



/// in each place we need to listen for that change (or as a direct child of the builder child)
/// we provide at the main widget, we listen at the rest.
class ProfileScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MultiBlocListener(
        listeners: [
          BlocListener<AuthenticationBloc, AuthenticationState>(
            listener: (context, state) {},
          )
        ],
        child: child: ProfilePage();
  }
}