Flutter: ListView in a SimpleDialog
Asked Answered
L

15

105

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?

Luck answered 30/4, 2018 at 7:23 Comment(1)
@Jus10 no, using FullscreenDialog insteadLuck
L
141

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.

Leonialeonid answered 21/11, 2018 at 10:57 Comment(3)
I only needed height or width, not both.Cakewalk
I needed to fill both height and width? why is that? @CakewalkFacility
I needed both too.Sightseeing
G
86

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>[]
                     )
                  )
               ]
            ),
         ),
      );
   }
);
Gracchus answered 29/5, 2019 at 8:15 Comment(6)
@AntonDuzenko you're right, I wrote that alert dialog had a child parameter, but it is content indeed. Anyway, does it works for you? for me works like a charmGracchus
I had a problem with this approach when using a ListView in a ColumnBithynia
@AntonDuzenko I have tried this aproach aswell with a Column, I updated the exampleGracchus
Adding "Container(width: double.maxFinite, wrapper" above column solves the problem. Seems like dialogs without this works only on new Android versions like 9.0.Polycrates
@Jose Jet I don't find it working anymore for the ios app. The listview.builder is not scrollable.Electroacoustics
You can use Flexible instead of Expanded to shrink itCharest
R
60

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:

enter image description here

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

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;
    },
  );
}
Rego answered 14/1, 2019 at 22:26 Comment(3)
Thank for this. Aside from it, how do I dismiss the dialog when the option is clicked?Laurilaurianne
@Emen: You can dismiss the dialog with Navigator.of(context).pop();Rego
This is the best choice for the functionality of choosing between several options. Whereas AlertDialog is for alerting user about situationIncautious
L
10

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'),
          ],
        ),
      ),
    );
  }
Leo answered 21/1, 2021 at 16:50 Comment(0)
M
2

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();
                    })
              ],
            ),
          ),
        );
      },
    );
Marinamarinade answered 1/11, 2020 at 12:43 Comment(0)
I
2

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:

enter image description here

Iaverne answered 12/6, 2021 at 4:54 Comment(0)
S
2

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'),
                        ),
                      ),
                    ),
                  );
    
Sedgewake answered 5/10, 2021 at 18:9 Comment(0)
C
2

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(),
      ),
    );
Cadman answered 19/2, 2022 at 20:1 Comment(0)
F
1

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;
Featureless answered 30/4, 2018 at 11:49 Comment(0)
P
1

I also had the same problem and it was solved by below:

  @override
  Widget build(BuildContext context) {
    return AlertDialog(
      title: Text('Sample Dialog'),
      content: Container(
        width: double.maxFinite, //<- this line is important
        child: ListView(
          shrinkWrap: true, //<- this line is important
          children: <Widget>[
            Text('Item 1'),
            Text('Item 2'),
            Text('Item 3'),
          ],
        ),
      ),
    );
  }

See details here.

Pelage answered 21/6, 2022 at 2:41 Comment(0)
P
0

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.

Protero answered 21/5, 2018 at 15:33 Comment(0)
T
0

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"),
            ],
          ),
        )
      ],
    );
  },
);
Tadtada answered 9/8, 2018 at 18:52 Comment(0)
S
0
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);
            },
          )
        ],
      );
    });
Straightedge answered 6/7, 2020 at 4:8 Comment(0)
N
0

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),),
                )],
              );
            });
      }
Nonparticipating answered 13/11, 2020 at 10:9 Comment(0)
B
-1

A bit late but I have found a way to solve this problem without creating a container so you will have a dynamic sized list. Instead of creating a listview, map the list to a widget then put the resulting widgets inside a column children, also don't forget to set the column size to min, that way it will only expand as much as it needs to.

    showDialog(
      context: context,
      builder: (BuildContext context) {
        return AlertDialog(
          content: Column(
            mainAxisSize: MainAxisSize.min,
            crossAxisAlignment: CrossAxisAlignment.start,
            children: list.map((element) =>
                Text(element.text)).toList(),
          ),
        );
      },
    );
Beghard answered 22/9, 2022 at 7:23 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.