173

How to use Expanded in SingleChildScrollView? I have a screen with Image.network, ListView.builder and Row (TextFormField and IconButton). I wrapped ListView with Expanded. How to wrap this column with SingleChildScrollView? I need to move screen when the keyboard is open to see what I am writing. When I wrap my column I have this error.

      body: SingleChildScrollView(
        child: Column(
          children: <Widget>[
            Container(
              child: GestureDetector(
                child:
                Image.network(
                  postOne.imageUrl,
                  fit: BoxFit.fitWidth,
                  height: MediaQuery
                      .of(context)
                      .size
                      .width,
                  width: MediaQuery
                      .of(context)
                      .size
                      .width,
                ),
                onLongPress: () {},
                onDoubleTap: () {},
              ),
            ),
            Expanded(
              //height: MediaQuery.of(context).size.width*0.33,
              child: ListView.builder(
                  itemCount: commentList.length,
                  itemBuilder: (context, position) {
                    return GestureDetector(
                        onLongPress: () {},
                        child: Card(
                          child: Padding(
                            padding: EdgeInsets.all(5.0),
                            child: new CheckboxListTile(
                                title: new Text(commentList
                                    .elementAt(position)
                                    .coment,
                                  style: TextStyle(fontSize: 18.0),),
                                value: values[commentList
                                    .elementAt(position)
                                    .coment],
                                onChanged: (bool value) {}),
                          ),
                        )
                    );
                  }
              ),
            ),
            Container(
              child: Row(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    new Flexible(
                      child: Theme(
                        data: new ThemeData(
                            brightness: Brightness.light,
                            primarySwatch: Colors.grey,
                            inputDecorationTheme: new InputDecorationTheme(
                              labelStyle: new TextStyle(
                                  color: Colors.black45, fontSize: 18.0
                              ),
                            )
                        ),
                        child: new Form(
                          key: _formKey,
                          child: new TextFormField(
                            validator: (value) {
                              if (value.isEmpty) {
                                return 'Please enter the comment';
                              }
                            },
                            controller: commentController,
                            decoration: new InputDecoration(
                              labelText: "Add comment",
                              //hintText: 'Add comment'
                            ),
                            keyboardType: TextInputType.text,
                          ),
                        ),
                      ),
                    ),
                    new Container(
                        margin: EdgeInsets.only(left: 10.0, top: 12.0),
                        child: new IconButton(
                            icon: new Icon(Icons.send, color: Colors.black,),
                            onPressed: () {}
                        )
                    ),
                  ]),
            ),
          ],
        ),
      ),
