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:

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

  • 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'),
          ],
        ),
      ),
    );
  }