ismailfarisi
ismailfarisi

Reputation: 309

how to use flutter_bloc with go_router

I have built an app where I use flutter_bloc. i want to use go_router for navigation.but for dynamic routing how can i use GoRouter refreshListener parameter with flutter_bloc

GoRouter(
  routes: [
    GoRoute(
      path: '/',
      name: 'home',
      pageBuilder: (context, state) => HomePage.page(),
    ),
    GoRoute(
      path: '/login',
      name: 'login',
      pageBuilder: (context, state) => LoginPage.page(),
    ),
  ],
  redirect: (state) {
    final isLoggedIn =
        bloc.state.status == AuthenticationStatus.authenticated;
    final isLoggingIn = state.location == '/login';

    if (!isLoggingIn && !isLoggingIn) return '/login';
    if (isLoggedIn && isLoggingIn) return "/";

    return null;
  },
  refreshListenable: 
   );

Upvotes: 12

Views: 22023

Answers (9)

vargascarlitos
vargascarlitos

Reputation: 21

After much reading, I want to share my solution, I hope it can help many people. :)

  1. I create a file called go_router_refresh_stream.dart within my project that looks like this:
import 'dart:async';

import 'package:flutter/foundation.dart';

class GoRouterRefreshStream extends ChangeNotifier {
  GoRouterRefreshStream(Stream<dynamic> stream) {
    notifyListeners();
    _subscription = stream.asBroadcastStream().listen(
          (dynamic _) => notifyListeners(),
    );
  }

  late final StreamSubscription<dynamic> _subscription;

  @override
  void dispose() {
    _subscription.cancel();
    super.dispose();
  }
}
  1. With the help of getIt, I make a registerLazySingleton of my AuthBloc.
getIt.registerLazySingleton(
    () => AuthBloc(
      authStatusChangesFirebaseUseCase: getIt(),
      authCurrentUserUseCase: getIt(),
    ),
  );
  1. I have a mixin of my GoRoute() class and with the help of getIt, I access the stream property of my AuthBloc.

Mixin GoRoute():

mixin RouterMixin on State<MyApp> {
  final _router = GoRouter(
    initialLocation: LoginScreen.path,
    errorBuilder: (context, state) => ErrorScreen(routerState: state),
    routes: [
      GoRoute(
        name: LoginScreen.name,
        path: LoginScreen.path,
        builder: (context, state) => const LoginScreen(),
      ),
      GoRoute(
        path: HomeScreen.path,
        name: HomeScreen.name,
        builder: (context, state) => HomeScreen(),
      ),
    ],
    redirect: (context, state) async {
      final sessionStatus = GetIt.instance<AuthBloc>().state.status;
      if (sessionStatus == AuthStatus.unauthenticated) {
        return LoginScreen.path;
      }
      return null;
    },
    refreshListenable: GoRouterRefreshStream(GetIt.instance<AuthBloc>().stream),
  );

  GoRouter get router => _router;
}
  1. Lastly, I convert my MyApp class into a StatefulWidget so that I can use the mixin and also instantiate my BlocProvider:
class MyApp extends StatefulWidget {
  const MyApp({super.key});

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> with RouterMixin {
  @override
  Widget build(BuildContext context) {
    return BlocProvider(
      create: (_) => getIt<AuthBloc>(),
      child: MaterialApp.router(
        title: 'Dase BackOffice',
        theme: DaseTheme.lightTheme,
        darkTheme: DaseTheme.darkTheme,
        themeMode: ThemeMode.dark,
        routerConfig: router,
      ),
    );
  }
}

The route protection works perfectly. The use of getIt with flutter_bloc is completely a personal preference.

pd: I don't publish the logic of my AuthBloc for practical reasons, but if you're interested, I can tell you that it's a copy of the example called Firebase Login found in the official flutter_bloc documentation :P

Saludos

Upvotes: 2

Angel
Angel

Reputation: 143

my approach was on redirect use

refreshListenable :GoRouterRefreshStream(authenticationBloc.stream),

Or with getit

  refreshListenable:
        GoRouterRefreshStream(getIt<AuthenticationBloc>().stream),

On redirect do not use if on that way because u can't check data inside states

