Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Sign in
Toggle navigation
F
Front-End
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
abdullh.alsoleman
Front-End
Commits
9dd30878
Unverified
Commit
9dd30878
authored
Dec 09, 2022
by
Michael Goderbauer
Committed by
GitHub
Dec 09, 2022
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add LookupBoundary to Material (#116736)
parent
332032dd
Changes
6
Show whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
291 additions
and
8 deletions
+291
-8
debug.dart
packages/flutter/lib/src/material/debug.dart
+10
-4
material.dart
packages/flutter/lib/src/material/material.dart
+13
-3
lookup_boundary.dart
packages/flutter/lib/src/widgets/lookup_boundary.dart
+47
-0
debug_test.dart
packages/flutter/test/material/debug_test.dart
+2
-1
material_test.dart
packages/flutter/test/material/material_test.dart
+95
-0
lookup_boundary_test.dart
packages/flutter/test/widgets/lookup_boundary_test.dart
+124
-0
No files found.
packages/flutter/lib/src/material/debug.dart
View file @
9dd30878
...
...
@@ -11,7 +11,8 @@ import 'scaffold.dart' show Scaffold, ScaffoldMessenger;
// Examples can assume:
// late BuildContext context;
/// Asserts that the given context has a [Material] ancestor.
/// Asserts that the given context has a [Material] ancestor within the closest
/// [LookupBoundary].
///
/// Used by many Material Design widgets to make sure that they are
/// only used in contexts where they can print ink onto some material.
...
...
@@ -32,12 +33,17 @@ import 'scaffold.dart' show Scaffold, ScaffoldMessenger;
/// Does nothing if asserts are disabled. Always returns true.
bool
debugCheckHasMaterial
(
BuildContext
context
)
{
assert
(()
{
if
(
context
.
widget
is
!
Material
&&
context
.
findAncestorWidgetOfExactType
<
Material
>()
==
null
)
{
if
(
LookupBoundary
.
findAncestorWidgetOfExactType
<
Material
>(
context
)
==
null
)
{
final
bool
hiddenByBoundary
=
LookupBoundary
.
debugIsHidingAncestorWidgetOfExactType
<
Material
>(
context
);
throw
FlutterError
.
fromParts
(<
DiagnosticsNode
>[
ErrorSummary
(
'No Material widget found.'
),
ErrorSummary
(
'No Material widget found
${hiddenByBoundary ? ' within the closest LookupBoundary' : ''}
.'
),
if
(
hiddenByBoundary
)
ErrorDescription
(
'There is an ancestor Material widget, but it is hidden by a LookupBoundary.'
),
ErrorDescription
(
'
${context.widget.runtimeType}
widgets require a Material '
'widget ancestor.
\n
'
'widget ancestor
within the closest LookupBoundary
.
\n
'
'In Material Design, most widgets are conceptually "printed" on '
"a sheet of material. In Flutter's material library, that "
'material is represented by the Material widget. It is the '
...
...
packages/flutter/lib/src/material/material.dart
View file @
9dd30878
...
...
@@ -343,7 +343,7 @@ class Material extends StatefulWidget {
final
BorderRadiusGeometry
?
borderRadius
;
/// The ink controller from the closest instance of this class that
/// encloses the given context.
/// encloses the given context
within the closest [LookupBoundary]
.
///
/// Typical usage is as follows:
///
...
...
@@ -358,11 +358,11 @@ class Material extends StatefulWidget {
/// * [Material.of], which is similar to this method, but asserts if
/// no [Material] ancestor is found.
static
MaterialInkController
?
maybeOf
(
BuildContext
context
)
{
return
context
.
findAncestorRenderObjectOfType
<
_RenderInkFeatures
>(
);
return
LookupBoundary
.
findAncestorRenderObjectOfType
<
_RenderInkFeatures
>(
context
);
}
/// The ink controller from the closest instance of [Material] that encloses
/// the given context.
/// the given context
within the closest [LookupBoundary]
.
///
/// If no [Material] widget ancestor can be found then this method will assert
/// in debug mode, and throw an exception in release mode.
...
...
@@ -383,6 +383,16 @@ class Material extends StatefulWidget {
final
MaterialInkController
?
controller
=
maybeOf
(
context
);
assert
(()
{
if
(
controller
==
null
)
{
if
(
LookupBoundary
.
debugIsHidingAncestorRenderObjectOfType
<
_RenderInkFeatures
>(
context
))
{
throw
FlutterError
(
'Material.of() was called with a context that does not have access to a Material widget.
\n
'
'The context provided to Material.of() does have a Material widget ancestor, but it is '
'hidden by a LookupBoundary. This can happen because you are using a widget that looks '
'for a Material ancestor, but no such ancestor exists within the closest LookupBoundary.
\n
'
'The context used was:
\n
'
'
$context
'
,
);
}
throw
FlutterError
(
'Material.of() was called with a context that does not contain a Material widget.
\n
'
'No Material widget ancestor could be found starting from the context that was passed to '
...
...
packages/flutter/lib/src/widgets/lookup_boundary.dart
View file @
9dd30878
...
...
@@ -250,6 +250,53 @@ class LookupBoundary extends InheritedWidget {
});
}
/// Returns true if a [LookupBoundary] is hiding the nearest
/// [Widget] of the specified type `T` from the provided [BuildContext].
///
/// This method throws when asserts are disabled.
static
bool
debugIsHidingAncestorWidgetOfExactType
<
T
extends
Widget
>(
BuildContext
context
)
{
bool
?
result
;
assert
(()
{
bool
hiddenByBoundary
=
false
;
bool
ancestorFound
=
false
;
context
.
visitAncestorElements
((
Element
ancestor
)
{
if
(
ancestor
.
widget
.
runtimeType
==
T
)
{
ancestorFound
=
true
;
return
false
;
}
hiddenByBoundary
=
hiddenByBoundary
||
ancestor
.
widget
.
runtimeType
==
LookupBoundary
;
return
true
;
});
result
=
ancestorFound
&
hiddenByBoundary
;
return
true
;
}
());
return
result
!;
}
/// Returns true if a [LookupBoundary] is hiding the nearest
/// [RenderObjectWidget] with a [RenderObject] of the specified type `T`
/// from the provided [BuildContext].
///
/// This method throws when asserts are disabled.
static
bool
debugIsHidingAncestorRenderObjectOfType
<
T
extends
RenderObject
>(
BuildContext
context
)
{
bool
?
result
;
assert
(()
{
bool
hiddenByBoundary
=
false
;
bool
ancestorFound
=
false
;
context
.
visitAncestorElements
((
Element
ancestor
)
{
if
(
ancestor
is
RenderObjectElement
&&
ancestor
.
renderObject
is
T
)
{
ancestorFound
=
true
;
return
false
;
}
hiddenByBoundary
=
hiddenByBoundary
||
ancestor
.
widget
.
runtimeType
==
LookupBoundary
;
return
true
;
});
result
=
ancestorFound
&
hiddenByBoundary
;
return
true
;
}
());
return
result
!;
}
@override
bool
updateShouldNotify
(
covariant
InheritedWidget
oldWidget
)
=>
false
;
}
packages/flutter/test/material/debug_test.dart
View file @
9dd30878
...
...
@@ -28,7 +28,8 @@ void main() {
error
.
toStringDeep
(),
'FlutterError
\n
'
' No Material widget found.
\n
'
' Chip widgets require a Material widget ancestor.
\n
'
' Chip widgets require a Material widget ancestor within the
\n
'
' closest LookupBoundary.
\n
'
' In Material Design, most widgets are conceptually "printed" on a
\n
'
" sheet of material. In Flutter's material library, that material
\n
"
' is represented by the Material widget. It is the Material widget
\n
'
...
...
packages/flutter/test/material/material_test.dart
View file @
9dd30878
...
...
@@ -1034,6 +1034,101 @@ void main() {
materialKey
.
currentContext
!.
findRenderObject
()!.
paint
(
PaintingContext
(
ContainerLayer
(),
Rect
.
largest
),
Offset
.
zero
);
expect
(
tracker
.
paintCount
,
2
);
});
group
(
'LookupBoundary'
,
()
{
testWidgets
(
'hides Material from Material.maybeOf'
,
(
WidgetTester
tester
)
async
{
MaterialInkController
?
material
;
await
tester
.
pumpWidget
(
Material
(
child:
LookupBoundary
(
child:
Builder
(
builder:
(
BuildContext
context
)
{
material
=
Material
.
maybeOf
(
context
);
return
Container
();
},
),
),
),
);
expect
(
material
,
isNull
);
});
testWidgets
(
'hides Material from Material.of'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
Material
(
child:
LookupBoundary
(
child:
Builder
(
builder:
(
BuildContext
context
)
{
Material
.
of
(
context
);
return
Container
();
},
),
),
),
);
final
Object
?
exception
=
tester
.
takeException
();
expect
(
exception
,
isFlutterError
);
final
FlutterError
error
=
exception
!
as
FlutterError
;
expect
(
error
.
toStringDeep
(),
'FlutterError
\n
'
' Material.of() was called with a context that does not have access
\n
'
' to a Material widget.
\n
'
' The context provided to Material.of() does have a Material widget
\n
'
' ancestor, but it is hidden by a LookupBoundary. This can happen
\n
'
' because you are using a widget that looks for a Material
\n
'
' ancestor, but no such ancestor exists within the closest
\n
'
' LookupBoundary.
\n
'
' The context used was:
\n
'
' Builder(dirty)
\n
'
);
});
testWidgets
(
'hides Material from debugCheckHasMaterial'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
Material
(
child:
LookupBoundary
(
child:
Builder
(
builder:
(
BuildContext
context
)
{
debugCheckHasMaterial
(
context
);
return
Container
();
},
),
),
),
);
final
Object
?
exception
=
tester
.
takeException
();
expect
(
exception
,
isFlutterError
);
final
FlutterError
error
=
exception
!
as
FlutterError
;
expect
(
error
.
toStringDeep
(),
startsWith
(
'FlutterError
\n
'
' No Material widget found within the closest LookupBoundary.
\n
'
' There is an ancestor Material widget, but it is hidden by a
\n
'
' LookupBoundary.
\n
'
' Builder widgets require a Material widget ancestor within the
\n
'
' closest LookupBoundary.
\n
'
' In Material Design, most widgets are conceptually "printed" on a
\n
'
" sheet of material. In Flutter's material library, that material
\n
"
' is represented by the Material widget. It is the Material widget
\n
'
' that renders ink splashes, for instance. Because of this, many
\n
'
' material library widgets require that there be a Material widget
\n
'
' in the tree above them.
\n
'
' To introduce a Material widget, you can either directly include
\n
'
' one, or use a widget that contains Material itself, such as a
\n
'
' Card, Dialog, Drawer, or Scaffold.
\n
'
' The specific widget that could not find a Material ancestor was:
\n
'
' Builder
\n
'
' The ancestors of this widget were:
\n
'
' LookupBoundary
\n
'
),
);
});
});
}
class
TrackPaintInkFeature
extends
InkFeature
{
...
...
packages/flutter/test/widgets/lookup_boundary_test.dart
View file @
9dd30878
...
...
@@ -958,6 +958,130 @@ void main() {
});
});
group
(
'LookupBoundary.debugIsHidingAncestorWidgetOfExactType'
,
()
{
testWidgets
(
'is hiding'
,
(
WidgetTester
tester
)
async
{
bool
?
isHidden
;
await
tester
.
pumpWidget
(
Container
(
color:
Colors
.
blue
,
child:
LookupBoundary
(
child:
Builder
(
builder:
(
BuildContext
context
)
{
isHidden
=
LookupBoundary
.
debugIsHidingAncestorWidgetOfExactType
<
Container
>(
context
);
return
Container
();
},
),
),
));
expect
(
isHidden
,
isTrue
);
});
testWidgets
(
'is not hiding entity within boundary'
,
(
WidgetTester
tester
)
async
{
bool
?
isHidden
;
await
tester
.
pumpWidget
(
Container
(
color:
Colors
.
blue
,
child:
LookupBoundary
(
child:
Container
(
color:
Colors
.
red
,
child:
Builder
(
builder:
(
BuildContext
context
)
{
isHidden
=
LookupBoundary
.
debugIsHidingAncestorWidgetOfExactType
<
Container
>(
context
);
return
Container
();
},
),
),
),
));
expect
(
isHidden
,
isFalse
);
});
testWidgets
(
'is not hiding if no boundary exists'
,
(
WidgetTester
tester
)
async
{
bool
?
isHidden
;
await
tester
.
pumpWidget
(
Container
(
color:
Colors
.
blue
,
child:
Builder
(
builder:
(
BuildContext
context
)
{
isHidden
=
LookupBoundary
.
debugIsHidingAncestorWidgetOfExactType
<
Container
>(
context
);
return
Container
();
},
),
));
expect
(
isHidden
,
isFalse
);
});
testWidgets
(
'is not hiding if no boundary and no entity exists'
,
(
WidgetTester
tester
)
async
{
bool
?
isHidden
;
await
tester
.
pumpWidget
(
Builder
(
builder:
(
BuildContext
context
)
{
isHidden
=
LookupBoundary
.
debugIsHidingAncestorWidgetOfExactType
<
Container
>(
context
);
return
Container
();
},
));
expect
(
isHidden
,
isFalse
);
});
});
group
(
'LookupBoundary.debugIsHidingAncestorRenderObjectOfType'
,
()
{
testWidgets
(
'is hiding'
,
(
WidgetTester
tester
)
async
{
bool
?
isHidden
;
await
tester
.
pumpWidget
(
Padding
(
padding:
EdgeInsets
.
zero
,
child:
LookupBoundary
(
child:
Builder
(
builder:
(
BuildContext
context
)
{
isHidden
=
LookupBoundary
.
debugIsHidingAncestorRenderObjectOfType
<
RenderPadding
>(
context
);
return
Container
();
},
),
),
));
expect
(
isHidden
,
isTrue
);
});
testWidgets
(
'is not hiding entity within boundary'
,
(
WidgetTester
tester
)
async
{
bool
?
isHidden
;
await
tester
.
pumpWidget
(
Padding
(
padding:
EdgeInsets
.
zero
,
child:
LookupBoundary
(
child:
Padding
(
padding:
EdgeInsets
.
zero
,
child:
Builder
(
builder:
(
BuildContext
context
)
{
isHidden
=
LookupBoundary
.
debugIsHidingAncestorRenderObjectOfType
<
RenderPadding
>(
context
);
return
Container
();
},
),
),
),
));
expect
(
isHidden
,
isFalse
);
});
testWidgets
(
'is not hiding if no boundary exists'
,
(
WidgetTester
tester
)
async
{
bool
?
isHidden
;
await
tester
.
pumpWidget
(
Padding
(
padding:
EdgeInsets
.
zero
,
child:
Builder
(
builder:
(
BuildContext
context
)
{
isHidden
=
LookupBoundary
.
debugIsHidingAncestorRenderObjectOfType
<
RenderPadding
>(
context
);
return
Container
();
},
),
));
expect
(
isHidden
,
isFalse
);
});
testWidgets
(
'is not hiding if no boundary and no entity exists'
,
(
WidgetTester
tester
)
async
{
bool
?
isHidden
;
await
tester
.
pumpWidget
(
Builder
(
builder:
(
BuildContext
context
)
{
isHidden
=
LookupBoundary
.
debugIsHidingAncestorRenderObjectOfType
<
RenderPadding
>(
context
);
return
Container
();
},
));
expect
(
isHidden
,
isFalse
);
});
});
}
class
MyStatefulContainer
extends
StatefulWidget
{
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment