When Super is called in Flutter Bloc? - flutter

I have the following code of a Bloc:
class BetBloc extends Bloc<BetEvent, BetState>{
BetBloc() : super(BetInitial() ){
on<LoadBet>(
(event, emit) async {
return;
}
}
);
and in main.dart and other sub-screens I use MultiBlocProvider in the build method
return MultiBlocProvider(
providers: [
BlocProvider(
create: (context) =>
BetBloc()..add(LoadBet()),
)
],
child: ...
I can't understand when and why super(BetInitial() ) sets the state to BetInitial. Because every time I change screen the state is reset to BetInitial, even if I don't throw an Event.
Hope I explained well enough

In bloc super() is used to define the initial state of an app. This means what will be the state of an app when you will not call any event or not emit any state. For more info about the bloc go through the bloc documentation.
https://bloclibrary.dev/#/

Related

When do we initialise a provider in flutter?

I just arrived on a flutter project for a web app, and all developers have a problem using flutter provider for state management.
What is the problem
When you arrive on a screen, the variables of the corresponding provider are initialised by calling a function of the provider. This function calls an api, and sets the variables in the provider.
Problem : This function is called in the build section of the widget. Each time the window is resized, the widget is rebuilt, and the function is called again.
What we want
We want to call an api when the page is first displayed, set variables with the result, and not call the api again when the widget is rebuilt.
What solution ?
We use a push from the first screen to go to the second one. We can call the function of the provider at this moment, to initialise the provider just before the second screen.
→ But a refresh on the second page will clear the provider variables, and the function to initialise them will not be called again.
We call the function to initialise the provider in the constructor of the second screen. Is it a good pattern ?
Thank you for your help in my new experience with flutter :)
I think you're mixing a couple different issues here:
How do you correctly initialize a provider
How do you call a method on initialization (only once)
For the first question:
In your main.dart file you want to do something like this:
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider(create: (context) => SomeProvider()),
ChangeNotifierProvider(create: (context) => AnotherProvider()),
],
child: YourRootWidget();
);
}
Then in a widget (that probably represents a "screen" in your app), you need to do something like this to consume state changes from that provider:
#override
Widget build(BuildContext context) {
return Container(
child: Consumer<SomeProvider>(
builder: (context, provider, child) {
return Text(provider.someState);
}
),
)
}
And you need to do something like this to get access to the provider to mutate state:
#override
Widget build(BuildContext context) {
SomeProvider someProvider = Provider.of<SomeProvider>(context, listen: false);
return Container(
child: TextButton(
child: Text('Tap me'),
onPressed: () async {
await someProvider.mutateSomeState();
}
),
)
}
Regarding the second question... You can (I think) just use the initState() method on a widget to make the call only 1 time. So...
#override
void initState() {
super.initState();
AnotherProvider anotherProvider = Provider.of<AnotherProvider>(context, listen: false);
Future.microtask(() {
anotherProvider.doSomethingElse();
});
}
If I'm off on any of that, I'm sorry. That mirrors my implementation and works fine/well.
A caveat here is that I think RiverPod is likely the place you really want to go (it's maybe easier to work with and has additional features that are helpful, etc.) but I've not migrated to RiverPod yet and do not have that figured out all the way.
Anyway... Good luck!
As far as I understood, you can wrap your application with MultiProvider and call the API before going to the second screen.

Do I have to specifically call dispose() when I use Provider<T> class?

I was reading the documentation and it states that ChangeNotifierProviders will automatically be disposed.
https://pub.dev/packages/provider#existing-providers
Provider
The most basic form of provider. It takes a value and exposes it, whatever the value is.
ChangeNotifierProvider
A specification of ListenableProvider for ChangeNotifier. It will automatically call ChangeNotifier.dispose when needed.
So, when I'm using a basic Provider class as the top of my widget tree, do I need to dispose() it in my app's lifecycle? Given it will not be automatically disposed, and the reason behind?
void main() {
runApp(
Provider(
create: (context) => MemberRepository(),
child: const MyApp(),
),
);
}

Consumer not updating with notifyListeners()

I have created a simple Widget Tree of my app in flutter that adresses the issue.
The issue is that when I call the method in my SleepSessionData which is a ChangeNotifier class, the consumer does not get fired.
There is no issue if the method containing notifyListeners() is called within the same widget, but when it is two sibling widgets the consumer does not update unless a setState is called.
And a image of the screen for reference
SleepSessionData extends ChangeNotifier
void updateSelectedPlaylists(List<String> selectedPlaylists) {
this.selectedPlaylists = selectedPlaylists;
notifyListeners();
_saveData();
}
PlaylistSelectorWidget
void selectedIndex(int index) {
...
context.read<SleepSessionData>().updateSelectedPlaylists(_selectedPlaylistIds);
setState(() {});
}
In my SettingsWidget I tried using both Consumer Widget and and watch() method. But it is as the notifyListeners() does not trigger the consumer/watcher. If i run a setState() (triggered by user input) the value from the the ChangeNotifier updates.
Here's my MultiProvider in main.dart
child: MultiProvider(
providers: [
ChangeNotifierProvider<MySleepData>(create: (_) => MySleepData()),
ChangeNotifierProvider<DirectoryData>(create: (_) => DirectoryData()),
FutureProvider<UserData>(
create: (_) => LocalPersistence.getUserData(),
initialData: UserData()),
FutureProvider<SleepSessionData>(
create: (_) => LocalPersistence.getSleepSessionData(),
initialData: SleepSessionData(),
)
],
You shouldn't wrap your ChangeNotifier inside the FutureProvider in order to make it works properly.
When you wrap the ChangeNotifier inside the FutureProvider it breaks the process of adding listeners to the ChangeNotifiers somehow. You always get 0 listeners when wrapping ChangeNotifier with FutureProvider. (You can verify this by debugging and check the listeners property of your ChangeNotifier instance.) That's why when calling notifyListeners(), the widgets don't rerender since they're not the listener of your ChangeNotifier instance.
So the solution to your problem would be:
Use ChangeNotifier in main.dart
child: MultiProvider(
providers: [
ChangeNotifierProvider<MySleepData>(create: (_) => MySleepData()),
ChangeNotifierProvider<DirectoryData>(create: (_) => DirectoryData()),
ChangeNotifierProvider<UserData>(create: (_) => UserData()),
ChangeNotifierProvider<SleepSessionData>(create: (_) => SleepSessionData()),
],
In the SessionSetupPage, you get the data from your local store and load the changes to SleepSessionData
loadData(SleepSessionData sessionData) async {
final data = await LocalPersistence.getData();
sessionData.setData(data);
}
Widget build(BuildContext context) {
loadData(context.read<SleepSessionData>());
// ..
}
Then the code in your PlaylistSelector and SettingsWidget should work whenever notifyListeners() is called since the listeners are bound correctly now.
so when the future completes it will overwrite this value
This is not totally the root cause of the issue though. Even if we create the SleepSessionData instance beforehand and return the same instance inside the FutureProvider, the problem still persists.
You only have a FutureProvider that holds the SleepSessionData so that will only update once when the future completes.
If you want the ui to update on changes of the result of the future then you need to listen to those changes, for instance by using a ChangeNotifierProvider.
Additionally you are giving the FutureProvider an initial data with a new instance of your SleepSessionData so when the future completes it will overwrite this value meaning that any actions you do on the initial value will be discarded.
So a better way of doing this would be to not use a FutureProvider but use a ChangeNotifierProvider and start the future to load the data within your SleepSessionData class.

Using two BLoCs in same page and passing first BLoC's state in second BLoC

I have been learning about Bloc Pattern in Flutter for a few days.
I have a page where I need to generate OTP and validate it.
There are two APIs(generateOtp, validateOtp) two implement this functionality.
In the generateOtp API response, I need to save one key i.e uniqueIdentifier.
Then I need to pass the above uniqueIdentifier and Otp value(User entered) to the validateOtp API.
I have created two separate BLoCs... generateOtpBloc, validateOtpBloc.
Using MultiBLoC Provider I am using these two BLoCs.
Navigator.of(context).push(
MaterialPageRoute<LandingPage>(
builder: (_) => MultiBlocProvider(
providers: [
BlocProvider<GenerateOtpBloc>(
create: (context) => GenerateOtpBloc(GenerateOtpInitial())
),
BlocProvider<ValidateOtpBloc>(
create: (context) => ValidateOtpBloc(ValidateOtpInitial())
)
],
child: OtpPage(),
),
),
);
I am able to invoke APIs and get the API responses in my UI page.
But how to save the uniqueIdentifier value which I get in the generateOtp and how to pass this uniqueIdentifier in the second API?
I thought of using setState() to set the state of uniqueIdentifier. But I'm receiving an error.
child: BlocBuilder<GenerateOtpBloc, GenerateOtpState>(
builder: (context, state) {
if (state is GenerateOtpLoading) {
print("**********GenerateOtpLoading*************");
return buildLoading();
} else if (state is GenerateOtpLoaded) {
print("**********GenerateOtpLoaded*************");
***//But Im getting error here.***
***setState(() {
uniqueIdentifier: state.uniqueIdentifier
});***
return buildGenerateOtpWidget(context, state.generateOtpRes);
} else {
print("**********Else*************");
print(state);
}
},
),
),
Both generateOtp and validateOtp requests and responses are completely different... that is why I used two different BLoCs.
Suggest to me the best way to handle this?
Why you try to use two blocs for handle it? you can use two events in one bloc. This is my code in the OTP login project similar to your project:
class LoginBloc extends Bloc<LoginEvent, LoginState> {
FirstApiClass _firstApi;
SecondApiClass _secondApi;
LoginBloc() : super(Loading()) {
_firstApi = FirstApiClass();
_secondApi = SecondApiClass();
}
#override
Stream<LoginState> mapEventToState(
LoginEvent event,
) async* {
if (event is GenerateOtp) {
// Use FirstApiClass
} else if (event is ValidateOtpBloc) {
// Use SecondApiClass
}
}
}
However, you can also use one Api class for this situation!
I hope it's useful for you.

Same BLoC event not triggering more than one time

I've HomeBloc from HomeView class. Basic UI architecture is as following:
HomeView extends StatelessWidget {
BlocProvider( child: HomeBody(), bloc: new HomeBloc() )
}
HomeBody extends StatefulWidget {
createState => HomeBodyState()
}
HomeBodyState extends State {
BlocBuilder(
bloc: BlocProvider.of<HomeBloc>()
child: Container(
child: Column(
children: [
BlocProvider( child: CashFlowView, bloc: new HomeBloc() )
]
)
)
)
}
CashFlowView extends StatefulWidget {
createState => CashFlowState()
}
CashFlowState extends State {
BlocBuilder(
bloc: BlocProvider.of<HomeBloc>()
child: Container(
child: Column(
children: [
ChipGroupWidget(
onClick => BlocProvider.of().add(event) // <----- Problem is here
)
]
)
)
)
}
Whole code can be found in this repository.
Problem is when any Chip inside my ChipGroup is tapped, a callback function is called in CashFlowState. Inside that, a bloc event is added to bloc with some data. But it is triggering only for first time. What's wrong in my code?
From the bloc library documentation
Extend your state class with equatable and after each event emit a new state (same type) with the new properyties (data)
In flutter bloc pattern system, the UI is rebuilt only when the state changes. If in any case when a fired event triggers the same state currently the bloc is in, the build function won't be called again, i.e, the UI won't be rebuilt.
In your case, when for the first time the CashFlowState is yielded, the whole code works just fine. But then the same state is yielded again, the event is triggered, but the build function isn't being called again, because the state never changed.
What you have to do is, create two different states along with two different events. Lets say, chipTappedEvent will yield chipTappedState, and chipResetEvent will yield chipResetState.
In the beginning you can use chipResetState or any other state as initial state. Then when the user taps on a chip, just trigger the chipTappedEvent which should yield chipTappedState.
In your listener, listen for the state chipTappedState and do what you have to do. Then immediately trigger the chipResetEvent which should yield chipResetState. In this way, when the user taps the chip again, the yielded state will be chipTappedState which will be different from chipResetState, so the build function will be called again.