How to show a dialog inside a futurebuilder? - flutter

I want to show a dialog if I receive an error in a futurebuilder.
If I receiver an error, I want to show a dialog and force the user to click on the button, so that he can be redirected to another page.
The problems seems to be that it is not possible to show a dialog while widget is being built.
FutureBuilder(
future: ApiService.getPosts(),
builder: (BuildContext context, AsyncSnapshot snapShot) {
if (snapShot.connectionState == ConnectionState.done) {
if (snapShot.data.runtimeType == http.Response) {
var message =
json.decode(utf8.decode(snapShot.data.bodyBytes));
showDialog(
context: context,
barrierDismissible: false,
builder: (context) {
return AlertDialog(
content: Text(message),
actions: <Widget>[
FlatButton(
child: Text("Ok"),
onPressed: () => null",
)
],
);
});
}
return ListView.separated(
separatorBuilder: (BuildContext context, int index) {
return Divider(
color: Colors.grey,
height: 1,
);
},
itemBuilder: (BuildContext context, int index) {
return _buildPostCard(index);
},
itemCount: snapShot.data.length,
);
return Center(
child: CircularProgressIndicator(),
);
},
)
If I return the AlertDialog alone, it works. But I need the showDialog because of the barrierDismissible property.
Does any one know if that is possible?
Also, is this a good way to handle what I want?
Thanks
UPDATE
For further reference, a friend at work had the solution.
In order to do what I was looking for, I had to decide which future I was going to pass to the futureBuilder.
Future<List<dynamic>> getPostsFuture() async {
try {
return await ApiService.getPosts();
} catch (e) {
await showDialog(
context: context,
barrierDismissible: false,
builder: (context) {
return AlertDialog(
content: Text(message),
actions: <Widget>[
FlatButton(
child: Text("Ok"),
onPressed: () => null",
)
],
);
});
}
}
}
Then in the futureBuilder I would just call
FutureBuilder(
future: getPostsFuture(),
Thanks

To avoid setState() or markNeedsBuild() called during build error when using showDialog wrap it into Future.delayed like this:
Future.delayed(Duration.zero, () => showDialog(...));

setState() or markNeedsBuild() called during build.
This exception is allowed because the framework builds parent widgets before its children, which means a dirty descendant will always be built. Otherwise, the framework might not visit this widget during this build phase.
So to avoid that Future Callback is used, which adds a call like this EventQueue.
class HomeScreen extends StatefulWidget {
#override
_HomeScreenState createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
Future futureCall() async {
await Future.delayed(Duration(seconds: 2));
}
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: futureCall(),
builder: (_, dataSnapshot) {
if (dataSnapshot.connectionState == ConnectionState.waiting) {
return Center(child: CircularProgressIndicator());
} else {
Future(() { // Future Callback
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text('Employee Data'),
content: Text('Do you want to show data?'),
actions: <Widget>[
FlatButton(
onPressed: () =>
Navigator.of(context).pop('No'),
child: Text('NO')),
FlatButton(
onPressed: () =>
Navigator.of(context).pop('Yes'),
child: Text('YES'))
],
));
});
return Container();
}
},
);
}
}

The builder param expects you to return a Widget. showDialog is a Future.
So you can't return that.
You show Dialog on top of other widgets, you can't return it from a build method that is expecting a widget.
What you want can be implemented the following way.
When you receive an error, show a dialog on the UI and return a Container for the builder. Modify your code to this:
if (snapShot.data.runtimeType == http.Response) {
var message =
json.decode(utf8.decode(snapShot.data.bodyBytes));
showDialog(
context: context,
barrierDismissible: false,
builder: (context) {
return AlertDialog(
content: Text(message),
actions: <Widget>[
FlatButton(
child: Text("Ok"),
onPressed: () => null",
)
],
);
});
return Container();
}

Related

Check content function (Flutter)

