Hello
I want to know how to show/hide Dialog when state changed.
For example in a bloc
that have state isLoading
i want to show a Dialog, and when the state isNotLoading
i want to hide this.
But I read for hide dialog i need to use Navigator.pop()
, so when I used that the Navigator pop the screen
I am trying with something like this:
bloc.state.listen(
(state) {
if(state.isLoading){
showDialog(
// ...
)
}else{
// Here I want to hide the dialog
// This Pop the current page :(
Navigator.of(context).pop()
}
}
}
Also, I implement using a Container
with Stack
, but If I put in the body
of Scafold
, the Container
with the loading widget not overlap the AppBar
. so for overlap the AppBar
i need to put this in a Column
in the body
of the Scaffold
.
I don't like this form, so i want to find how to use showDialog
or maybe other solution.
thanks,
Hey @basketball-ico check out this gist and let me know if that's what you were looking for?
Closing this for now. Let me know if you're still having trouble or have any follow up questions 馃憤
Hi @felangel thanks for help me :)
Not works.
I use like this, and the navigator pop the dialog and the page
bloc.state.listen(
(state) {
if(state.isLoading){
_dialogBloc.dispatch(ShowDialog());
}else{
_dialogBloc.dispatch(HideDialog());
}
}
}
No problem 馃憤
Are you able to share the full code with me? Did you try the gist I linked to above?
Thanks for sharing! I took a look and you don鈥檛 need the snippet where you do bloc.state.listen. That is what BlocBuilder is for. If you just remove the listen snippet it should all work.
Let me know if that helps! 馃憤
Sorry, i don't understand :(.
In the example I create the someBloc.state.listen...
because I want to show the dialog when this state is loading
and hide when not.
The state change from loading
to notLoading
in 5 seconds with Future.delayed...
when you press load data
button.
if I remove the listen, never show de dialog based on someBlocState
I think haha.
note:
if you comment this line works, but i don't know hot to hide the dialog.
Thanks
BlocBuilder calls the builder method anytime the bloc state changes. In the example you sent whenever a HideDialog event is dispatched it will update BlocBuilder which will hide the Dialog but since you also are listening to the bloc state you are going to execute the pop twice. Does that make sense?
Yes, that have sense if I listen the same bloc two times, but are two different blocs.
I update de gist
Sorry, i can't understand :(.
No worries! I鈥檓 not sure I understand your question so I鈥檓 sorry.
I don鈥檛 understand why do you have two blocs that are both trying to show/hide the dialog? I also don鈥檛 recommend directly listening to bloc state unless you have no choice. I would always recommend using BlocBuilder when you need to update ui based on state changes.
Mmm, I have 1 bloc that have the state loading
, and i want to show a dialog when have this state, and hide when not. :(
How is that different from the original gist I linked?
Just rename the states from DialogVisible to Loaded and DialogHidden to Loading and I think it does exactly what you want.
Yes, sorry it works, thanks :), I don't know what i was thinking :( haha
but...
I need to reset the state, because when use hot reload, it pop the view, and for othe use case I need to reset this state to initial state
I update the gist
I try this but in the BlocBuilder only arrive the InitialDialogState
and not DialogHidden
if (event is ShowDialog) {
yield DialogVisible();
await Future.delayed(Duration(seconds: 2));
yield DialogHidden();
// reset state
yield InitialDialogState();
}
Great! So everything is fine now?
No :(, I don't know why in the bloc builder only not arrive the second yield (yield DialogHidden();
)
if (event is ShowDialog) {
yield DialogVisible();
await Future.delayed(Duration(seconds: 2));
yield DialogHidden(); // this not arrive in BlocBuilder
yield InitialDialogState();
}
It's because you are resetting the state immediately after by yielding InitialDialogState
.
If you remove
yield InitialDialogState();
so that the code looks like:
yield DialogVisible();
await Future.delayed(Duration(seconds: 2));
yield DialogHidden();
then it will work.
I need something like this issue
So I need to create reset event, and in the UI dispatch this event after Pop the dialog ?
class Reset extends DialogEvent {}
...
else if (event is Reset) {
yield InitialDialogState();
}
...
...
Navigator.pop(context);
_dialogBloc.dispatch(Reset());
...
it works, but I prefer something like this because is less code and not need to dispatch reset events in the UI, but not work :( , @felangel so you have other way to do something like this? o what is your recommendation?
...
yield DialogHidden();
yield InitialDialogState();
...
Thanks 馃
@felangel Hello, sorry i can't understand why if I yield
sequentially the BlocBuilder
and the Delegate
not show this states transitions
if (event is ShowDialog) {
yield DialogVisible();
yield DialogHidden();
yield InitialDialogState();
}
@basketball-ico that's not the problem. If you override onTransition
in the bloc and print the transitions you'll see that all transitions are occurring. The problem is in the UI we show the dialog when the widget is ready (so that we avoid exceptions for dirty widget) so in this case the hide dialog state change is happening but the UI is not updating right away and by the time it does the state is already back to the InitialDialogState
. Does that help?
Yes, I am understand, so the only way to reset is dispatch a event to reset in te UI after Dialog is hidden ?
Yes, given the current setup I think that's the easiest way. It would help me give you advice if you could share more about what you're trying to accomplish. Usually, if you're just going to show a dialog or navigate to another screen you don't need a bloc since there's no business logic.
Thanks, 馃
I have a bloc
at the top level of my app that have a state loading
, so I want to show the loadingDialog
when this bloc have this state, and remove when not,
Other blocs
interact with this bloc
for this reason I try to show in based on this state
.
Is like your loginBloc
and your AuthenticationBloc
, the loginBloc
dispatch event to AuthenticationBloc
and I want to show the dialog based of the state of AuthenticationBloc
not LoginBloc
.
Now it is working, but i don't like the idea to call reset
event.
Thanks for the details! I don't think you should have to call reset
.
Can you share a link to the github repo? It would be much easier for me to help if I can run the app locally. Thanks!
SchedulerBinding.instance.addPostFrameCallback
resolved my problem.
The recommended approach for this moving forward is to use BlocListener
. Check out the SnackBar Recipe for more details.
@felangel If I use BlocListener
, can I just showDialog in a listener? or still requires to call showDialog withaddPostFrameCallback
. Thank you in advance. One more question, this is not related to flutter bloc but some posts used WidgetsBinding.instance.addPostFrameCallback
to show a dialog instead of SchedulerBinding.instance
. Does it have any difference using one over the other one in this scenario?
We should NEVER "do things" in response to state changes in the builder method of BlocBuilder because that method can be called many times by the Flutter framework. The builder method should be a pure function that just returns a widget in response to the state of the bloc.
I changed my code to use BlocListener
to show a dialog based on a certain state after I read the warning from the link. In addition, I've confirmed that I don't need to call addPostFrameCallback
with a listener. Thanks for your quick support!
@njovy you beat me to it haha 馃槃
Hi everyone, great thread going on here. I'm having issues similar to the ones discussed.
I'm trying to show 1. snackbar and 2. showDialog in response to state changes with bloc Listener.
The problem I'm having is that the showDialog will occur, but it won't automatically close itself when the state changes. Here's my code:
return BlocListener<AuthBloc, AuthState>(
listener: (context, state) {
if (state is AuthError) {
Flushbar(
title: 'hi',
message: 'hi',
duration: Duration(seconds: 3),
)..show(context);
}
if (state is AuthLoading) {
showDialog(
context: context,
builder: (_) {
return Center(
child: Container(
width: 30,
height: 30,
color: Colors.red,
));
});
}
},
I've also tried doing
SchedulerBinding.instance.addPostFrameCallback((_) {
... show dialog stuff
})
but it didn't do anything either to solve my problem
Thanks!
Sorry, turned out this was a problem of how the dialog works when it opens and closes. I needed to Navigator.pop(context). Everything worked fine when I added a condition to my BlocListener like so:
condition: (prevState, currentState) {
if (prevState is AuthLoading) {
Navigator.pop(context);
}
return true;
},
thank you
Sorry, turned out this was a problem of how the dialog works when it opens and closes. I needed to Navigator.pop(context). Everything worked fine when I added a condition to my BlocListener like so:
condition: (prevState, currentState) { if (prevState is AuthLoading) { Navigator.pop(context); } return true; },
Thank you, 100% worked !
i have a similar issue here, but i have both a listener and builder, i want to pop the showdialog if there is issue and return a widget if the state is successful or not, here is an example code block..
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'index.dart';
class SendResetCodeForm extends StatefulWidget {
@override
_SendResetCodeFormState createState() => _SendResetCodeFormState();
}
class _SendResetCodeFormState extends State<SendResetCodeForm> {
SendResetCodeBloc _bloc;
@override
void initState() {
super.initState();
_bloc = BlocProvider.of<SendResetCodeBloc>(context);
}
@override
Widget build(BuildContext context) {
return BlocConsumer<SendResetCodeBloc, SendResetCodeState>(
listener: (BuildContext context, SendResetCodeState state) {
if (state is LoadingSendResetCodeState) {
showDialog(
context: context,
barrierDismissible: false,
child: Spinner(
title: "Sending Reset Code, Please Wait ...",
),
);
}
if (state is NetworkErrorSendResetCodeState) {
Routes.sailor.pop(); // pop the loading state
}
},
builder: (BuildContext context, SendResetCodeState state) {
if (state is NetworkErrorSendResetCodeState) {
return NoInternet(previousPage: SendResetCode());
}
return Form(); // use this form to insert data
},
);
}
}
The issue here is that, the nointernet widget never shows, but the showdialog is popped, if i comment out the line for the popping of the showdialog, the network error page doesnt still show up, please what is the issue?
Sorry, turned out this was a problem of how the dialog works when it opens and closes. I needed to Navigator.pop(context). Everything worked fine when I added a condition to my BlocListener like so:
condition: (prevState, currentState) { if (prevState is AuthLoading) { Navigator.pop(context); } return true; },
But this works only once. When i try clicking the button again (that pops up the same dialog) then the dialog gets stuck there loading, and this condition block no longer gets triggered....why?
@milanobrenovic can you share a link to a sample app? Usually this means you鈥檙e not yielding the loading state anymore and/or your states are Equatable so they aren鈥檛 considered different.
Hello, I'm a bit late to the discussion - I've been a lurker here for a while, but handling modals have been something that's been very confusing to me with flutter as a whole, since I primarily relied on showDialogs for that in the past as well - but being a web developer myself, I started to think that modals are widgets too, which made it easier to wrap my head around.
Anyway, instead of using showDialog
to show modals for preventing user taps and showing background process progress, I just wrapped the whole widget with a Stack
and added the following code:
BlocProvider<MyBloc>(
create: (_) => MyBloc(),
child: Stack(
children: [
MyWidget(),
BlocBuilder<MyBloc, MyState>(
buildWhen: (previous, current) => previous.loading != current.loading || previous.loadingMessage != current.loadingMessage,
builder: (context, state) {
if (!state.loading) return Container();
return Stack(
children: [
ModalBarrier(
color: Colors.black.withAlpha(100),
),
Center(
child: AlertDialog(
title: Text(state.loadingMessage ?? "Loading..."),
elevation: 0,
)
)
]
);
}
]
)
)
class MyWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold();
}
}
You can customize the dialog and the modal barrier as much as you want, and even create a reusable widget to contain the modal code. Plus, I find it much more "safe" than having showDialogs and popping from the Navigator since there's no concrete reference to what you're popping out from. I believe there are also some libraries in pub.dev that does similar things.
Also, awesome library @felangel ! This library is so much more simpler to work with ~that~ than just using plain streams and RxDart for state management! Just wanted to give you a shout out! :)
Most helpful comment
I changed my code to use
BlocListener
to show a dialog based on a certain state after I read the warning from the link. In addition, I've confirmed that I don't need to calladdPostFrameCallback
with a listener. Thanks for your quick support!