How to use Expanded in SingleChildScrollView?

Solution 1:

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

Solution 2:

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

Solution 3:

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).

Solution 4:

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;

Solution 5:

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>[
          ...
        ]
      }
    }
  }