     redirect: (state) {
        final bloc = getIt<AuthenticationBloc>().state;
       
        final isLoggingIn = state.location == '/login';
    
        if (bloc is authenticated && !isLoggingIn) return '/login';
        if (bloc is otherState (notAuthenticaded && isLoggingIn) return 
        "/";
    
        return null;
      },

Upvotes: 0

Devplanet
Devplanet

Reputation: 868

you can convert a Flutter BLoCinto a Listenable object by using the ChangeNotifier class from the Flutter framework.

Here's how you can do it:

class BlocListenable<T> extends ChangeNotifier implements Listenable {
  final BlocBase<T> bloc;

  BlocListenable(this.bloc) {
    bloc.stream.listen((state) {
      notifyListeners();
    });
  }

  @override
  void dispose() {
    bloc.close();
    super.dispose();
  }
}

Use the listenableBloc in your router:

GoRouter route(AuthenticationBloc bloc) => GoRouter(
        routes: [
        ....
        ],
        redirect: (context, state) {
          final status = bloc.state.status;
          .....
        },
        refreshListenable: BlocListenable(bloc),
      );

Now, whenever the state of your BLoC changes, the redirection will notify.

Upvotes: 0

Michael S
Michael S

Reputation: 769

Since go_router become an official flutter package the source moved to here. The examples got also updated and this one should be the one you need.

Upvotes: 0

Pushparaj Samant
Pushparaj Samant

Reputation: 111

What worked for me was to return a Streambuilder from my GoRoute and redirect based on the value in the state. refreshListenable also works but you need to pass the bloc inside the router due to which you loose the ability to hot reload on a page as it will refresh the router every time.

routes: <GoRoute>[
   GoRoute(
      path: AppPage.login.toPath,
      name: AppPage.login.toName,
      builder: (context, state) {
        return StreamBuilder(
          stream: context.read<LoginBloc>().stream,
          builder: (context, snapshot) {
            if (kDebugMode) {
              print("Updated loginbloc");
            }
            if (snapshot.hasData) {
              if (snapshot.data?.status == LoginStatus.success) {
                return const HomePage();
              } else {
                return const LoginPage();
              }
            }
            return const LoginPage();
          },
        );
      },
    )]

Just for context also adding my LoginBloc

import 'package:equatable/equatable.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

import '../../../services/auth_service.dart';

part 'login_event.dart';
part 'login_state.dart';

class LoginBloc extends Bloc<LoginEvent, LoginState> {
  LoginBloc({
    required AuthService authService,
  })  : _authService = authService,
        super(const LoginState()) {
    on<LoginButtonPressedEvent>(_handleLoginWithEmailAndPasswordEvent);
    on<LoginEmailChangedEvent>(_handleLoginEmailChangedEvent);
    on<LoginPasswordChangedEvent>(_handleLoginPasswordChangedEvent);
  }

  final AuthService _authService;

  Future<void> _handleLoginEmailChangedEvent(
    LoginEmailChangedEvent event,
    Emitter<LoginState> emit,
  ) async {
    emit(state.copyWith(email: event.email));
  }

  Future<void> _handleLoginPasswordChangedEvent(
    LoginPasswordChangedEvent event,
    Emitter<LoginState> emit,
  ) async {
    emit(state.copyWith(password: event.password));
  }

  Future<void> _handleLoginWithEmailAndPasswordEvent(
    LoginButtonPressedEvent event,
    Emitter<LoginState> emit,
  ) async {
    try {
      final userLoggedIn = await AuthService.signInWithEmailAndPassword(
        email: state.email,
        password: state.password,
      );
      if (kDebugMode) {
        print(userLoggedIn);
      }
      if (userLoggedIn != null) {
        emit(state.copyWith(message: 'Success', status: LoginStatus.success));
      } else {
        emit(state.copyWith(message: 'Failure', status: LoginStatus.failure));
      }
    } catch (e) {
      emit(state.copyWith(message: e.toString(), status: LoginStatus.failure));
    }
  }
}

Upvotes: 1

Rahul Mishra
Rahul Mishra

Reputation: 476

Since bloc also returns states in form of a stream, you can directly use it. You can do it like ->

refreshListenable = GoRouterRefreshStream(authenticationBloc.stream),

Upvotes: 9

Rafik EL YAAGOUBI
Rafik EL YAAGOUBI

Reputation: 422

Checkout this example, go_router supports since a recent release refreshing with a Stream, this would avoid additional wrapping in your code.

Upvotes: 0

ismailfarisi
ismailfarisi

Reputation: 309

For me, Mixing changeNotifier with the Bloc class and calling notififyListener() from the event worked

This is my bloc class

class AuthenticationBloc extends Bloc<AuthenticationEvent, AuthenticationState>
    with ChangeNotifier {
  AuthenticationBloc(
      {required AuthenticationRepository authenticationRepository})
      : _authenticationRepository = authenticationRepository,
        super(const AuthenticationState.unknown()) {
    on<AppStarted>(_onAppStarted);

    on<AuthenticationUserChanged>(_onAuthenticationUserChanged);

    on<AuthenticationLogoutRequested>(_onAuthenticationLogoutRequested);

    _userSubscription = _authenticationRepository.user
        .listen((user) => add(AuthenticationUserChanged(user)));
  }

  final AuthenticationRepository _authenticationRepository;

  late StreamSubscription<User> _userSubscription;


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


  FutureOr<void> _onAppStarted(
      AppStarted event, Emitter<AuthenticationState> emit) {
    emit(AuthenticationState.unknown());
  }

  FutureOr<void> _onAuthenticationUserChanged(
      AuthenticationUserChanged event, Emitter<AuthenticationState> emit) {
    final status = event.user != User.empty
        ? AuthenticationState.authenticated(event.user)
        : const AuthenticationState.unauthenticated();
    emit(status);
    notifyListeners();
  }

  FutureOr<void> _onAuthenticationLogoutRequested(
      AuthenticationLogoutRequested event, Emitter<AuthenticationState> emit) {
    unawaited(_authenticationRepository.logOut());
  }
}

This is GoRouter

GoRouter routes(AuthenticationBloc bloc) {
  return GoRouter(
      routes: [
        GoRoute(
          path: '/',
          name: 'home',
          builder: (context, state) => const HomePage(),
        ),
        GoRoute(
          path: '/login',
          name: 'login',
          builder: (context, state) => const LoginPage(),
        ),
      ],
      redirect: (state) {
        final isLoggedIn =
            bloc.state.status == AuthenticationStatus.authenticated;
        final isLoggingIn = state.location == '/login';
        print(isLoggedIn);

        if (!isLoggedIn && !isLoggingIn) return '/login';
        if (isLoggedIn && isLoggingIn) return '/';

        return null;
      },
      refreshListenable: bloc);
}

Upvotes: 8

聂超群
聂超群

Reputation: 2120

Maybe you can do something like this:

class AuthStateNotifier extends ChangeNotifier {
  late final StreamSubscription<AuthState> _blocStream;
  AuthStateProvider(AuthBloc bloc) {
    _blocStream = bloc.stream.listen((event) {
      notifyListeners();
    });
  }

  @override
  void dispose() {
    _blocStream.cancel();
    super.dispose();
  }
}

The code is from this answer, hope you get the idea.

Upvotes: 1

Related Questions