Flutter: ListView in a SimpleDialog
I want to show a SimpleDialog with ListView.builder in my Flutter app with this code:
showDialog(
context: context,
builder: (BuildContext context) {
return new SimpleDialog(
children: <Widget>[
new FittedBox(
child: new ListView(
children: <Widget>[
new Text("one"),
new Text("two"),
],
),
)
],
);
},
);
which gives this error (sorry, I couldn't wrap the logs as code because Stackoverflow complains that there's too much code):
══╡ EXCEPTION CAUGHT BY RENDERING LIBRARY ╞═════════════════════════════════════════════════════════ I/flutter ( 4481): The following assertion was thrown during performLayout(): I/flutter ( 4481): RenderViewport does not support returning intrinsic dimensions. I/flutter ( 4481): Calculating the intrinsic dimensions would require instantiating every child of the viewport, which I/flutter ( 4481): defeats the point of viewports being lazy. I/flutter ( 4481): If you are merely trying to shrink-wrap the viewport in the main axis direction, consider a I/flutter ( 4481): RenderShrinkWrappingViewport render object (ShrinkWrappingViewport widget), which achieves that I/flutter ( 4481): effect without implementing the intrinsic dimension API. I/flutter ( 4481): ... I/flutter ( 4481): Another exception was thrown: RenderBox was not laid out: RenderPhysicalShape#83d92 relayoutBoundary=up2 NEEDS-PAINT I/flutter ( 4481): Another exception was thrown: 'package:flutter/src/rendering/shifted_box.dart': Failed assertion: line 310 pos 12: 'child.hasSize': is not true. I/flutter ( 4481): Another exception was thrown: RenderBox was not laid out: RenderPhysicalShape#83d92 relayoutBoundary=up2
I tried using Container with specific height and width, and it works, but I want the ListView to fit itself in the Dialog.
How to include a ListView in a SimpleDialog?
Solution 1:
Just wrap ListView.builder
in a Container
with a specific height and width.
Widget setupAlertDialoadContainer() {
return Container(
height: 300.0, // Change as per your requirement
width: 300.0, // Change as per your requirement
child: ListView.builder(
shrinkWrap: true,
itemCount: 5,
itemBuilder: (BuildContext context, int index) {
return ListTile(
title: Text('Gujarat, India'),
);
},
),
);
}
And call the above method in showDialog.
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text('Country List'),
content: setupAlertDialoadContainer(),
);
});
EDITED:
You can go with @Rap's comment too.
Solution 2:
Wraping the ListView in a Container and giving it a width: double.maxFinite, solves the problem with iOS/Android having trouble with ListView inside a dialog:
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
content: Container(
width: double.maxFinite,
child: ListView(
children: <Widget>[]
),
),
);
}
);
In the case of a ListView inside a Column that is inside an AlertDialog:
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
content: Container(
width: double.maxFinite,
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Expanded(
child: ListView(
shrinkWrap: true,
children: <Widget>[]
)
)
]
),
),
);
}
);
Solution 3:
This is a more general answer for future visitors.
How to create a dialog with a list
If you want a dialog with a ListView, you should consider a SimpleDialog. A SimpleDialog is designed to show options in a list (as opposed to an AlertDialog, which is meant to notify the user of something).
Here is a simple example:
The process of creating a SimpleDialog is basically the same as for an AlertDialog (they are both based on Dialog), except that you define list item widgets called SimpleDialogOptions instead of buttons. When a list option is pressed a callback is fired that you can respond to.
// set up the list options
Widget optionOne = SimpleDialogOption(
child: const Text('horse'),
onPressed: () {},
);
Widget optionTwo = SimpleDialogOption(
child: const Text('cow'),
onPressed: () {},
);
Widget optionThree = SimpleDialogOption(
child: const Text('camel'),
onPressed: () {},
);
Widget optionFour = SimpleDialogOption(
child: const Text('sheep'),
onPressed: () {},
);
Widget optionFive = SimpleDialogOption(
child: const Text('goat'),
onPressed: () {},
);
// set up the SimpleDialog
SimpleDialog dialog = SimpleDialog(
title: const Text('Choose an animal'),
children: <Widget>[
optionOne,
optionTwo,
optionThree,
optionFour,
optionFive,
],
);
// show the dialog
showDialog(
context: context,
builder: (BuildContext context) {
return dialog;
},
);
Handling option presses
When a user clicks an item you can close the dialog an perform some action.
Widget optionOne = SimpleDialogOption(
child: const Text('horse'),
onPressed: () {
Navigator.of(context).pop();
_doSomething();
},
);
Notes
- The documentation recommends using a switch to return an enum as a Future.
- See also How to make an AlertDialog in Flutter?
Code
Here is the full code for the example above.
main.dart
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'SimpleDialog',
home: Scaffold(
appBar: AppBar(
title: Text('SimpleDialog'),
),
body: MyLayout()),
);
}
}
class MyLayout extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: RaisedButton(
child: Text('Show alert'),
onPressed: () {
showAlertDialog(context);
},
),
);
}
}
// replace this function with the examples above
showAlertDialog(BuildContext context) {
// set up the list options
Widget optionOne = SimpleDialogOption(
child: const Text('horse'),
onPressed: () {
print('horse');
Navigator.of(context).pop();
},
);
Widget optionTwo = SimpleDialogOption(
child: const Text('cow'),
onPressed: () {
print('cow');
Navigator.of(context).pop();
},
);
Widget optionThree = SimpleDialogOption(
child: const Text('camel'),
onPressed: () {
print('camel');
Navigator.of(context).pop();
},
);
Widget optionFour = SimpleDialogOption(
child: const Text('sheep'),
onPressed: () {
print('sheep');
Navigator.of(context).pop();
},
);
Widget optionFive = SimpleDialogOption(
child: const Text('goat'),
onPressed: () {
print('goat');
Navigator.of(context).pop();
},
);
// set up the SimpleDialog
SimpleDialog dialog = SimpleDialog(
title: const Text('Choose an animal'),
children: <Widget>[
optionOne,
optionTwo,
optionThree,
optionFour,
optionFive,
],
);
// show the dialog
showDialog(
context: context,
builder: (BuildContext context) {
return dialog;
},
);
}
Solution 4:
adding width: double.maxFinite
to the container
solved my problem.
@override
Widget build(BuildContext context) {
return AlertDialog(
title: Text('Sample Dialog'),
content: Container(
width: double.maxFinite,
child: ListView(
children: <Widget>[
Text('Item 1'),
Text('Item 2'),
],
),
),
);
}