FlutterBloc: BlocListener not consuming emitted state - flutter

The bloc that is giving me trouble is the AuthBloc. Here is how it is initialised:
void main() {
runApp(MultiBlocProvider(
providers: [
BlocProvider<AuthBloc>(
create: (_) => AuthBloc(),
),
BlocProvider<SignUpBloc>(create: (_) => SignUpBloc()),
],
child: ChangeNotifierProvider(
create: (BuildContext context) => UserModel(), child: App())));
}
Here is where I need the state to be acted on, but in certain cases the bloc's listener is not called:
Widget home(BuildContext context) {
// ignore: close_sinks
var authBloc = BlocProvider.of<AuthBloc>(context);
return BlocListener(
bloc: authBloc,
listener: (context, state) {
UserModel userModel = Provider.of<UserModel>(context, listen: false);
if (state is SignedInState) {
userModel.updateUser(state.user);
Navigator.of(context).pushReplacementNamed(kHomeRoute);
}
},
child: HomePage(),
);
}
}
I know that the bloc is recieving the event, but when I yield the state, this bloc does not rebuild. Any thoughts on where I'm going wrong?
Thanks!

Related

Provider is not working when navigate to new screen

I implemented Authentication by provider
The problem is when is the first time myHomeCalss is notified that the user is Authenticated by dont return the correctPage (MainGui)
SplashPages is page with a button continue, and push the login page ,
The Login page is pushed outside of costumer
but when I dont pass in the SplashPages is worked perfectyl
any adea please
//splash page
ContinueButton(
onPressed: (){
Navigator.push(
context,
MaterialPageRoute(
builder: (_) =>
ListenableProvider.value(
value: yourModel,
child: LoginPage(),
),
),
);
}
)
//main
void main() async {
setupLocator();
WidgetsFlutterBinding.ensureInitialized();
await firebase_core.Firebase.initializeApp();
runApp(
MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => AuthenticationService()),
],
child: MyApp(),
),
);
}
//My app
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: MyHome(),
builder: (context, child) => Navigator(
key: locator<DialogService>().dialogNavigationKey,
onGenerateRoute: (settings) => MaterialPageRoute(
builder: (context) => DialogManager(child: child)),
));
}
}
MyHome
Class MyHome extends StatelessWidget {
#override
Widget build(BuildContext context) {
return SafeArea(
child: FutureBuilder<bool>(
future: startTime(),
builder: (BuildContext context, AsyncSnapshot<bool> snapshot2) {
if (snapshot2.hasData) {
if (snapshot2.data) {
return SplashPages();
} else {
return Consumer<AuthenticationService>(builder: (_, auth, __) {
if (auth.currentUserr == null) {
return LoginPage();
} else {
return FutureBuilder(
future: auth.populateCurrentUser(auth.currentUserr),
builder: (context, snapshot) {
if (snapshot.hasData) {
if (auth.currentUserr.emailVerified) {
return MainGui();
} else {
return ValidationMailPage(
email: auth.currentUserr.email,
);
}
} else
return Container(
// child: Center(
// child: SpinKitRotatingCircle(
// color: Colors.white,
// size: 50.0,
// ))
);
});
}
});
}
}
You may consider using SharedPreferences, in which you will store the user (or maybe just the token), and then check in main if there is a token/user stored there before rendering the app; if there is a token you log in and then push to the homepage, if not you navigate directly to the login page.
SharedPrefenreces is persisted data storage that persists even if you restart the app, but Provider is a state management solution that doesn't persist between app restarts.
Here is the SharedPreferences plugin you may use.

How to switch between Auth screen and Home screen based on bool value?

I want to switch between the login screen and Home screen based on bool value(user.status) from the model class below
class User extends ChangeNotifier {
int phoneNumber;
bool status = false;
notifyListeners();
}
The bool User.status value is flipped from below function
User _user = Provider.of<User>(context);
...
...
if (form.validate()) {
_user.status = true;
}
The below function has to listen to the changes in the status value from the User model and change the screen to Home().
class Wrapper extends StatelessWidget {
#override
Widget build(BuildContext context) {
User authStatus = Provider.of<User>(context);
return authStatus.status ? Home() : Auth();
}
}
I don't have any errors, all the values are updating accordingly but the Wrapper() is not being rebuilt after listening to the changes from ChangeNotifier
Here's how I do it with Provider :
routes: {
"/": (context) => MainPage(),
"/detail": (context) => UserDetailPage(),
},
builder: (context, child) {
return Consumer<UsersProvider>(
child: child,
builder: (context, provider, child) {
final value = provider.user;
if (!value.status) {
return Navigator(
onGenerateRoute: (settings) => MaterialPageRoute(
settings: settings, builder: (context) => LoginPage()),
);
}
return MultiProvider(
providers: [
ChangeNotifierProvider(create: (context) => UsersProvider()),
ChangeNotifierProvider(
create: (context) => InvoicesProvider()),
ChangeNotifierProvider(create: (context) => EventsProvider()),
],
child: child,
);
},
);
},
Basically use builder in main.dart and defines routes, then inside builder use Consumer were child is the initial route MainPage() so if the user already login they will go there, and if not, base on status they will redirect to LoginPage(). I hope you can understand feel free to comment

MultiBlocProvider not instantiating all bloc providers - how to properly work with MultiBlocProvider?

I am using flutter_bloc and leveraging the MultiBlocProvider widget. Things were working fine, and then at some point, my MultiBlocProvider just refused to instantiate any new Blocs I created and added to it. I am pretty new to flutter_bloc so any pointers would help.
From my sample below, I had ItemListsBloc and CartBloc before this problem, and those continue to get instantiated correctly. The problem here is the LoadFaves bloc, and any other new blocs i have tried to add.
Any help would be appreciated.
Here is my MultiBlocProvider:
#override
Widget build(BuildContext context) {
return MultiBlocProvider(
providers: [
BlocProvider(
create: (context) => ItemListsBloc()..add(LoadItemLists()),
),
BlocProvider(
create: (context) =>
FaveBloc()..add(LoadFaves('3pujK2EPXFaIPue3F42kSMTLgDV2')),
),
BlocProvider(
create: (context) => CartBloc()..add(CartInitialize()),
),
],
child: BlocBuilder<ItemListsBloc, ItemListsState>(
builder: (context, state) {
if (state is ItemListsLoaded) {
return Column(children: [
Categories(items: state.items.values.toList()),
SizedBox(
height: 10.0,
),
Expanded(
child: ListView(
shrinkWrap: true,
children: _buildItemLists(state.itemLists, state.items),
),
)
]);
}
return Container();
},
),
);
}
And here are the "problematic" blocs:
class FaveBloc extends Bloc<FaveEvent, FaveState> {
final FavesRepository _favesRepository = FavesRepository.instance;
StreamSubscription _favesSubscription;
#override
FaveState get initialState => FaveInitial();
#override
Stream<FaveState> mapEventToState(
FaveEvent event,
) async* {
if (event is LoadFaves) {
yield* _mapLoadFavesToState(event);
} else if (event is UpdateFaves) {
yield* _mapUpdateFavesToState(event);
}
}
Stream<FaveState> _mapLoadFavesToState(LoadFaves event) async* {
_favesSubscription?.cancel();
_favesSubscription = _favesRepository.faves(event.userId).listen(
(faves) => add(UpdateFaves(faves)),
);
}
Stream<FaveState> _mapUpdateFavesToState(UpdateFaves event) async* {
yield FavesUpdated(event.faves);
}
}
and
class OrderBloc extends Bloc<OrderEvent, OrderState> {
final OrderRepository _orderRepository = OrderRepository.instance;
StreamSubscription _ordersSubscription;
StreamSubscription _currOrderSubsction;
#override
OrderState get initialState => OrdersUnitialized();
#override
Stream<OrderState> mapEventToState(
OrderEvent event,
) async* {
if (event is OrderCreated) {
yield* _mapOrderCreatedToState(event);
} else if (event is OrdersUpdated) {
yield* _mapOrderUpdateToState(event);
} else if (event is OrderLoadRequest) {
yield* _mapLoadReqToState();
}
}
Stream<OrderState> _mapLoadReqToState() async* {
_ordersSubscription?.cancel();
_ordersSubscription = _orderRepository.orders().listen(
(orders) => add(OrdersUpdated(orders)),
);
}
Stream<OrderState> _mapOrderCreatedToState(OrderCreated event) async* {
var orderId = await _orderRepository.createNewOrder(event.order);
var state = this.state as OrdersLoadSuccess;
yield state.copyWith(currentOrderId: orderId);
}
Stream<OrderState> _mapOrderUpdateToState(OrdersUpdated event) async* {
yield OrdersLoadSuccess(orders: event.orders);
}
#override
Future<void> close() {
_ordersSubscription?.cancel();
_currOrderSubsction?.cancel();
return super.close();
}
}
Thank you very much in advance
By default, blocs are created lazily by BlocProvider which means create will not be called until the bloc is looked up via BlocProvider.of(context). You can set lazy to false on BlocProvider to force the blocs to be created immediately.
BlocProvider(
lazy: false,
create: (_) => MyBloc(),
child: MyChild(),
)
From Felix A.
set lazy parameter to false
BlocProvider(
lazy: false,
create: (BuildContext context) => BlocA(),
child: ChildA(),
);
According to docs:
By default, Create is called only when the instance is accessed. To override this behavior, set lazy to false.

How to delay returning a screen in BlocBuilder Flutter

In my app I use flutter_bloc for state management and in main() I use a BlocBuilder for the Authentication which if it receive an Authenticated state returns MapScreen, if state is Unauthenticated it returns the LoginScreen, else returns the Splashscreen. I'd like to control how long Splashscreen is displayed so I tried adding a timer in BlocBuilder inside state checking but it never returns the screen. How would I set Splashscreen stay visible for a certain amount of time?
As always many thanks for your time and help.
This is the BlocBuilder:
home: BlocBuilder<AuthenticationBloc, AuthenticationState>(
builder: (context, state) {
if (state is Unauthenticated) {
// Timer(Duration(seconds: 10), () {
return LoginScreen(userRepository: _userRepository);
// });
}
if (state is Authenticated) {
// Timer(Duration(seconds: 50), () {
return MultiBlocProvider(
providers: [
BlocProvider<DefaultsBloc>(
lazy: false,
create: (context) => DefaultsBloc()..add(InitializeRemote()),
),
BlocProvider<TrackingBloc>(
create: (context) => TrackingBloc(),
),
BlocProvider<DirectionsBloc>(
create: (context) => DirectionsBloc(),
),
BlocProvider<GeoBloc>(
create: (context) => GeoBloc(),
),
BlocProvider<RouteBloc>(
lazy: false,
create: (context) => RouteBloc(),
),
BlocProvider<SchedulerBloc>(
create: (context) => SchedulerBloc(),
),
BlocProvider<CheckerBloc>(
create: (context) => CheckerBloc(),
),
BlocProvider<LocationBloc>(
lazy: false,
create: (context) => LocationBloc(
mapRepository: _mapRepository,
)
..add(GetLocationStream())
..add(GetLocation())
..add(GetIsoLocationUser())),
BlocProvider<AlertBloc>(
create: (context) => AlertBloc(
alertRepository: _alertRepository, user: state.user),
),
BlocProvider<LocalNotificationBloc>(
lazy: false,
create: (context) => LocalNotificationBloc(),
)
],
child: MapScreen(
// mapRepository: _mapRepository,
user: state.user,
// alertRepository: FirebaseAlertRepository(),
),
);
// });
}
return SplashScreen();
},
),
I had to create a new event StartApp to be the first sent to bloc and then in bloc I set a timer to add the AppStartedevent that starts all the authentication logic.
Stream<AuthenticationState> mapEventToState(
AuthenticationEvent event) async* {
if (event is StartApp) {
yield* _startAppToState();
}
if (event is AppStarted) {
yield* _mapAppStartedToState();
} else if (event is LoggedIn) {
yield* _mapLoggedInToState();
} else if (event is LoggedOut) {
yield* _mapLoggedOutToState();
}
}
Stream<AuthenticationState> _startAppToState() async* {
Timer(Duration(seconds: 5), () {
add(AppStarted());
});
}
Stream<AuthenticationState> _mapAppStartedToState() async* {
try {
final isSignedIn = await _userRepository.isSignedIn();
if (isSignedIn) {
final user = await _userRepository.getUser();
yield Authenticated(user);
} else {
yield Unauthenticated();
}
} catch (_) {
yield Unauthenticated();
}
}
I hope this will help others.
Cheers.
You can't use a timer in the build method. You can create a new StatefulWidget and then add a timer in initState whcih navigates to the next screen, which will be the widget you currently have used for home.
import 'dart:async';
import 'package:flutter/material.dart';
class Splash extends StatefulWidget {
#override
_SplashState createState() => _SplashState();
}
class _SplashState extends State<Splash> {
#override
void initState() {
super.initState();
Timer(
const Duration(seconds: 1),
() => Navigator.of(context).pushReplacement(
MaterialPageRoute(builder: (context) => OtherScreen()),
),
);
}
#override
Widget build(BuildContext context) {
return Material(
child: Center(
child: Text('Splash'),
),
);
}
}

BlocProvider.of() called with a context that does not contain a Bloc of type TrackingBloc

I'm trying to provide a TrackingBloc to MapScreen but when sending an event from onPressed I get the error BlocProvider.of() called with a context that does not contain a Bloc of type TrackingBloc.
MapScreen also uses a MapBloc provided from main(), but for TrackingBloc I want to make it local, not to clutter MultiBlocProviderin main().
I tried:
To use the bloc: parameter in the BlocListener<TrackingBloc, TrackingState>, as I've been told that it just provides the bloc as a BlocProvider would(https://github.com/felangel/bloc/issues/930#issuecomment-593790702) but it didn't work.
Then I tried making MultiBlocLister a child of a MultiBlocProvider and set TrackingBloc there, but still get the message.
Set TrackingBlocin the MultiBlocProvider in `main() and worked as expected.
Why 1 and 2 don't provide TrackingBlocto the tree?
Many thanks for your help.
MapScreen:
class MapScreen extends StatefulWidget {
final String name;
final MapRepository _mapRepository;
MapScreen(
{Key key, #required this.name, #required MapRepository mapRepository})
: assert(mapRepository != null),
_mapRepository = mapRepository,
super(key: key);
#override
_MapScreenState createState() => _MapScreenState();
}
class _MapScreenState extends State<MapScreen> {
List<Marker> alerts;
LatLng userLocation;
MapController _mapController = MapController();
#override
Widget build(BuildContext context) {
return MultiBlocProvider(
providers: [
BlocProvider<TrackingBloc>(create: (context) {
return TrackingBloc();
}),
],
child: MultiBlocListener(
listeners: [
BlocListener<MapBloc, MapState>(
listener: (BuildContext context, MapState state) {
if (state is LocationStream) {
setState(() {
userLocation = (state).location;
// print(
// ' #### MapBloc actual user location from stream is : $userLocation');
});
}
if (state is MapCenter) {
userLocation = (state).location;
// print(' #### MapBloc initial center location is : $userLocation');
_mapController.move(userLocation, 16);
}
}),
BlocListener<TrackingBloc, TrackingState>(
// bloc: TrackingBloc(),
listener: (BuildContext context, TrackingState state) {
if (state is TrackedRoute) {
List<Position> route = (state).trackedRoute;
print(route);
}
}),
],
child: Scaffold(
main():
runApp(
MultiBlocProvider(
providers: [
BlocProvider<AuthenticationBloc>(
create: (context) {
return AuthenticationBloc(
userRepository: UserRepository(),
)..add(AppStarted());
},
),
BlocProvider<MapBloc>(create: (context) {
return MapBloc(
mapRepository: mapRepository,
)
..add(GetLocationStream())
..add(GetLocation());
}),
BlocProvider<TrackingBloc>(create: (context) {
return TrackingBloc();
}),
// BlocProvider<AlertBloc>(create: (context) {
// return AlertBloc(
// alertRepository: alertRepository,
// )..add(LoadAlerts());
// }),
],
child:
Right of the bat, I can see two things are wrong with your code.
First: You provide multiple TrackingBloc, in main and MapScreen.
Second: You are accessing TrackingBloc via BlocListener within the same context where you provide it (the second BlocProvider(create: (context) {return TrackingBloc();})). My guess is this is what causing the error.
BlocProvider.of() called with a context that does not contain a Bloc of type TrackingBloc
I think by simply removing BlocProvider in the MapScreen will do the job.
I was providing TrackingBlocfrom the wrong place in the widget tree.
I can provide the bloc globally which I don't need, so to provide it locally as I want, I have to provide it from Blocbuilderin main() which is returning MapScreen.
Changing main() from:
return MaterialApp(
home: BlocBuilder<AuthenticationBloc, AuthenticationState>(
builder: (context, state) {
if (state is Unauthenticated) {
return LoginScreen(userRepository: _userRepository);
}
if (state is Authenticated) {
// BlocProvider.of<MapBloc>(context).add(GetLocationStream());
// BlocProvider.of<AlertBloc>(context).add(LoadAlerts());
return MapScreen(
mapRepository: _mapRepository,
name: state.displayName,
// alertRepository: FirebaseAlertRepository(),
);
}
if (state is Unauthenticated) {
return LoginScreen(userRepository: _userRepository);
}
return SplashScreen();
},
),
);
to:
return MaterialApp(
home: BlocBuilder<AuthenticationBloc, AuthenticationState>(
builder: (context, state) {
if (state is Unauthenticated) {
return LoginScreen(userRepository: _userRepository);
}
if (state is Authenticated) {
// BlocProvider.of<MapBloc>(context).add(GetLocationStream());
// BlocProvider.of<AlertBloc>(context).add(LoadAlerts());
return MultiBlocProvider(
providers: [
BlocProvider<TrackingBloc>(create: (context) {
return TrackingBloc();
}),
],
child: MapScreen(
mapRepository: _mapRepository,
name: state.displayName,
// alertRepository: FirebaseAlertRepository(),
),
);
return MapScreen(
mapRepository: _mapRepository,
name: state.displayName,
// alertRepository: FirebaseAlertRepository(),
);
}
if (state is Unauthenticated) {
return LoginScreen(userRepository: _userRepository);
}
return SplashScreen();
},
),
);
makes it work as I intended.
Then in MapScreen I just use different BlocListener to listen to blocs being global as MapBloc or local as TrackingBloc :
class _MapScreenState extends State<MapScreen> {
List<Marker> alerts;
LatLng userLocation;
MapController _mapController = MapController();
#override
Widget build(BuildContext context) {
return MultiBlocListener(
listeners: [
BlocListener<MapBloc, MapState>(
listener: (BuildContext context, MapState state) {
if (state is LocationStream) {
setState(() {
userLocation = (state).location;
// print(
// ' #### MapBloc actual user location from stream is : $userLocation');
});
}
if (state is MapCenter) {
userLocation = (state).location;
// print(' #### MapBloc initial center location is : $userLocation');
_mapController.move(userLocation, 16);
}
}),
BlocListener<TrackingBloc, TrackingState>(
// bloc: TrackingBloc(),
listener: (BuildContext context, TrackingState state) {
// userLocation = (state as LocationStream).location;
if (state is TrackedRoute) {
List<Position> route = (state).trackedRoute;
print(route);
// initialLocation = (state).location.then((value) {
// print('###### value is : $value');
//// _mapController.move(value, 16.0);
// return value;
// }
// );
}
}),
],
child: Scaffold(
Hope this will help others just starting out with flutter_bloc that might not find documentation usage explanation of its widgets clearly enough.
Still have to fully understand BlocProvider's and BlocListener's bloc: property dough..
Cheers.