0

So i will provide my code as it is the simplest way of understanding my problem. I started with my UserService class that creates a stream which receives data continuously whenever the AuthStateChanges (from FirebaseAuth) stream triggers

enum AuthEventEnum { authenticated, unauthenticated }

class UserService {
  StreamController<AuthEventEnum> controller =
      StreamController<AuthEventEnum>();
  late final StreamSubscription _subscription;
  StreamSubscription get subscription => _subscription;
  UserService() {
    final StreamSubscription _subscription =
        FirebaseAuth.instance.authStateChanges().listen(
      (User? user) {
        print(user);
        if (user == null) {
          controller.sink.add(AuthEventEnum.unauthenticated);
        } else if (user != null) {
          controller.sink.add(AuthEventEnum.authenticated);
        }
      },
    );
  }
}

now i tried to connect to my AuthBloc (which i am pretty sure the errors comes from here... but i dont know how to fix it)

class AuthBloc extends Bloc<AuthEventEnum, AuthState> {
  final UserService _userService;
  late final _addEvents;

  AuthBloc(this._userService) : super(AuthInitial()) {
    _addEvents = _userService.controller.stream.listen(
      (event) {
        if (event == AuthEventEnum.authenticated) {
          mapEventToState(AuthEventEnum.authenticated);
        } else if (event == AuthEventEnum.unauthenticated) {
          mapEventToState(AuthEventEnum.authenticated);
        }
      },
    );
  }

  @override
  Stream<AuthState> mapEventToState(AuthEventEnum event) async* {
    if (event == AuthEventEnum.authenticated) {
      yield AuthAuthenticatedState();
    } else if (event == AuthEventEnum.unauthenticated) {
      yield AuthUnauthenticatedState();
    }
  }
}

now the end goal was to redirect users to to different routes according to their AuthStatus...

class AppRouter {
  AuthBloc authBloc;
  late final GoRouter routes;
  AppRouter({required this.authBloc}) {
    routes = GoRouter(
      initialLocation: '/home',
      redirect: (context, state) {
        if (authBloc.state is AuthInitial) {
          return '/register';
        } else if (authBloc.state is AuthAuthenticatedState) {
          return '/app';
        } else if (authBloc.state is AuthUnauthenticatedState) {
          return '/register';
        }
      },
      routes: [
        GoRoute(
          path: '/home',
          builder: (context, state) => const HomePage(),
        ),
        GoRoute(
          path: '/login',
          builder: (context, state) => const LoginPage(),
        ),
        GoRoute(
          path: '/register',
          builder: (context, state) => const RegisterPage(),
        ),
        GoRoute(
          path: '/app',
          builder: (context, state) => const AppPage(),
        ),
      ],
    );
  }
}

in case main function is relevant here it is:

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp(
    options: DefaultFirebaseOptions.currentPlatform,
  );
  final authService = UserService();
  final authBloc = AuthBloc(authService);

  runApp(
    BlocProvider.value(
      value: authBloc,
      child: MaterialApp.router(
        title: 'flutter demo',
        theme: ThemeData(primarySwatch: Colors.blue),
        routerConfig: AppRouter(authBloc: authBloc).routes,
      ),
    ),
  );
}

Learn more about how Bloc works with streams and how can a bloc listen to other streams and add events to itself

miguel
  • 1
  • 3

1 Answers1

0

You're using a very outdated API for Bloc for starters. The article that @ Krish Bhanushali linked in the comments is a good first start. There's no reason to still be using mapEventToState, and you should update your Bloc package.

That aside, the best way to deal with a stream directly with Bloc is emit.forEach(...)

// The functionality you had in your UserService class can be simplified to this

class UserService {
  Stream<User?> get userSubscription =>
      FirebaseAuth.instance.authStateChanges();
}

// Create a standard Bloc event that will init a stream listener

abstract class AuthEvent {}

class AuthInitListener extends AuthEvent {}


// Use AuthEvent in Bloc instead of the enum

class AuthBloc extends Bloc<AuthEvent, AuthState> {
  final UserService _userService;

  AuthBloc(this._userService) : super(AuthInitial()) {
    // register all event handlers in the constructor
    on<AuthInitListener>(_onInitAuthListener); 
  }

  // Event handler function

  Future<void> _onInitAuthListener(
    AuthInitListener event, 
    Emitter<AuthState> emit,
    ) async {
    // emit.forEach takes a stream and returns a new state when the stream
    // is updated
    emit.forEach(
      _userService.userSubscription, // stream from UserService
    // The state returned from the onData callback is what is emitted and all listeners will be notified
     onData: (User? user) {
        return user != null
            ? AuthAuthenticatedState()
            : AuthUnauthenticatedState();
      },
    );
  }
}

Then call the event when you initialize the BlocProvider.

runApp(
    BlocProvider.value(
      value: authBloc..add(AuthInitListener()), // calling the event here
...

So the overall idea here is to use the tools that Bloc provides to deal with streams. Firebase already provides a Stream<User>, so just use that. No need to create additional streams/controllers/subscriptions etc.. Its simpler and less error prone.

Loren.A
  • 4,872
  • 1
  • 10
  • 17