show alert on cubit state in flutter - flutter

I know we can return different widgets on certain state of cubit, but how can we show an alert or other interactions on states:
BlocBuilder<LoginCubit, LoginState> (
builder: (context, LoginState loginState) {
if (loginState is LoginInitial) {
return Text("LoginInitial");
} else if (loginState is LoginLoading) {
return Text("LoginLoading");
} else if (loginState is LoginLoaded) {
return Text("LoginLoaded");
} else if (loginState is LoginError) {
return Text("LoginError");
} else {
return Container();
}
},
)
here in LoginError I want to show an alert dialog.

You can use BlocConsumer, which has both a builder and a listener:
The builder attribute is the widget builder callback you already know
The listener is a callback called when the state changes and you can do pretty much anything there.
For more fine grained control you can use buildWhen and listenWhen, which trigger respectively the builder or listener callbacks if they return true.
For example you can see how I've used BlocConsumer to show a SnackBar when an error state occurs here.
Don't mind the double check on the type
if (state is RegionalReportLoadingError)
because it's probably useless (according to the docs) and I just wanted to be sure about that when I did not have the usage of listenWhen very clear.
You can check more about BlocConsumer in the docs (Unfortunately I cannot link the anchor).

Showing dialogs, snackbars, exiting the screen or navigating somewhere else - these kind of tasks should be done in listeners, like this:
useCubitListener<BookDetailsCubit, BookDetailsPageState>(cubit, (cubit, state, context) {
state.maybeWhen(
saveBook: () => context.router.pop<bool>(true),
deleteBook: () => context.router.pop<bool>(true),
orElse: () => null,
);
}, listenWhen: (state) => (state is BookDetailsPageSaveBook || state is BookDetailsPageDeleteBook));
Here I used cubits with hooks. useCubitListener() is a global function. More on this in my Flutter cubits + hooks tutorial.

Related

flutter bloc - dispatch event to hosting widget without changing the state

I got this situation:
screen1 (widget) navigates to screen2 (widget) and passes it a function (callback)
screen2 has its own bloc (bloc2)
screen2 add event to bloc2
bloc2 performs a long async operation
when operation completes, bloc2 should notify screen2 (The problem is here)
screen2 should invoke the passed function
screen1 in a réponse should close screen2
How should bloc2 notify screen2 without changing a state.
Changing the state seems a redundant operation in this case, as I don't see a point in build function to run again (and returning a redundant widget)
Assuming you are using a BlocBuilder to build screen2, then you could use the buildWhen property to determine on which conditions to rebuild, maybe never on state-change?
You can get the blocs public properties and methods using the previous and current parameters. Those parameters are of type BlocAState.
BlocBuilder<BlocA, BlocAState>(
buildWhen: (previous, current) {
// return true/false to determine whether or not
// to rebuild the widget with state
},
builder: (context, state) {
// return widget here based on BlocA's state
}
)
Furthermore, this step:
screen2 should invoke the passed function
Should, probably, be done in a BlocListener or BlocConsumer. Maybe that is already how you do it, if not. Look into it..
So to solve your problem I would imagine that you should switch your BlocBuilder to a BlocConsumer, actually change the state, because your "long async operation" does change the state of screen2 from "not done the long thing", to "has done the long thing". Then your BlocListener can invoke the passed function, and the buildWhen property can make sure to not rebuild your screen2.
The BlocConsumer looks like this:
BlocConsumer<BlocA, BlocAState>(
listenWhen: (previous, current) {
// return true/false to determine whether or not
// to invoke listener with state
},
listener: (context, state) {
// do stuff here based on BlocA's state
// I.e. invoke the passed function
},
buildWhen: (previous, current) {
// return true/false to determine whether or not
// to rebuild the widget with state
},
builder: (context, state) {
// return widget here based on BlocA's state
}
)

how can I call async function after BlocBuilder state is success?