I/flutter ( 6816): ══╡ EXCEPTION CAUGHT BY RENDERING LIBRARY ╞═════════════════════════════════════════════════════════
I/flutter ( 6816): The following assertion was thrown during performLayout():
I/flutter ( 6816): RenderFlex children have non-zero flex but incoming height constraints are unbounded.
I/flutter ( 6816): When a column is in a parent that does not provide a finite height constraint, for example if it is
I/flutter ( 6816): in a vertical scrollable, it will try to shrink-wrap its children along the vertical axis. Setting a
I/flutter ( 6816): flex on a child (e.g. using Expanded) indicates that the child is to expand to fill the remaining
I/flutter ( 6816): space in the vertical direction.
I/flutter ( 6816): These two directives are mutually exclusive. If a parent is to shrink-wrap its child, the child
I/flutter ( 6816): cannot simultaneously expand to fit its parent.
I/flutter ( 6816): Consider setting mainAxisSize to MainAxisSize.min and using FlexFit.loose fits for the flexible
I/flutter ( 6816): children (using Flexible rather than Expanded). This will allow the flexible children to size
I/flutter ( 6816): themselves to less than the infinite remaining space they would otherwise be forced to take, and
I/flutter ( 6816): then will cause the RenderFlex to shrink-wrap the children rather than expanding to fit the maximum
I/flutter ( 6816): constraints provided by the parent.
I/flutter ( 6816): The affected RenderFlex is:
I/flutter ( 6816):   RenderFlex#9f534 relayoutBoundary=up11 NEEDS-LAYOUT NEEDS-PAINT
I/flutter ( 6816): The creator information is set to:
I/flutter ( 6816):   Column ← _SingleChildViewport ← IgnorePointer-[GlobalKey#3670d] ← Semantics ← Listener ←
I/flutter ( 6816):   _GestureSemantics ← RawGestureDetector-[LabeledGlobalKey<RawGestureDetectorState>#4878e] ←
I/flutter ( 6816):   Listener ← _ScrollableScope ← _ScrollSemantics-[GlobalKey#c5885] ← RepaintBoundary ← CustomPaint ←
I/flutter ( 6816):   ⋯
I/flutter ( 6816): The nearest ancestor providing an unbounded width constraint is:
I/flutter ( 6816):   _RenderSingleChildViewport#155d8 relayoutBoundary=up10 NEEDS-LAYOUT NEEDS-PAINT
I/flutter ( 6816):   creator: _SingleChildViewport ← IgnorePointer-[GlobalKey#3670d] ← Semantics ← Listener ←
I/flutter ( 6816):   _GestureSemantics ← RawGestureDetector-[LabeledGlobalKey<RawGestureDetectorState>#4878e] ←
I/flutter ( 6816):   Listener ← _ScrollableScope ← _ScrollSemantics-[GlobalKey#c5885] ← RepaintBoundary ← CustomPaint ←
I/flutter ( 6816):   RepaintBoundary ← ⋯
I/flutter ( 6816):   parentData: <none> (can use size)
I/flutter ( 6816):   constraints: BoxConstraints(0.0<=w<=440.8, 0.0<=h<=649.3)
I/flutter ( 6816):   size: MISSING
I/flutter ( 6816): See also: https://flutter.dev/layout/
I/flutter ( 6816): If this message did not help you determine the problem, consider using debugDumpRenderTree():
I/flutter ( 6816):   https://flutter.dev/debugging/#rendering-layer
I/flutter ( 6816):   http://docs.flutter.io/flutter/rendering/debugDumpRenderTree.html
I/flutter ( 6816): If none of the above helps enough to fix this problem, please don't hesitate to file a bug:
I/flutter ( 6816):   https://github.com/flutter/flutter/issues/new?template=BUG.md
I/flutter ( 6816): 

screen

Alex
  • 1,457
  • 1
  • 13
  • 26
Filip
  • 2,191
  • 3
  • 12
  • 25
  • 5
    You cant combine singlechildscroolview -> column -> expanded whitout give a height. Wrap you singlechildscroolview in a container or sizedbox with height: MediaQuery.of(context).size.height. – Rubens Melo May 27 '19 at 12:56
  • If I understood correctly [this](https://api.flutter.dev/flutter/widgets/ScrollPosition/ensureVisible.html) is what you are looking for... – TheMri May 27 '19 at 13:50
  • https://stackoverflow.com/a/71260393/7706354 – lava Feb 25 '22 at 02:43

11 Answers11

359

Instead of using SingleChildScrollView, It's easier to use CustomScrollView with a SliverFillRemaining.

Try this:

CustomScrollView(
  slivers: [
    SliverFillRemaining(
      hasScrollBody: false,
      child: Column(
        children: <Widget>[
          const Text('Header'),
          Expanded(child: Container(color: Colors.red)),
          const Text('Footer'),
        ],
      ),
    ),
  ],
)
tanghao
  • 4,073
  • 2
  • 13
  • 17
  • Not sure if the effect is intended, but the Expanded widget will shrink if you open keyboard on the page. – Laurensius Adi Jan 07 '21 at 04:09
  • 6
    `CustomScrollView` doesn't respect keyboard overflow – Hashem Aboonajmi Feb 10 '21 at 09:58
  • this is the best solution as it doesn't miss up your column layout and `IntrinsicHeight` – hewa jalal Oct 13 '21 at 03:33
  • 1
    Slim and robust solution. My text can stay centered with a widget pushed to top and bottom of the screen, while the scrolling still works, whenever the text is too long for the screen. Thanks! – KYL3R Jan 25 '22 at 14:53
  • 1
    This is excellent for web pages where the bottom bar sticks to the bottom. Thank you so much for solving this issue for me. – user2244131 Feb 24 '22 at 14:21
205

Try this,

LayoutBuilder(
  builder: (context, constraint) {
    return SingleChildScrollView(
      child: ConstrainedBox(
        constraints: BoxConstraints(minHeight: constraint.maxHeight),
        child: IntrinsicHeight(
          child: Column(
            children: <Widget>[
              Text("Header"),
              Expanded(
                child: Container(
                  color: Colors.red,
                ),
              ),
              Text("Footer"),
            ],
          ),
        ),
      ),
    );
  },
)

I got this solution from git issues when I get into the same situation. I don't have the git link. I think it may help you.

Reusable widget:

Note: use it, only if one of the children is Expanded

import 'package:flutter/material.dart';

class ScrollColumnExpandable extends StatelessWidget {
  final List<Widget> children;
  final CrossAxisAlignment crossAxisAlignment;
  final MainAxisAlignment mainAxisAlignment;
  final VerticalDirection verticalDirection;
  final TextDirection textDirection;
  final TextBaseline textBaseline;
  final EdgeInsetsGeometry padding;

  const ScrollColumnExpandable({
    Key key,
    this.children,
    CrossAxisAlignment crossAxisAlignment,
    MainAxisAlignment mainAxisAlignment,
    VerticalDirection verticalDirection,
    EdgeInsetsGeometry padding,
    this.textDirection,
    this.textBaseline,
  })  : crossAxisAlignment = crossAxisAlignment ?? CrossAxisAlignment.center,
        mainAxisAlignment = mainAxisAlignment ?? MainAxisAlignment.start,
        verticalDirection = verticalDirection ?? VerticalDirection.down,
        padding = padding ?? EdgeInsets.zero,
        super(key: key);

  @override
  Widget build(BuildContext context) {
    final children = <Widget>[const SizedBox(width: double.infinity)];

    if (this.children != null) children.addAll(this.children);
    return LayoutBuilder(
      builder: (context, constraint) {
        return SingleChildScrollView(
          child: Padding(
            padding: padding,
            child: ConstrainedBox(
              constraints: BoxConstraints(
                minHeight: constraint.maxHeight - padding.vertical,
              ),
              child: IntrinsicHeight(
                child: Column(
                  crossAxisAlignment: crossAxisAlignment,
                  mainAxisAlignment: mainAxisAlignment,
                  mainAxisSize: MainAxisSize.max,
                  verticalDirection: verticalDirection,
                  children: children,
                  textBaseline: textBaseline,
                  textDirection: textDirection,
                ),
              ),
            ),
          ),
        );
      },
    );
  }
}
Crazy Lazy Cat
  • 13,595
  • 4
  • 30
  • 54
  • 5
    constraint.maxHeight is not recognizeable in my code!! – evals Oct 27 '19 at 15:28
  • 3
    @evals you forgot copy LayoutBuilder( builder: (context, constraint) – umni4ek Nov 11 '19 at 08:50
  • 2
    This was such a life saver, thank you so much! IntrinsicHeight was the missing piece for me. – Paul Popiel Mar 15 '20 at 02:42
  • 2
    I believe this is the git issue you were referring to: https://github.com/flutter/flutter/issues/18711 – Paul Popiel Mar 15 '20 at 04:11
  • BoxConstraints forces an infinite height. The offending constraints were: BoxConstraints(0.0<=w<=1200.0, h=Infinity) – Manticore Mar 28 '20 at 17:38
  • How to make room for the keypad in this code(autoscrolls the page and bring the widget in question into view and the keypad below it). Adding the viewinsets didn't help, it overflowed. – Braj Nov 07 '20 at 06:37
  • You don't need the `IntrinsicHeight`. the `constraints` does the job. – Vahid Apr 10 '21 at 22:35
  • Using `IntrinsicHeight` caused an exception `RenderViewport does not support returning intrinsic dimensions` so I replaced it with `Container( height: MediaQuery.of(context).size.height...` and it worked. – Stack Underflow May 19 '21 at 01:15
  • Great solution, although IntrinsicHeight is by documentation "relatively expensive. Avoid using it where possible." And in my case IntrinsicHeight is a must, not like @Vahid says. – madlick71 Sep 16 '21 at 10:54
  • This solution does not work if the keyboard is on the screen. – cpper Sep 23 '22 at 11:58
  • Bro, this widget literally saved me! For my whole entire life I've been wondering how to add space, but make it tiny and scrollable when the device is small. Thank YOU – Dennis Barzanoff Oct 31 '22 at 11:01
96

The answer is in the error itself. When the column is inside a view that is scrollable, the column is trying to shrink-wrap its content but since you used Expanded as a child of the column it is working opposite to the column trying to shrink-wrap its children. This is causing this error because these two directives are completely opposite to each other.

As mentioned in the error logs try the following:

Consider setting mainAxisSize to MainAxisSize.min (for column) and using FlexFit.loose fits for the flexible(use Flexible rather than Expanded).

Alex
  • 1,457
  • 1
  • 13
  • 26
nick.tdr
  • 4,627
  • 4
  • 28
  • 41
48

I tried Vijaya Ragavan solution but did some adjustments to it & it still works. To use Expanded with SingleChildScrollView, I used ConstrainedBox and set its height to the height of the screen (using MediaQuery). You'll just need to make sure the screen content you put inside ConstrainedBox is not bigger than the height of the screen.

Otherwise set the height of ConstrainedBox to height of the content you want to display on the screen.

SingleChildScrollView(
    child: ConstrainedBox(
        constraints: BoxConstraints(maxHeight: MediaQuery.of(context).size.height),
        child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            crossAxisAlignment: CrossAxisAlignment.stretch,
            children: <Widget>[
                Expanded(
                    child: Text('Hello World!'),
                ),
            ],
        ),
    )
)

Edit: To subtract the height of the AppBar and/or the Status Bar, see below:

double screenHeightMinusAppBarMinusStatusBar = MediaQuery.of(context).size.height 
    - appBar.preferredSize.height
    - MediaQuery.of(context).padding.top;
Alex
  • 1,457
  • 1
  • 13
  • 26
Mimina
  • 2,603
  • 2
  • 29
  • 21
  • 1
    LayoutBuilder will actually give you the remaining space your SingleChildScrollView can have. MediaQuery.of(context).size.height will give you the total device screen height (where the AppBar already took some space) – Crazy Lazy Cat Dec 17 '19 at 13:42
  • LayoutBuilder sounds better indeed. But if someone wants to use MediaQuery, you will need to subtract the AppBar height (if SingleChildScrollView is a widget child of a Scaffold that has an appBar) and/or subtract the status/notification bar height (if SingleChildScrollView is a child widget of a SafeArea) from the total screen height, for example like this: `double screenHeightMinusAppBarMinusStatusBar = MediaQuery.of(context).size.height - appBar.preferredSize.height - MediaQuery.of(context).padding.top` – Mimina Dec 24 '19 at 19:49
  • well, I am getting a syntax error it tells me that " undefined name appBar" – Amera Abdallah Jun 16 '20 at 22:22
  • So basically he use a Expanded to fit the entire height dinamically, and you set a fixed height. Your'e such a genius – Arfmann Jan 23 '21 at 22:02
18

Simply wrap your SingleChildScrollView in a Center or an Align element.

Example :

  Align(
    alignment: Alignment.topCenter,
    child: SingleChildScrollView(
      child: Column(
        children: <Widget>[
          ...
        ]
      }
    }
  }

or

  Center(
    child: SingleChildScrollView(
      child: Column(
        children: <Widget>[
          ...
        ]
      }
    }
  }
Yann39
  • 14,285
  • 11
  • 56
  • 84
  • Using `Center` actually worked for me. `Scaffold(Center(Scrollbar(SingleChildScrollView(Container(...)))))` – Itamar Bitton May 26 '20 at 22:45
  • 1
    This doesn't work for me. – bounxye May 25 '22 at 13:53
  • very smart approach – Fathi Dev Jul 21 '22 at 09:10
  • If use Center => Center will center all items in SingleChildScrollView, this way cause the display of this item list not to be in the correct position as we want like: padding, aligntment. So, I recommend use Align (alignment: Alignment.topCenter) or SizedBox(height: double.infinity) to cover child is SingleChildScrollView. – Nghien Nghien Jul 29 '22 at 08:40
  • Wouldn't have expected that it works, but it does. Thanks! – Tomic Jul 27 '23 at 07:44
6

You can simply wrap the column in a sized box and give it a width and height as shown:

SingleChildScrollView(
        child: SizedBox(
          width: MediaQuery.of(context).size.width,
          height: MediaQuery.of(context).size.height * 0.9,
          child: Column(
            mainAxisSize: MainAxisSize.min,
            children: [
              Container() //widget here
              const Expanded(
                child: SizedBox(),
              ),
              Container() //widget here
            ],
          ),
Tomas Ward
  • 854
  • 6
  • 23
3

As already pointed out, because you are using a scrollable, you can't expand to the infinity (theoretically speaking), that's what's happening when you try to expand your ListView that is nested in a SingleChildScrollView.

You can try using a NestedScrollView, or, if it fits your demands and because you have commented out this line:

//height: MediaQuery.of(context).size.width*0.33,

You can just wrap your ListView in a ConstrainedBox (or even just a regular Container) with that height, for example, instead of the Expanded, like so:

 Container(
         height: MediaQuery.of(context).size.width*0.33,
              child: ListView.builder(
                  itemCount: commentList.length,
                ...
               )
          )

Since you are already in a scrollable, you shouldn't have issues with smaller screens, because the whole tree is scrollable.

Miguel Ruivo
  • 16,035
  • 7
  • 57
  • 87
3

Most of the answers are not taken into account wich you have a textfield widget, so when the keyboard open you will get a problem with the size of your content (it will be heigher than the screen), so you should to wrap one of the widgets inside the (expanded) at least with (flexible).

Scaffold(
    resizeToAvoidBottomInset: true,
    body:CustomScrollView(
      slivers: [
        SliverFillRemaining(
          hasScrollBody: false,
          child: Column(
            children: <Widget>[
              const TextField(),
              Expanded(
                child: Column(
                         children: [
                           Flexible(child: someWidget()),
                                   ]
                             )
                      ),
            ],
          ),
        ),
      ],
    )
)
2

The trick is to only apply the ScrollView when you need to, and otherwise to let the content expand.

Something like this works well:

class ConstrainedFlexView extends StatelessWidget {
  final Widget child;
  final double minSize;
  final Axis axis;

  const ConstrainedFlexView(this.minSize, {Key key, this.child, this.axis}) : super(key: key);

  bool get isHz => axis == Axis.horizontal;

  @override
  Widget build(BuildContext context) {
    return LayoutBuilder(
      builder: (_, constraints) {
        double viewSize = isHz ? constraints.maxWidth : constraints.maxHeight;
        if (viewSize > minSize) return child;
        return SingleChildScrollView(
          scrollDirection: axis ?? Axis.vertical,
          child: ConstrainedBox(
            constraints: BoxConstraints(
                maxHeight: isHz ? double.infinity : minSize, 
                maxWidth: isHz ? minSize : double.infinity),
            child: child,
          ),
        );
      },
    );
  }
}

Usage:

ConstrainedFlexView(600, child: FlexContent())

This will flex to fill all vertical space, but once the widget is <600px it will switch to a constrained box + scroll view, allowing the content not to be squished too much.

shawnblais
  • 1,038
  • 11
  • 23
1

If what you want is:

  • Being able to use expanded inside the SingleChildScrollView to fill the remaining screen.
  • Not being bothered by the keyboard either hidding the TextFormField you are writing into either resizing the content of the SingleChildScrollView.

I had the same problem. Here is a maybe hazardous but in my case working solution I used:

import 'package:flutter/material.dart';

class FiniteSizeSingleChildScrollViewNotBotheredByKeyboard
    extends StatefulWidget {
  final Widget child;

  const FiniteSizeSingleChildScrollViewNotBotheredByKeyboard(
      {Key? key, required this.child})
      : super(key: key);

  @override
  State<FiniteSizeSingleChildScrollViewNotBotheredByKeyboard> createState() =>
      _FiniteSizeSingleChildScrollViewNotBotheredByKeyboardState();
}

class _FiniteSizeSingleChildScrollViewNotBotheredByKeyboardState
    extends State<FiniteSizeSingleChildScrollViewNotBotheredByKeyboard> {
  double width = 0, height = 0;

  @override
  Widget build(BuildContext context) {
    return LayoutBuilder(builder: (context, constraints) {
      if (width != constraints.maxWidth) {
        width = constraints.maxWidth;
        height = constraints.maxHeight;
      }
      return SingleChildScrollView(
        child: SizedBox(
          width: width,
          height: height,
          child: widget.child,
        ),
      );
    });
  }
}


The idea is to get the available size just before the SingleChildScrollView, and then to inject this size into a SizedBox which is inside the SingleChildScrollView. Also, to avoid the keyboard changing this size, there is a if condition which prevents changing the height if the width has not changed.

The only issue I uncontered yet with this custom widget, is that if a TextFormField controller inside this widget (lets call it widget A) call setState on a widget B containing this widget A which itself is a child of the keyed Form associated with the TextFormField, The contoller will trigger a rebuild at the same time as the keyboard will trigger a rebuild of the widget A, which generate an exception. To avoid this put the keyed Form inside the widget A (and not above).

niko
  • 71
  • 1
  • 5
1

I ran into the problem that a widget within the sub tree of the SliverFillRemaining / IntrinsicHeight was using a LayoutBuilder. And LayoutBuilder cannot be used in any widget tree that calculates its intrinsic dimensions (You will get an error saying that LayoutBuilder does not support returning intrinsic dimensions).

Since SliverFillRemaining with hasScrollBody: false also calculates the intrinsic dimensions of its child, it cannot be combined with any descendant widget that uses LayoutBuilder.

It is therefore not possible to combine both options in the same widget sub tree.

If your layout, however, does not use LayoutBuilder as a descendant of the SliverFillRemaining / IntrinsicHeight widget, but somewhere else in the scroll view, you can simply put it in a different sliver. Example reusing tanghao's code could look like this:

CustomScrollView(
  slivers: [
    // Use SliverList or any different sliver to display the children that use LayoutBuilder
    SliverList(
       delegate: SliverChildListDelegate(childrenContainingLayoutBuilder),
    ),
    // Use the SliverFillRemaining for the sub tree that uses Expanded / Flexible / Spacer etc.
    SliverFillRemaining(
      hasScrollBody: false,
      child: Column(
        children: <Widget>[
          const Text('Header'),
          Expanded(child: Container(color: Colors.red)),
          const Text('Footer'),
        ],
      ),
    ),
  ],
)
hnnngwdlch
  • 2,761
  • 1
  • 13
  • 20