Scrollable AlertDialog (No longer deprecated)
Summary
An AlertDialog
now scrolls automatically when it overflows.
Context
Before this change,
when an AlertDialog
widget’s contents were too tall,
the display overflowed, causing the contents to be clipped.
This resulted in the following issues:
- There was no way to view the portion of the content that was clipped.
- Most alert dialogs have buttons beneath the content to prompt users for actions. If the content overflowed, obscuring the buttons, users might be unaware of their existence.
Description of change
The previous approach listed the title and content
widgets consecutively in a Column
widget.
Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
if (title != null)
Padding(
padding: titlePadding ?? EdgeInsets.fromLTRB(24.0, 24.0, 24.0, content == null ? 20.0 : 0.0),
child: DefaultTextStyle(
style: titleTextStyle ?? dialogTheme.titleTextStyle ?? theme.textTheme.title,
child: Semantics(
child: title,
namesRoute: true,
container: true,
),
),
),
if (content != null)
Flexible(
child: Padding(
padding: contentPadding,
child: DefaultTextStyle(
style: contentTextStyle ?? dialogTheme.contentTextStyle ?? theme.textTheme.subhead,
child: content,
),
),
),
// ...
],
);
The new approach wraps both widgets in a
SingleChildScrollView
above the button bar,
making both widgets part of the same scrollable
and exposing the button bar at the bottom of the dialog.
Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
if (title != null || content != null)
SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
if (title != null)
titleWidget,
if (content != null)
contentWidget,
],
),
),
// ...
],
),
Migration guide
You might see the following issues as a result of this change:
- Semantics tests might fail because of the addition of a
SingleChildScrollView
. - Manual testing of the
Talkback
andVoiceOver
features show that they still exhibit the same (correct) behavior as before. - Golden tests might fail.
- This change might have caused diffs in (previously passing)
golden tests since the
SingleChildScrollView
now nests both the title and content widgets. Some Flutter projects have taken to creating semantics tests by taking goldens of semantics nodes used in Flutter’s debug build.
Any semantics golden updates that reflect the scrolling container addition are expected and these diffs should be safe to accept.Sample resulting Semantics tree:
flutter: ├─SemanticsNode#30 <-- SingleChildScrollView
flutter: │ flags: hasImplicitScrolling
flutter: │ scrollExtentMin: 0.0
flutter: │ scrollPosition: 0.0
flutter: │ scrollExtentMax: 0.0
flutter: │
flutter: ├─SemanticsNode#31 <-- title
flutter: │ flags: namesRoute
flutter: │ label: "Hello"
flutter: │
flutter: └─SemanticsNode#32 <-- contents
flutter: label: "Huge content"
- Layout changes might result because of the scroll view.
- If the dialog was already overflowing,
this change corrects the problem.
This layout change is expected.
A nestedSingleChildScrollView
inAlertDialog.content
should work properly if left in the code, but should be removed if unintended, since it might cause confusion.
Code before migration:
AlertDialog(
title: Text(
'Very, very large title that is also scrollable',
textScaleFactor: 5,
),
content: SingleChildScrollView( // won't be scrollable
child: Text('Scrollable content', textScaleFactor: 5),
),
actions: <Widget>[
TextButton(child: Text('Button 1'), onPressed: () {}),
TextButton(child: Text('Button 2'), onPressed: () {}),
],
)
Code after migration:
AlertDialog(
title: Text('Very, very large title', textScaleFactor: 5),
content: Text('Very, very large content', textScaleFactor: 5),
actions: <Widget>[
TextButton(child: Text('Button 1'), onPressed: () {}),
TextButton(child: Text('Button 2'), onPressed: () {}),
],
)
Timeline
Landed in version: 1.16.3
In stable release: 1.17
References
Design doc:
API documentation:
Relevant issue:
Relevant PRs: