Flutter: ListView in a SimpleDialog
ListviewFlutterListview Problem Overview
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?
Listview Solutions
Solution 1 - Listview
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 - Listview
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 - Listview
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 - Listview
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'),
],
),
),
);
}
Solution 5 - Listview
Used SingleChildScrollView
with physics: NeverScrollableScrollPhysics()
and shrinkWrap: true
in your ListView
showDialog(
context: context,
builder: (_) {
return AlertDialog(
title: widget,
content: SingleChildScrollView( //MUST TO ADDED
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
...//any widgets,
ListView.builder(
shrinkWrap: true, //MUST TO ADDED
physics: NeverScrollableScrollPhysics(), //MUST TO ADDED
itemCount: model.length,
itemBuilder: (BuildContext c, int index) {
return ListTile();
})
],
),
),
);
},
);
Solution 6 - Listview
you can create a separate method method for SimpleDialogOptions code below:
final SimpleDialog dialog = new SimpleDialog(
title: const Text('Select assignment'),
children: <Widget>[
new SimpleDialogOption(
onPressed: () { Navigator.pop(context); },
child: const Text('Text one'),
),
new SimpleDialogOption(
onPressed: () {},
child: const Text('Text two'),
),
],
);
return dialog;
Solution 7 - Listview
Iterate List with spread operator (...)
or if List can be null then use null-aware spread operator (...?)
List<String> values = ['one', 'two', 'three'];
await showDialog(
context: context,
builder: (BuildContext context) {
return SimpleDialog(
title: Text("Select Value"),
children: [
...values.map((value) {
return SimpleDialogOption(child: Text(value));
}),
],
);
},
);
Using Listview:
List<String> values = ['one', 'two', 'three'];
await showDialog(
context: context,
builder: (BuildContext context) {
return SimpleDialog(
title: Text("Select Value"),
children: [
SizedBox(
width: MediaQuery.of(context).size.width,
child: ListView.builder(
shrinkWrap: true,
itemBuilder: (ctx, index) {
return SimpleDialogOption(
onPressed: () => Navigator.pop(context),
child: Center(
child: Text(values[index]),
),
);
},
itemCount: values.length,
),
)
],
);
},
);
Output:
Solution 8 - Listview
Use predefined width for the container which wraps the ListView.builder(). This code will help you
-
Shrink wrap to control the height from going infinity.
-
Using Limited Width for the Listview
showDialog( context: context, builder: (context) => AlertDialog( title: Text('Orders'), content: SizedBox( width: double.maxFinite, // <------- Use SizedBox to limit width child: ListView.builder( shrinkWrap: true, // <------ USE SHRINK WRAP itemCount: 5, itemBuilder: (context, index) => Text('Order'), ), ), ), );
Solution 9 - Listview
I found a way... Although it's a bit hacky, and so there may be a better option.
You'll need this package:
import 'package:flutter/services.dart';
Create the widget:
class MyDialog extends StatefulWidget {
MyDialog ({Key key}) : super(key: key);
MyDialogState createState() => new MyDialogState();
}
class MyDialogState extends State<MyDialog> {
If the screen rotates it screws things up because the dialog maintains it's original size. You can probably fix that with a bit of effort, but I just lock it to prevent rotating, like this:
@override
initState() { super.initState();
SystemChrome.setPreferredOrientations([
DeviceOrientation.portraitUp,
DeviceOrientation.portraitDown,
]);
}
Then I unlock it like this at the end:
@override
dispose() { super.dispose();
SystemChrome.setPreferredOrientations([
DeviceOrientation.landscapeRight,
DeviceOrientation.landscapeLeft,
DeviceOrientation.portraitUp,
DeviceOrientation.portraitDown,
]);
}
So that stops the dialog from screwing up. Then I get the size and width of the screen in the build method:
@override
Widget build(BuildContext context) {
double width = MediaQuery.of(context).size.width;
double height = MediaQuery.of(context).size.height;
Then followed with this layout:
return ConstrainedBox(
constraints: BoxConstraints(maxHeight: height, maxWidth: width),
child: Column(
children: <Widget>[
Expanded(
child: GridView.count(
primary: false,
padding: const EdgeInsets.all(20.0),
crossAxisSpacing: 10.0,
crossAxisCount: 3,
children: _images
)
),
]
),
);
}
..again, I don't think it's the best, but it's been working for me so far.
Solution 10 - Listview
Tried it with itemExtent property first, but that doesn't work. Simply wrap the ListView in a Container with defined height and width, if your items are static.
showDialog(
context: context,
builder: (BuildContext context) {
return new SimpleDialog(
children: <Widget>[
new Container(
height: 100.0,
width: 100.0,
child: new ListView(
children: <Widget>[
new Text("one"),
new Text("two"),
],
),
)
],
);
},
);
Solution 11 - Listview
showDialog(context: parentcontext,
builder: (context){
return SimpleDialog(
title: Text('create post'),
children: <Widget>[
SimpleDialogOption(
onPressed: handleImageTaking,
child: Text('take a pic'),
),
SimpleDialogOption(
onPressed: handleImageSelecting,
child: Text('select a pic'),
),
SimpleDialogOption(
child: Text('cancel'),
onPressed: (){
Navigator.pop(context);
},
)
],
);
});
Solution 12 - Listview
SImply in the content
we need to use a Container
with fixed height and width.
and the use ListView
as a child of Container
.
scrollDialogFunction(){
return showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text('List of Index'),
content: Container(
width: 350.0,
height: 250,// Change as per your requirement
child: ListView.builder(
itemCount: 150,
itemBuilder: (BuildContext context, int index) {
return ListTile(
title: Text(index.toString()),
);
},
),
),
actions: [Padding(
padding: const EdgeInsets.all(8.0),
child: Text("ok",style: TextStyle(fontSize: 18),),
)],
);
});
}
Solution 13 - Listview
A good alernative is setting scrollAble to true in AlertDialog and using a Column as content. This will scale to fit the screen
AlertDialog(
scrollable: true,
title: Text("Gjenta sjekkliste"),
content: Column(
mainAxisSize: MainAxisSize.min,
children: choices
.map((e) => ListTile(
title: Text(e ?? "Ingen gjentakelse"),
onTap: () {
Navigator.pop(context, e);
},
))
.toList(),
),
);