More useful error messages when you use Stack without a textDirection. (#59360)

This commit is contained in:
Ian Hickson 2020-06-15 09:40:10 -07:00 committed by GitHub
parent b3c237ce54
commit f7f019aa94
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 61 additions and 2 deletions

View file

@ -3262,8 +3262,21 @@ class Stack extends MultiChildRenderObjectWidget {
/// [Overflow.clip], children cannot paint outside of the stack's box.
final Overflow overflow;
bool _debugCheckHasDirectionality(BuildContext context) {
if (alignment is AlignmentDirectional && textDirection == null) {
assert(debugCheckHasDirectionality(
context,
why: 'to resolve the \'alignment\' argument',
hint: alignment == AlignmentDirectional.topStart ? 'The default value for \'alignment\' is AlignmentDirectional.topStart, which requires a text direction.' : null,
alternative: 'Instead of providing a Directionality widget, another solution would be passing a non-directional \'alignment\', or an explicit \'textDirection\', to the $runtimeType.'),
);
}
return true;
}
@override
RenderStack createRenderObject(BuildContext context) {
assert(_debugCheckHasDirectionality(context));
return RenderStack(
alignment: alignment,
textDirection: textDirection ?? Directionality.of(context),
@ -3274,6 +3287,7 @@ class Stack extends MultiChildRenderObjectWidget {
@override
void updateRenderObject(BuildContext context, RenderStack renderObject) {
assert(_debugCheckHasDirectionality(context));
renderObject
..alignment = alignment
..textDirection = textDirection ?? Directionality.of(context)
@ -3322,6 +3336,7 @@ class IndexedStack extends Stack {
@override
RenderIndexedStack createRenderObject(BuildContext context) {
assert(_debugCheckHasDirectionality(context));
return RenderIndexedStack(
index: index,
alignment: alignment,
@ -3331,6 +3346,7 @@ class IndexedStack extends Stack {
@override
void updateRenderObject(BuildContext context, RenderIndexedStack renderObject) {
assert(_debugCheckHasDirectionality(context));
renderObject
..index = index
..alignment = alignment

View file

@ -242,13 +242,33 @@ bool debugCheckHasMediaQuery(BuildContext context) {
/// assert(debugCheckHasDirectionality(context));
/// ```
///
/// To improve the error messages you can add some extra color using the
/// named arguments.
///
/// * why: explain why the direction is needed, for example "to resolve
/// the 'alignment' argument". Should be an adverb phrase describing why.
/// * hint: explain why this might be happening, for example "The default
/// value of the 'aligment' argument of the $runtimeType widget is an
/// AlignmentDirectional value.". Should be a fully punctuated sentence.
/// * alternative: provide additional advice specific to the situation,
/// especially an alternative to providing a Directionality ancestor.
/// For example, "Alternatively, consider specifying the 'textDirection'
/// argument.". Should be a funny punctuated sentence.
///
/// Each one can be null, in which case it is skipped (this is the default).
/// If they are non-null, they are included in the order above, interspersed
/// with the more generic advice regarding [Directionality].
///
/// Does nothing if asserts are disabled. Always returns true.
bool debugCheckHasDirectionality(BuildContext context) {
bool debugCheckHasDirectionality(BuildContext context, { String why, String hint, String alternative }) {
assert(() {
if (context.widget is! Directionality && context.findAncestorWidgetOfExactType<Directionality>() == null) {
why = why == null ? '' : ' $why';
throw FlutterError.fromParts(<DiagnosticsNode>[
ErrorSummary('No Directionality widget found.'),
ErrorDescription('${context.widget.runtimeType} widgets require a Directionality widget ancestor.\n'),
ErrorDescription('${context.widget.runtimeType} widgets require a Directionality widget ancestor$why.\n'),
if (hint != null)
ErrorHint(hint),
context.describeWidget('The specific widget that could not find a Directionality ancestor was'),
context.describeOwnershipChain('The ownership chain for the affected widget is'),
ErrorHint(
@ -259,6 +279,8 @@ bool debugCheckHasDirectionality(BuildContext context) {
'values, and to resolve EdgeInsetsDirectional, '
'AlignmentDirectional, and other *Directional objects.'
),
if (alternative != null)
ErrorHint(alternative),
]);
}
return true;

View file

@ -749,4 +749,25 @@ void main() {
expect(tester.getRect(find.byType(SizedBox).at(7)), const Rect.fromLTWH(0.0, 0.0, 100.0, 100.0));
expect(tester.getRect(find.byType(SizedBox).at(8)), const Rect.fromLTWH(0.0, 500.0, 100.0, 100.0));
});
testWidgets('Stack error messages', (WidgetTester tester) async {
await tester.pumpWidget(
Stack(),
);
expect(
tester.takeException().toString(),
'No Directionality widget found.\n'
'Stack widgets require a Directionality widget ancestor to resolve the \'alignment\' argument.\n'
'The default value for \'alignment\' is AlignmentDirectional.topStart, which requires a text direction.\n'
'The specific widget that could not find a Directionality ancestor was:\n'
' Stack\n'
'The ownership chain for the affected widget is: "Stack ← [root]"\n'
'Typically, the Directionality widget is introduced by the MaterialApp or WidgetsApp widget at the '
'top of your application widget tree. It determines the ambient reading direction and is used, for '
'example, to determine how to lay out text, how to interpret "start" and "end" values, and to resolve '
'EdgeInsetsDirectional, AlignmentDirectional, and other *Directional objects.\n'
'Instead of providing a Directionality widget, another solution would be passing a non-directional '
'\'alignment\', or an explicit \'textDirection\', to the Stack.'
);
});
}