Hello guys I'm using BLoC for app I'm currently developing but there some cases which I'm clueless like when you do login you fire API call and wait for result naturally I would send loading state and show loader but after
that finishes how to handle for example navigating to different screen.
I've currently have something like this
typedef void LoginSuccessCallback();
class LoginBloc(){
LoginBloc(Api this.api,LoginSuccessCallback loginSuccesCallback){
_login.switchMap((ev) => api.login(ev.payload.email,ev.payload.password)).listen((_) => loginSuccessCallback);
}
}
But I'm sure there is much cleaner way for handling this I've tried to search some samples which have something similar but couldn't find anything.
Edit: After a few months with this solution in place, I noticed that there are a few problems with it:
- Android hardware back button does not work
- The app resets when you toggle "inspect" mode.
- No transitions possible
- No guarantee that no forbidden route is displayed
So I no longer recommend using this approach!
For normal user-initiated navigation, you don't need the BLoC pattern at all. Just use the Navigator
.
Login is a special case. Following the BLoC pattern, it would make sense to provide a isAuthenticated
stream:
abstract class MyBloc {
Stream<bool> get isAuthenticated;
}
Your app will probably have 2 different named route trees: One for logged in users, and one for anonymous users:
final Map<String, WidgetBuilder> anonymousRoutes = {
'/': (context) => new LoginScreen(), // default for anon
'/register': (context) => new RegisterScreen(),
};
final Map<String, WidgetBuilder> authenticatedRoutes = {
'/': (context) => new HomeScreen(), // default for logged in
'/savings': (context) => new SavingsScreen(),
// ...
};
Usually the Navigator
and its named routes are tightly coupled to the MaterialApp
, but you can also define your own that is rebuilt when the isAuthenticated
stream is updated:
class MyApp extends StatelessWidget {
const MyApp({Key key, this.bloc}) : super(key: key);
final MyBloc bloc;
@override
Widget build(BuildContext context) {
return MaterialApp(
builder: (BuildContext context, Widget child) {
return StreamBuilder<bool>(
stream: bloc.isAuthenticated,
builder: (BuildContext context, AsyncSnapshot<bool> snapshot) {
if (!snapshot.hasData) {
return Text('loading...');
}
bool isAuthenticated = snapshot.data;
return _buildNavigator(isAuthenticated);
},
);
},
);
}
}
Navigator _buildNavigator(bool isAuthenticated) {
// different route tree and different default route depending on auth state
final routes = isAuthenticated ? authenticatedRoutes : anonymousRoutes;
return Navigator(
key: new ValueKey(isAuthenticated),
onGenerateRoute: (RouteSettings settings) {
final name = settings.name;
return new MaterialPageRoute(
builder: routes[name],
settings: settings,
);
},
onUnknownRoute: (RouteSettings settings) {
throw Exception('unknown route');
},
);
}
Sadly right now (2018-07-14) there are a 2 conflicting asserts in the Flutter code which you have to remove to make the code above work (you can just edit it with your IDE):
Line 93 and 96 in packages\flutter\lib\src\widgets\app.dart
//assert(navigatorObservers != null),
//assert(onGenerateRoute != null || navigatorObservers == const <NavigatorObserver>[]),