Reputation: 309
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
Reputation: 21
After much reading, I want to share my solution, I hope it can help many people. :)
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();
}
}
getIt.registerLazySingleton(
() => AuthBloc(
authStatusChangesFirebaseUseCase: getIt(),
authCurrentUserUseCase: getIt(),
),
);
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;
}
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
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
Reputation: 868
you can convert a Flutter BLoC
into 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
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
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
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
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
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