How to work with flutter_bloc 8+, go_router +6 and refreshListenable
Asked Answered
F

0

8

I'm using go_router for navigation and flutter_bloc for my state management.

I would like to use refreshListenable and listen to my AuthBloC events in my GoRouter configuration, however, I don't have access to context in my GoRouter, and I would like not to provide it.

most of the answers I've found are outdated or use alternatives like StreamBuilder to re-render the whole app on event changes which are not ideal.

How can I listen to my AuthEvents outside of context?

main.dart

return RepositoryProvider.value(
      value: authRepo,
      child: BlocProvider(
        create: (_) => AuthBloc(authRepo),
        child: MaterialApp.router(
          ...
          routerConfig: Routes.router,
          builder: (_, child) {
            final isRTL = LocaleSettings.currentLocale == AppLocale.ar;

            return Directionality(
              textDirection: isRTL ? TextDirection.rtl : TextDirection.ltr,
              child: child!,
            );
          },
        ),
      ),

bloc/auth/auth_bloc.dart

class AuthBloc extends Bloc<AuthEvent, AuthState> {
  AuthBloc(this._authenticationRepository) : super(const AuthState()) {
    on<AuthEvent>((event, emit) {
      event.when(
        authStateChangeEvent: (account) => _authStateChangeEvent(account, emit),
        signOutEvent: _signOutEvent,
        verifyAccount: _verifyAccount,
      );
    });
    _userSubscription = _authenticationRepository.user.listen((account) {
      add(AuthEvent.authStateChangeEvent(account));
    });
  }

  late final StreamSubscription<Account> _userSubscription;
  final AuthenticationRepository _authenticationRepository;

  void _authStateChangeEvent(
    Account account,
    Emitter<AuthState> emit,
  ) {
   ...
  }

  void _signOutEvent() {
   ...
  }

  void _verifyAccount() {
    ...
  }

  @override
  Future<void> close() {
    _userSubscription.cancel();

    return super.close();
  }
}

Router.dart

class Routes {
  Routes._();

  static final _rootNavigatorKey = GlobalKey<NavigatorState>();

  static GoRouter get router => _router;

  static final GoRouter _router = GoRouter(
    navigatorKey: _rootNavigatorKey,
    refreshListenable: , // ! Listen to Auth Changes
    routes: [..._directNavigations, ..._nestedNavigations],
    redirect: _authGuard, //
  );

  static String? _authGuard(BuildContext context, GoRouterState state) {
    final authState = context.watch<AuthBloc>().state;
    print(authState.toString());

    final isAuthenticated = authState.authStatus == AuthStatus.authenticated;
    final isVerified =
        authState.account != null && authState.account!.emailVerified;
    
    /// ... Some logic for redirection
   
    return null;
  }
  
  ...
}

I'm getting this error when I'm navigating with context.go/context.push

"Tried to listen to a value exposed with provider, from outside of the widget tree.\n\nThis is
js_primitives.dart:30 likely caused by an event handler (like a button's onPressed) that called\nProvider.of without
js_primitives.dart:30 passing `listen: false`.\n\nTo fix, write:\nProvider.of<AuthBloc>(context, listen: false);\n\nIt is
js_primitives.dart:30 unsupported because may pointlessly rebuild the widget associated to the\nevent handler, when the
js_primitives.dart:30 widget tree doesn't care about the value.\n\nThe context used was:
js_primitives.dart:30 Navigator-[LabeledGlobalKey<NavigatorState>#31496](dependencies: [HeroControllerScope,
js_primitives.dart:30 UnmanagedRestorationScope, _FocusTraversalGroupMarker], state: NavigatorState#795d6(tickers:
js_primitives.dart:30 tracking 1 ticker))\n"

full crashlog: https://pastebin.com/3A3pdKtr

Note: I'm willing to replace my Auth BloC with a change notifier but I'm not sure if it's the right way to deal with this problem.

Forficate answered 17/2, 2023 at 8:0 Comment(6)
I've just found an ongoing discussion on GitHub. github.com/flutter/flutter/issues/116651Forficate
Hello Phil, as per the discusstion you mentioned, I have been able to apply the fix suggested by this message: github.com/flutter/flutter/issues/… - his idea is to convert an existing bloc's stream to a listenable with generic implementation. Very useful and works here.Internuncio
Yeah, this was an option, but I don't want to pass the app context to go_router. so for now I'm wrapping all the navigations inside a shell route with a BlocListener.Forficate
I am accessing bloc only in the redirect and refreshListenable methods. This way, each GoRoute/ShellRoute can continue to be clean and easy to understand. In my case, I have GoRoutes outside ShellRoutes, because I need to do Onboarding.Internuncio
I finally did as you suggested, But the redirect is acting strangely on the browser history, it seems to be adding a new stack each time. Have you noticed it? Is there a way to replace the default behavior of redirect?Forficate
Sorry, but my app does not work well on Chrome because flutter_appauth does not run the web :(Internuncio

© 2022 - 2024 — McMap. All rights reserved.