Some events added in the MultiBlocProvider doesn't get dispatched Flutter - flutter

I have added a new bloc SyncBloc to a MultiBlocProvider (flutter_bloc package) that creates all the blocs needed for MapScreen and for some it also adds Events needed to present data in the screen itself.
The problem is that while location events (for LocationBloc) are added correctly from the MultiBlocProvider itself, sync events (for SyncBloc) are not. If I instead add them from MapScreen's MultiBlocListener as
BlocProvider.of<SyncBloc>(context).add(SyncLanguages());
they work as expected so looks like SyncBloc has been provided correctly.. Can you spot what I'm doing wrong with the newer SyncBloc or point me in the right direction?
As always thank you very much for your time and help.
This is the MultiBlocProvider in main():
#override
Widget build(BuildContext context) {
return MaterialApp(
localizationsDelegates: [
const AppLocalizationsDelegate(),
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
],
supportedLocales: [
const Locale('en', ''),
const Locale('it', ''),
const Locale('es', ''),
],
localeResolutionCallback:
(Locale locale, Iterable<Locale> supportedLocales) {
for (Locale supportedLocale in supportedLocales) {
if (supportedLocale.languageCode == locale.languageCode ||
supportedLocale.countryCode == locale.countryCode) {
return supportedLocale;
}
}
return supportedLocales.first;
},
debugShowCheckedModeBanner: false,
home: BlocBuilder<AuthenticationBloc, AuthenticationState>(
builder: (context, state) {
if (state is Unauthenticated) {
return LoginScreen(userRepository: _userRepository);
}
if (state is Authenticated) {
return MultiBlocProvider(
providers: [
BlocProvider<TrackingBloc>(
create: (context) => TrackingBloc(),
),
BlocProvider<DirectionsBloc>(
create: (context) => DirectionsBloc(),
),
BlocProvider<GeoBloc>(
create: (context) => GeoBloc(),
),
BlocProvider<RouteBloc>(
create: (context) => RouteBloc(),
),
BlocProvider<SchedulerBloc>(
create: (context) => SchedulerBloc(),
),
BlocProvider<CheckerBloc>(
create: (context) => CheckerBloc(),
),
BlocProvider<LocationBloc>(
create: (context) => LocationBloc(
mapRepository: _mapRepository,
)
..add(GetLocationStream())
..add(GetLocation())
..add(GetIsoLocationUser())),
BlocProvider<SyncBloc>(
create: (context) =>
SyncBloc()..add(SyncLanguages())..add(SyncIcons())),
BlocProvider<AlertBloc>(create: (context) {
return AlertBloc(
alertRepository: _alertRepository,
);
}),
],
child: MapScreen(
// mapRepository: _mapRepository,
user: state.user,
// alertRepository: FirebaseAlertRepository(),
),
);
}
return SplashScreen();
},
),
navigatorObservers: [
FirebaseAnalyticsObserver(analytics: analytics),
],
);
SyncEvent:
abstract class SyncEvent {
const SyncEvent();
#override
List<Object> get props => [];
}
class SyncLanguages extends SyncEvent {}
class SyncIcons extends SyncEvent {}
and SyncBloc:
class SyncBloc extends Bloc<SyncEvent, SyncState> {
#override
SyncState get initialState => InitialState();
Stream<SyncState> mapEventToState(SyncEvent event) async* {
if (event is SyncLanguages) {
print('SyncLanguages received');
}
if (event is SyncIcons) {
print('SyncIcons received');
}
}
}

The problem has to do with the BlocProvider's create method being lazy by default. So until the .of method is called BlocProvider doesn't create the bloc. To make it create the bloc immediately just set lazy: parameter to false.
BlocProvider<LocationBloc>(
lazy: false,
create: (context) => LocationBloc(
mapRepository: _mapRepository,
)
..add(GetLocationStream())
..add(GetLocation())
..add(GetIsoLocationUser())),
BlocProvider<SyncBloc>(
lazy: false,
create: (context) => SyncBloc()
..add(SyncLanguages())
..add(SyncIcons())),
This actually works, though AuthenticationBloc and LocationBloc events were sent even without the lazy parameter set to false. Still gotta check why that but I guess those two blocs are being created respectively by a BlocBuilder and a BlocListener. I'll edit the answer as soon as I find out for sure.

Related

Flutter Bloc Stream Listen not listening to state change

I have two blocs ( WorkOrderBloc and FilterBottomSheetBloc ) that need to communicate with each other.
MultiBloc Provider
I configured a MultiBloc Provider and I am passing the WorkOrderBloc to FilterSheetBottomBloc through its constructor.
#override
Widget build(BuildContext context) {
return MultiBlocProvider(
providers: [
BlocProvider<HomeBloc>(
create: (context) => HomeBloc(
authenticationBloc: BlocProvider.of<AuthenticationBloc>(context)),
),
BlocProvider<WorkOrderBloc>(
create: (context) => WorkOrderBloc(),
),
BlocProvider<FilterBottomSheetBloc>(
create: (context) => FilterBottomSheetBloc(
workOrderBloc: BlocProvider.of<WorkOrderBloc>(context)),
),
],
child: Scaffold(
appBar: AppBar(
actions: [
EndDrawerIcon(),
],
title: Text(_appBarTitle,
style: Theme.of(context).textTheme.headline5),
backgroundColor: Theme.of(context).colorScheme.secondary,
leadingWidth: 0,
centerTitle: false,
),
endDrawer: HomePageDrawer(),
body: Container(child: pages.elementAt(_currentIndex)),
bottomNavigationBar: HomePageBottomNavigation(
index: _currentIndex,
onTap: _switchPage,
)),
);
}
}
In the FilterBottomSheeBloc, I subscribed to the WorkOrderBloc stream and listen for changes emitted from the WorkOrderBloc
FilterBottomSheetBloc
class FilterBottomSheetBloc extends Bloc<FilterBottomSheetBlocEvent,FilterBottomSheetBlocState> {
final WorkOrderBloc workOrderBloc;
late StreamSubscription workOrderBlocSubscription;
FilterBottomSheetBloc({required this.workOrderBloc})
: super(FilterBottomSheetBlocState()) {
workOrderBlocSubscription = workOrderBloc.stream.listen((state) {
print('--- WorkOrderBloc state changed ---');
if (state.status == WorkOrderStatus.success)
add(GetListOfWorkOrders(state.listOfWorkOrders));
});
on<GetListOfWorkOrders>((event, emit) {
emit(state.copyWith(listOfWorkOrders: event.listOfWorkOrders));
});
}
My WorkOrderBloc Both the emitted WorkOrderStatus.loading and WorkOrderStatus.success fine and I can track the changes using the onChange function, but for some reason the workOrderBloc.stream.listen in the FilterBottomSheetBloc isn't responding to the changes from WorkOrderBloc.
WorkOrderBloc
class WorkOrderBloc extends Bloc<WorkOrderEvent, WorkOderState> {
final _workOrderRepository = locator.get<WorkOrderRepository>();
WorkOrderBloc()
: super(WorkOderState(
status: WorkOrderStatus.inital,
listOfWorkOrders: [],
filterOptions: FilterOptions(listOfWorkOrderStatusTypes: []))) {
on<getWorkOrders>((event, emit) async {
try {
emit(state.copyWith(status: WorkOrderStatus.loading));
List<WorkOrder> workOrders = await _workOrderRepository.getWorkOrders();
emit(state.copyWith(
status: WorkOrderStatus.success, listOfWorkOrders: workOrders));
} catch (e) {
print(e);
emit(state.copyWith(
status: WorkOrderStatus.failure,
failedMessage: 'Could not load your work orders'));
}
});
Any Suggestions as to why this isn't working ?
Bloc classes are "lazy" by default. It means that they won't be initialised until you use them. So you should use at least one BlocBuilder to get them initialised, or simply use lazy: false parameter when providing a bloc class.
BlocProvider<WorkOrderBloc>(
create: (context) => WorkOrderBloc(),
lazy: false,
),

Flutter Cubit, Listen to other Cubit states

I tried to implement a FilterEventCubit that listens to the states of a LocationTrackerCubit and an EventLoaderCubit. I used the tutorial from Felix Angelov (https://bloclibrary.dev/#/fluttertodostutorial) as a template:
class EventFilterCubit extends Cubit<EventFilterState> {
final EventLoaderCubit eventLoaderCubit;
final UserCubit userCubit;
final LocationTrackerCubit locationTrackerCubit;
StreamSubscription? eventsSubscription;
StreamSubscription? locationSubscription;
EventFilterCubit(
this.eventLoaderCubit, this.userCubit, this.locationTrackerCubit)
: super(
eventLoaderCubit.state is EventsUpToDate &&
userCubit.state is UserUpToDate &&
// TODO: not working correctly
locationTrackerCubit.state is LocationLoadSuccess
? EventFilterState.filteredEventsLoadSuccess(
(eventLoaderCubit.state as EventsUpToDate).events,
EventFilter.initial(
LatLng(
(locationTrackerCubit.state as LocationLoadSuccess)
.location
.latitude,
(locationTrackerCubit.state as LocationLoadSuccess)
.location
.longitude,
),
),
)
: const EventFilterState.filteredEventsLoadInProgress(),
) {
eventsSubscription = eventLoaderCubit.stream.listen(
(state) {
if (state is EventsUpToDate) {
print("Eventloaderbloc updated events");
eventsUpdated((eventLoaderCubit.state as EventsUpToDate).events);
}
},
);
locationSubscription = locationTrackerCubit.stream.listen(
(state) {
if (state is LocationLoadSuccess) {
print("locationtrackercubit updated location");
locationUpdated(
(locationTrackerCubit.state as LocationLoadSuccess).location,
);
}
},
);
}
The EventFilterCubit then builds the feed with all the events.
When I build the app or do a hot restart everything works just fine. But it stops listening after the first state update, so anytime a new event is added or the events are reloaded, nothing happens in the UI and the updateEvents function is not triggered.
Here is also my EventLoaderCubit:
part 'event_loader_cubit.freezed.dart';
part 'event_loader_state.dart';
#injectable
class EventLoaderCubit extends Cubit<EventLoaderState> {
final IEventRepository eventRepository;
// TODO: Streams atm useless
final StreamController<List<Event>> _eventController =
StreamController<List<Event>>();
Stream<List<Event>> get eventStream => _eventController.stream;
EventLoaderCubit(this.eventRepository)
: super(
const EventLoaderState.loadInProgress(),
) {
refreshEvents();
}
Future<void> refreshEvents() async {
print("refresh events");
emit(const EventLoaderState.loadInProgress());
final eventsFailOrSuccess = await eventRepository.loadEvents();
eventsFailOrSuccess.fold(
(failure) => emit(
const EventLoaderState.loadFailure(
EventFailure.serverError(),
),
),
(events) {
_eventController.add(events);
// getIt<EventFilterCubit>().eventsUpdated(events);
emit(
EventLoaderState.eventsUpToDate(events),
);
},
);
}
}
And the provider initialization:
child: BlocProvider(
create: (context) => getIt<AuthCubit>()..initialized(),
child: BlocBuilder<AuthCubit, AuthState>(
builder: (context, state) {
if (state is Authenticated) {
return MultiBlocProvider(
providers: [
BlocProvider<LocationTrackerCubit>(
create: (context) => getIt<LocationTrackerCubit>(),
),
BlocProvider<UserCubit>(
create: (context) => getIt<UserCubit>(),
),
BlocProvider<EventLoaderCubit>(
create: (context) =>
// TODO: Doesn't execute refreshEvents()
getIt<EventLoaderCubit>(),
),
BlocProvider<EventFilterCubit>(
create: (context) => getIt<EventFilterCubit>(),
),
],
child: _materialApp(context, authedRouter),
Also, as it is written in the code, the refreshEvents() function doesn't run when the Cubit is injected.
I had also troubles with the getIt dependency injection and BloCs. I solved this by changing the bloc access to the context.read and now it works fine.
You could try something like:
return MultiBlocProvider(
providers: [
BlocProvider<UserCubit>(
lazy: false,
create: (context) => UserCubit(
getIt<IUserRepository>(),
),
),
BlocProvider<LocationTrackerCubit>(
lazy: false,
create: (context) => LocationTrackerCubit(),
),
BlocProvider<EventLoaderCubit>(
lazy: false,
create: (context) => EventLoaderCubit(
getIt<IEventRepository>(),
),
),
BlocProvider<EventFilterCubit>(
create: (context) => EventFilterCubit(
eventLoaderCubit:
BlocProvider.of<EventLoaderCubit>(context),
userCubit: BlocProvider.of<UserCubit>(context),
locationTrackerCubit:
BlocProvider.of<LocationTrackerCubit>(context),
),
),
],

Provider trying to update my count when ever will I receive Notification?

I am unable to update my Ui
//// HERE I AM GETTING THE UPDATE DATA IN CONSOLE BUT UNABLT TO UPDATE INSIDE MY UI
My NotificationCountClass
class NotificationCount extends ChangeNotifier{
var count;
NotificationCount({
this.count =0,
});
addNotificationCount(){
count++;
notifyListeners();
print("Notification Count $count");
}
}
main : here i wrap the widget inside multiprovider so that i can use it any-where in my app
return MultiProvider(
providers: [
ChangeNotifierProvider(create: (context) => AppService()),
ChangeNotifierProvider(create: (context) => NotificationCount()),
],
child: Consumer<AppService>(
builder: (context, appService, child) {
return GetMaterialApp(
title: AppStrings.APP_TITLE,
theme: AppTheme.lightTheme,
darkTheme: AppTheme.dartTheme,
navigatorKey: GlobalVariable.navigatorKey,
supportedLocales: [
Locale('en'),
],
localizationsDelegates: [
CountryLocalizations.delegate,
],
themeMode: appService.isDarkMode ? ThemeMode.dark : ThemeMode.light,
initialRoute: AppRouter.SPLASH_SCREEN,
onGenerateRoute: AppRouter.router.generator,
// routes: {
// "/":(context) =>Home(),
// "/AppChat" : (context) => AppChat(),
// },
debugShowCheckedModeBanner: false,
// home: AppChat(),
);
},
),
);
// Using State with Consumer widget so that only required wiget rebuild
Consumer<NotificationCount>(
builder: (context, value, child) {
var count = value.count;
print("Count of Not : $count");
return Text(
"$count",
style: TextStyle(
color: Colors.white,
),
);
},
),
getting NotificationCount class with provider but still unable to update UI
final notificationCount = Provider.of<NotificationCount>(context , listen: false);
I could reproduce your issue on my side, and could fix it just by using the builder of MultiProvider instead of child.
Instead of
return MultiProvider(
providers: [
ChangeNotifierProvider(create: (context) => AppService()),
ChangeNotifierProvider(create: (context) => NotificationCount()),
],
child: Consumer<AppService>(
write somthing like:
return MultiProvider(
providers: [
ChangeNotifierProvider(create: (context) => AppService()),
ChangeNotifierProvider(create: (context) => NotificationCount()),
],
builder: (context, _) => Consumer<AppService>(
...

FlutterBloc: BlocListener not consuming emitted state

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!

Flutter: Dynamic Initial Route

Dears,
I am using provider dart package which allows listeners to get notified on changes to models per se.
I am able to detect the change inside my main app root tree, and also able to change the string value of initial route however my screen is not updating. Kindly see below the code snippet and the comments lines:
void main() => runApp(_MyAppMain());
class _MyAppMain extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider<UserProvider>.value(
value: UserProvider(),
),
ChangeNotifierProvider<PhoneProvider>.value(
value: PhoneProvider(),
)
],
child: Consumer<UserProvider>(
builder: (BuildContext context, userProvider, _) {
return FutureBuilder(
future: userProvider.getUser(),
builder: (BuildContext context, AsyncSnapshot<User> snapshot) {
if (!snapshot.hasData) {
return Center(
child: CircularProgressIndicator(),
);
}
final User user = snapshot.data;
String initialScreen = LoginScreen.path;
// (1) I am able to get into the condition
if (user.hasActiveLogin()) {
initialScreen = HomeOneScreen.path;
}
return MaterialApp(
title: 'MyApp',
theme: ThemeData(
primarySwatch: Colors.green,
accentColor: Colors.blueGrey,
),
initialRoute: initialScreen,
// (2) here the screen is not changing...?
routes: {
'/': (context) => null,
LoginScreen.path: (context) => LoginScreen(),
RegisterScreen.path: (context) => RegisterScreen(),
HomeOneScreen.path: (context) => HomeOneScreen(),
HomeTwoScreen.path: (context) => HomeTwoScreen(),
RegisterPhoneScreen.path: (context) => RegisterPhoneScreen(),
VerifyPhoneScreen.path: (context) => VerifyPhoneScreen(),
},
);
},
);
},
),
);
}
}
Kindly Note the Below:
These are are paths static const strings
LoginScreen.path = "login"
RegisterScreen.path = "/register-screen"
HomeOneScreen.path = "home-one-screen"
HomeTwoScreen.path = "home-two-screen"
RegisterPhoneScreen.path = "/register-phone-screen"
VerifyPhoneScreen.path = "/verify-phone-screen"
What I am missing for dynamic initialRoute to work?
Many Thanks
According to this issue described on github issues it is not permissible to have initial route changes. At least this is what I understood. However what I did is that I replaced the initialRoute attribute with home attr. Thus this change mandates that initialScreen becomes a widget var.
The changes is shown below:
void main() => runApp(_MyAppMain());
class _MyAppMain extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider<UserProvider>.value(
value: UserProvider(),
),
ChangeNotifierProvider<PhoneProvider>.value(
value: PhoneProvider(),
)
],
child: Consumer<UserProvider>(
builder: (BuildContext context, userProvider, _) {
return FutureBuilder(
future: userProvider.getUser(),
builder: (BuildContext context, AsyncSnapshot<User> snapshot) {
if (!snapshot.hasData) {
return Center(
child: CircularProgressIndicator(),
);
}
final User user = snapshot.data;
// (1) This becomes a widget
Widget initialScreen = LoginScreen();
if (user.hasActiveLogin()) {
initialScreen = HomeOneScreen();
}
return MaterialApp(
title: 'MyApp',
theme: ThemeData(
primarySwatch: Colors.green,
accentColor: Colors.blueGrey,
),
home: initialScreen,
// (2) here the initial route becomes home attr.
routes: {
'/': (context) => null,
LoginScreen.path: (context) => LoginScreen(),
RegisterScreen.path: (context) => RegisterScreen(),
HomeOneScreen.path: (context) => HomeOneScreen(),
HomeTwoScreen.path: (context) => HomeTwoScreen(),
RegisterPhoneScreen.path: (context) => RegisterPhoneScreen(),
VerifyPhoneScreen.path: (context) => VerifyPhoneScreen(),
},
);
},
);
},
),
);
}
}
Also note on my RegistrationScreen on success api response I did Navigator.of(context).pop()
Thanks