How to use Expanded in SingleChildScrollView?

In Flutter, when you want to scroll a widget but also want one of the children widgets to expand and take up any remaining space, things can get tricky. This is especially true when working with SingleChildScrollView. However, there’s a better way than using SingleChildScrollView.

A Better Approach: CustomScrollView with SliverFillRemaining

Instead of using SingleChildScrollView, it’s easier to use CustomScrollView with a SliverFillRemaining. Here’s an example:

CustomScrollView(
  slivers: [
    SliverFillRemaining(
      hasScrollBody: false,
      child: Column(
        children: <Widget>[
          const Text('Header'),
          Expanded(child: Container(color: Colors.red)),
          const Text('Footer'),
        ],
      ),
    ),
  ],
)

This approach is more efficient and scalable, especially when dealing with complex layouts. The SliverFillRemaining ensures that the remaining space in the layout is filled by the specified widget.

An Alternative Solution Using LayoutBuilder and ConstrainedBox

If you still want to use SingleChildScrollView, here’s an alternative solution:

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

This solution uses the LayoutBuilder widget to determine the maximum height of the layout, and then applies that constraint to a ConstrainedBox. Inside the ConstrainedBox, an IntrinsicHeight is used to ensure that the column takes up any remaining space.

A Reusable Widget: ScrollColumnExpandable

If you find yourself using this pattern frequently, consider creating a reusable widget called ScrollColumnExpandable. Here’s an example implementation:

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

This reusable widget takes in a list of child widgets and applies the necessary constraints to ensure that one of them expands and fills any remaining space.

Conclusion

In conclusion, using Expanded in conjunction with SingleChildScrollView can be tricky. A better approach is to use CustomScrollView with a SliverFillRemaining. If you still want to use SingleChildScrollView, consider using the alternative solution provided or creating a reusable widget like ScrollColumnExpandable.