I use the following function to check and display the content either in Dialog or Bottom Sheet, but when executing it does not work properly, as it displays both together, what is the reason and how can the problem be solved?
Is it possible to suggest a better name for the function?
Content function:
content(BuildContext context, dynamic dialog, dynamic bottomSheet) {
(MediaQuery.of(context).orientation == Orientation.landscape) ? dialog : bottomSheet;
}
Implementation:
ElevatedButton(
child: Text('Button'),
onPressed: () {
content(context, dialog(context), bottomSheet(context));
},
),
How can this be solved?
In order to determine the Orientation of the screen, we can use the OrientationBuilder Widget. The OrientationBuilder will determine the current Orientation and rebuild when the Orientation changes.
void main() async {
runApp(const Home(
));
}
class Home extends StatefulWidget {
const Home({Key key}) : super(key: key);
#override
State<Home> createState() => _HomeState();
}
class _HomeState extends State<Home> {
#override
Widget build(BuildContext context) {
return MaterialApp(home: Scaffold(
body: Center(
child: OrientationBuilder(
builder: (context, orientation) {
return ElevatedButton(
child: Text('Button'),
onPressed: () {
revealContent(orientation,context);
},
);
},
)
),
));
}
revealContent(Orientation orientation, BuildContext context) {
orientation == Orientation.landscape ? dialog(context) : bottomSheet(context);
}
dialog(BuildContext context){
showDialog(
context: context,
builder: (context) => const Dialog(
child: Padding(
padding: EdgeInsets.all(20.0),
child: Text('test'),
),
)
);
}
bottomSheet(final BuildContext context) {
return showModalBottomSheet(
context: context,
isScrollControlled: true,
builder: (builder) => const Padding(
padding: EdgeInsets.all(20.0),
child: Text('test'),
),
);
}
}
here are screenshots:
happy coding...
The reason the function is not working properly is because you're not actually showing the dialog or bottom sheet. To show the dialog or bottom sheet, you need to call showDialog or showModalBottomSheet, respectively, and pass them the result of calling dialog or bottomSheet.
try this
void revealContent(BuildContext context, Widget dialog, Widget bottomSheet) {
(MediaQuery.of(context).orientation == Orientation.landscape)
? showDialog(context: context, builder: (context) => dialog)
: showModalBottomSheet(context: context, builder: (context) => bottomSheet);
}
You have a fundamental misunderstanding as to what your code is doing.
Take your "Implementation" and revealContent code, for example:
ElevatedButton(
child: Text('Button'),
onPressed: () {
revealContent(context, dialog(context), bottomSheet(context));
},
),
revealContent(BuildContext context, dynamic dialog, dynamic bottomSheet) {
(MediaQuery.of(context).orientation == Orientation.landscape) ? dialog : bottomSheet;
}
You think that revealContent will invoke either dialog or bottomSheet based on the orientation of the screen. What you are actually doing, however, is you are invoking both of them and then passing the result of the invocations to revealContent, which isn't actually doing anything with them.
What you need to be doing is passing the functions as callbacks to revealContent and then invoking the callbacks within the function:
ElevatedButton(
child: Text('Button'),
onPressed: () {
revealContent(context, () => dialog(context), () => bottomSheet(context));
},
),
revealContent(BuildContext context, void Function() dialog, void Function() bottomSheet) {
if (MediaQuery.of(context).orientation == Orientation.landscape) {
dialog()
} else {
bottomSheet();
}
}
You should be calling showDialog and showModalBottomSheet inside revealContent.
Dialog
dialog(BuildContext context){
return Dialog( //.. );
}
BottomSheet
bottomSheet(final BuildContext context) {
return Widget( /.. );
}
Reveal Content
void revealContent(BuildContext context, Widget dialog, Widget bottomSheet) {
if (MediaQuery.of(context).orientation == Orientation.landscape) {
return showDialog(context: context, builder: (context) => dialog);
} else {
return showModalBottomSheet(context: context, builder: (context) => bottomSheet);
}
}

Can't access bloc in showDialog context in Flutter

I´m new in the bloc pattern. I want to show up an alert dialog when I press a button, but when I use showDialog func and showDialog I want to access Bloc from context or BlocListner doesn't contain bloc and throw error is there a way to access bloc in this situatuion
Error: Could not find the correct Provider above this BlocListener<TeacherFinancialCubit, TeacherFinancialState> Widget
#override
Widget build(BuildContext context) {
return BlocBuilder<UserBloc, UserState>(
builder: (context, state) {
if(state is! Authorize) {
return const BaBaFullScreenLoading();
}
return BlocProvider<TeacherFinancialCubit>(
lazy: false,
create: (context) => TeacherFinancialCubit(locator())..getTeacherFinancial(state.user, state.account, teacherId ?? 0),
child: _masterLayout(context),
);
},
);
}
Widget _masterLayout(BuildContext context) {
Authorize authorize = context.read<UserBloc>().state as Authorize;
return MasterLayout(
showBack: true,
floatingActionButton: authorize.account.type == master ? FloatingActionButton(
child: const Icon(Icons.add),
onPressed: () {
Future.microtask(() => _showAddPaymentDialog(context));
},
) : null,
title: const Text(""),
child: WillPopScope(
onWillPop: () => Future.value(false),
child: _showTeacherFinancials(context, authorize),
),
);
}
void _showAddPaymentDialog(BuildContext context) async {
return showDialog(
barrierDismissible: false,
context: context,
builder: (_) {
return BlocListener<TeacherFinancialCubit, TeacherFinancialState>(
listener: (context, state) {
if(state is TeacherFinancialLoading) {
EasyLoadingHelper.showLoading();
}
else {
EasyLoadingHelper.dismiss();
if(state is TeacherFinancialFailed) {
EasyLoadingHelper.showToastError(context, message: state.error);
}
else if(state is TeacherFinancialSuccess) {
EasyLoadingHelper.showToastSuccess(context, message: successMessage);
Navigator.pop(context);
}
}
},
child: AlertDialog(
title: const Text(''),
content: Form(
key: addPaymentFormKey,
child: Text(""),
),
actions: [],
),
);
}
);
}
Make sure to provide the bloc to the AlertDialog. You can do something like this:
return showDialog(
...
builder: (dialogContext) => BlocProvider.value(
value: BlocProvider.of<TeacherFinancialCubit>(context),
child: BlocListener<TeacherFinancialCubit, TeacherFinancialState>(
...
),
),

Statefulwidget is not refreshing ListView

I'm saving the data that is fetched from an API to the sqflite in flutter project, everything is working good, except that after clicking a raised button the data should be insert into the table and a new page should be open but there is no data unless I refresh that page so the data appear
As you can see, here is the code of the raised button:
RaisedButton(
child: Text('Get Cities'),
onPressed: () async {
setState(() {
GetAllData.data.Getdata();
});
await Navigator.push(context, MaterialPageRoute<void>(
builder: (BuildContext context) => StoreList()));
setState(() {});
},
)
Inside the setState I'm calling a function Getdata to get the data from the sqflite, after it getting it the app should open a new page
And below is the code of the page which should show the data in a ListView:
class StoreList extends StatefulWidget { #override
_StoreListState createState() => _StoreListState();}
class _StoreListState extends State<StoreList> {
#override void initState() {
super.initState();
setState(() {
DatabaseProvider_API.db.getRoutes();
});}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Stores List'),
),
body: FutureBuilder<List<Stores>>(
future: DatabaseProvider_API.db.getStores(),
builder: (context, snapshot){
if(snapshot.data == null){
return Center(
child: CircularProgressIndicator(),
);
}
else {
return ListView.separated(
separatorBuilder: (BuildContext context, int index){
return Divider();
},
itemCount: snapshot.data.length,
itemBuilder: (BuildContext context, int index){
String name = snapshot.data[index].sTORENAME;
String name_ar = snapshot.data[index].cITY;
return ListTile(
title: Text(name),
subtitle:Text (name_ar),
onTap: ()async{
setState(() {
});
await
Navigator.push(context, MaterialPageRoute<void>(
builder: (BuildContext context) => Category() ));
},
);
},
);
}
},
),
floatingActionButton: new FloatingActionButton(
onPressed: () {
setState(() {});
},
child: new Icon(Icons.update),
),
);
}
}
Try to add the await keyword before evoke GetAllData.data.GetData()
RaisedButton(
child: Text('Get Cities'),
onPressed: () async {
// await for new data to be inserted
await GetAllData.data.Getdata();
await Navigator.push(context, MaterialPageRoute<void>(
builder: (BuildContext context) => StoreList()));
setState(() {
dataFuture = GetAllData.data.Getdata();
});
},
)