I have tasks list from flutter_downloader but that list is not enough to show in list. I need to show other information in list view as well as download information.
I already got the download tasks in initial state but I need to wait to get another list from bloc. after DownloadedSongListLoaded, I want to call _combineList(favouriteSongs); But I only want to return the Container after _combineList(favouriteSongs) finished.
So, how can I call async function in widget in BlocBuilder or other way around.
child: BlocBuilder<FavouriteSongBloc, FavouriteSongState>(
builder: (context, state) {
if (state is FavouriteSongError) {
return SomethingWentWrongScreen(error: state.error);
} else if (state is DownloadedSongListLoaded) {
favouriteSongs = state.favouriteSongs;
await _combineList(favouriteSongs); <== here, I want to manipulate the favouriteSongs list before binding the below Container widget.
return const Container() //ListView builder will be here
}
return const CircularProgressIndicatorWidget();
},
)

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.

How can I use Provider to provide a bloc to a PageView() without the child resubscribing everytime I switch page?

I am using Provider to provide my bloc to a widget called TheGroupPage via a static create method
static Widget create(BuildContext context, GroupModel group) {
final database = Provider.of<DatabaseService>(context);
return Provider(
create: (_) => GroupMembersBloc(database, group),
child: TheGroupPage(group),
dispose: (BuildContext context, GroupMembersBloc bloc) => bloc.dispose(),
);
}
That widget has a PageView() with 3 pages
PageView(children: [
TheGroupNotificationsView(),
TheGroupMembersView(group),
TheGroupSettingsView(group),
])
The group members view looks for the GroupMembersBloc
GroupMembersBloc bloc = Provider.of<GroupMembersBloc>(context);
I also tried to put listen to false but this did not work. And I want the widget to listen for any changes. The page uses that bloc's stream to draw a list of group members
class GroupMembersBloc{
StreamController<List<UserModel>> _controller = StreamController<List<UserModel>>();
Stream<List<UserModel>> get stream => _controller.stream;
GroupMembersBloc(DatabaseService database, GroupModel group)
{
_controller.addStream(database.groupMembersStream(group));
}
void dispose(){
_controller.close();
}
}
The problem is when I switch page inside the PageView() I get an error on the page after the first time it has been shown. The error says Bad state: Stream has already been listened to. how can I solve this?
That's because stream controllers allow only 1 Subscription (or 1 listener) , you could use the [StreamController<List<UserModel>>.broadcast()][1] constructor instead of StreamController>().
I ended up moving the StreamBuilder to the parent widget above the PageView() which fixed the problem.

perform a navigation inside the build method of a widget? - flutter

Fast question: how can I perform a navigation inside the build method of a widget?
I'm developing a Flutter App.
I use Bloc architecture.
I have screen with a create form. When the user presses a button, it calls a REST api. While the call is being executed I display a circular progress. When the progress ends I want the screen to be popped (using navigation).
To display the job status I use a Stream in the bloc and a StreamBuilder in the widget. So I want to do something like this:
return StreamBuilder<Job<T>>(
stream: jobstream,
builder: (context, snapshot) {
if (snapshot.hasData) {
if (snapshot.data.jobStatus == JobStatus.progress)
// job being executed, display progress
return Center(child: CircularProgressIndicator());
else if (snapshot.data.jobStatus == JobStatus.success)
Navigator.pop(context); // Problem here!
return Center(child: CircularProgressIndicator());
} else {
return DisplayForm();
}
});
I have problems with the line: Navigator.pop(context);
Because navigation is not allowed during a build.
How can I do this navigation?
My currect dirty solution is using the following function, but its ugly:
static void deferredPop(BuildContext context) async{
Future.delayed(
Duration(milliseconds: 1),
() => Navigator.pop(context)
);
}
You can add a callback to be executed after the build method is complete with this line of code:
WidgetsBinding.instance.addPostFrameCallback((_) => Navigator.pop(context));
'Because navigation is not allowed during a build.' that being said you should consider creating a separate function that will receive as an input something that you will take into consideration whether to pop that screen.