How to showDialog in FutureBuilder

In Flutter,
I used FutureBuilder to display data from API.
I would like to popup an error dialog when there's error.
However, it throws error when I called showDialog()
setState() or markNeedsBuild() called during build.
Here's my code
return FutureBuilder(
future: xxx,
builder: (context, snapshot) {
if (snapshot.hasData) {
//DO SOMETHING
} else if (snapshot.hasError) {
//TODO: Show Error
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text("Error"),
content: Text("HAHAHA"),
actions: <Widget>[
FlatButton(
child: Text("No"),
),
FlatButton(
child: Text("Yes")
),
],
);
}
);
return Container();
} else {
//DO SOMETHING
}
},
Please help to advise how could I showDialog in FutureBuilder.
We can not call setState, navigate or showDialog while build method is building widget. so, we can wait for a microsecond and meanwhile build method complete building widget, so we can show dialog.
Create a method like below.
showError() async {
await Future.delayed(Duration(microseconds: 1));
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text("Error"),
content: Text("HAHAHA"),
actions: <Widget>[
FlatButton(
child: Text("No"),
),
FlatButton(child: Text("Yes")),
],
);
});
}
And call this method Where you want to show dialog.

How to pass context to second widget tree for provider

I am try pass context to second widget tree (in function) but I get error:
Tried to use Provider with a subtype of Listenable/Stream (Model2).
This is likely a mistake, as Provider will not automatically update
dependents when Model2 is updated. Instead, consider changing Provider
for more specific implementation that handles the update mecanism,
such as:
- ListenableProvider
- ChangeNotifierProvider
- ValueListenableProvider
- StreamProvider
Future<void> _neverSatisfied({Key key, #required BuildContext context}) async {
final model2 = Provider.of<Model2>(context, listen: false);
return showDialog<void>(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return
Provider.value(value: model2, child:
AlertDialog(
title: Text('Rewind and remember'),
content: SingleChildScrollView(
child: ListBody(
children: <Widget>[
Text('You will never be satisfied.'),
Text('You\’re like me. I’m never satisfied.'),
],
),
),
actions: <Widget>[
FlatButton(
child: Text('Regret'),
onPressed: () async {
await model2.getData();
Navigator.of(context).pop();
},
),
],
),
);
},
);
}
First widget in same StatefulWidget:
#override
Widget build(BuildContext context) {
return ChangeNotifierProxyProvider<Model1, Model2>(
initialBuilder: (_) => Model2(),
builder: (_, model1, model2) => model2
..string = model1.string,
),
child: Consumer<Model2>(
builder: (context, model2, _) =>
Second (I pass context from here):
#override
Widget buildStep(BuildContext context) {
Consumer<Model2>(
builder: (context, model2, _) =>
...
_neverSatisfied(context: context); //**pass context**
Instead of Provider.value you should use ChangeNotifierProvider.value