PortalTarget class Null safety
A widget that renders its follower in a different location of the widget tree.
Its child is rendered in the tree as you would expect, but its portalFollower is rendered through the ancestor Portal in a different location of the widget tree.
In short, you can use PortalTarget to show dialogs, tooltips, contextual
menus, etc.
You can then control the visibility of these overlays with a simple
setState
.
The benefits of using PortalTarget/PortalFollower
over
Overlay/OverlayEntry are multiple:
- PortalTarget is easier to manipulate
- It allows aligning your menus/tooltips next to a button easily
- It combines nicely with state-management solutions and the "state-restoration" framework. For example, combined with RestorableProperty when the application is killed then re-opened, modals/menus would be restored.
For PortalTarget to work, make sure to insert Portal higher in the widget tree.
Contextual menu example
In this example, we will see how we can use PortalTarget to show a menu after clicking on a ElevatedButton.
First, we need to create a StatefulWidget that renders our ElevatedButton:
class MenuExample extends StatefulWidget {
@override
_MenuExampleState createState() => _MenuExampleState();
}
class _MenuExampleState extends State<MenuExample> {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: ElevatedButton(
onPressed: () {},
child: Text('show menu'),
),
),
);
}
}
Then, we need to insert our PortalTarget in the widget tree.
We want our contextual menu to render right next to our ElevatedButton. As such, our PortalTarget should be the parent of ElevatedButton like so:
Center(
child: PortalTarget(
visible: // <todo>
portalFollower: // <todo>
child: ElevatedButton(
...
),
),
)
We can pass our menu as the portalFollower
to PortalTarget:
PortalTarget(
visible: true,
portalFollower: Material(
elevation: 8,
child: IntrinsicWidth(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
ListTile(title: Text('option 1')),
ListTile(title: Text('option 2')),
],
),
),
),
child: ElevatedButton(...),
)
At this stage, you may notice two things:
- our menu is full-screen
- our menu is always visible (because
visible
is true)
Let's fix the full-screen issue first and change our code so that our menu renders on the right of our ElevatedButton.
To align our menu around our button, we can specify the anchor
parameter:
PortalEntry(
visible: true,
anchor: const Aligned(
follower: Alignment.topLeft,
target: Alignment.topRight,
),
portalFollower: Material(...),
child: ElevatedButton(...),
)
What this code means is, this will align the top-left of our menu with the top-right or the ElevatedButton. With this, our menu is no longer full-screen and is now located to the right of our button.
Finally, we can update our code such that the menu show only when clicking on the button.
To do that, we need to declare a new boolean inside our StatefulWidget, that says whether the menu is open or not:
class _MenuExampleState extends State<MenuExample> {
bool isMenuOpen = false;
...
}
We then pass this isMenuOpen
variable to our PortalEntry
:
PortalTarget(
visible: isMenuOpen,
...
)
Then, inside the onPressed
callback of our ElevatedButton, we can
update this isMenuOpen
variable:
ElevatedButton(
onPressed: () {
setState(() {
isMenuOpen = true;
});
},
child: Text('show menu'),
),
One final step is to close the menu when the user clicks randomly outside of the menu.
This can be implemented with a second PortalTarget combined with GestureDetector like so:
Center(
child: PortalTarget(
visible: isMenuOpen,
portalFollower: GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () {
setState(() {
isMenuOpen = false;
});
},
),
child: PortalTarget(
// our previous PortalTarget
portalFollower: Material(...)
child: ElevatedButton(...),
),
),
)
- Inheritance
-
- Object
- DiagnosticableTree
- Widget
- StatefulWidget
- PortalTarget
Constructors
-
PortalTarget({Key? key, bool visible = true, Anchor anchor = const Filled(), Duration? closeDuration, PortalFollower? portalFollower, List<
PortalLabel> portalCandidateLabels = const [PortalLabel.main], String? debugName, required Widget child}) -
const
Properties
- anchor → Anchor
-
final
- child → Widget
-
final
- closeDuration → Duration?
-
final
- debugName → String?
-
final
- hashCode → int
-
The hash code for this object.
read-onlyinherited
- key → Key?
-
Controls how one widget replaces another widget in the tree.
finalinherited
-
portalCandidateLabels
→ List<
PortalLabel> -
final
- portalFollower → PortalFollower?
-
final
- runtimeType → Type
-
A representation of the runtime type of the object.
read-onlyinherited
- visible → bool
-
final
Methods
-
createElement(
) → StatefulElement -
Creates a StatefulElement to manage this widget's location in the tree.
inherited
-
createState(
) → _PortalTargetState -
Creates the mutable state for this widget at a given location in the tree.
override
-
debugDescribeChildren(
) → List< DiagnosticsNode> -
Returns a list of
DiagnosticsNode
objects describing this node's children.inherited -
debugFillProperties(
DiagnosticPropertiesBuilder properties) → void -
Add additional properties associated with the node.
override
-
noSuchMethod(
Invocation invocation) → dynamic -
Invoked when a non-existent method or property is accessed.
inherited
-
toDiagnosticsNode(
{String? name, DiagnosticsTreeStyle? style}) → DiagnosticsNode -
Returns a debug representation of the object that is used by debugging
tools and by DiagnosticsNode.toStringDeep.
inherited
-
toString(
{DiagnosticLevel minLevel = DiagnosticLevel.info}) → String -
A string representation of this object.
inherited
-
toStringDeep(
{String prefixLineOne = '', String? prefixOtherLines, DiagnosticLevel minLevel = DiagnosticLevel.debug}) → String -
Returns a string representation of this node and its descendants.
inherited
-
toStringShallow(
{String joiner = ', ', DiagnosticLevel minLevel = DiagnosticLevel.debug}) → String -
Returns a one-line detailed description of the object.
inherited
-
toStringShort(
) → String -
A short, textual description of this widget.
inherited
Operators
-
operator ==(
Object other) → bool -
The equality operator.
inherited
Static Methods
-
debugResolvePortal(
BuildContext context, List< PortalLabel> portalCandidateLabels) → String? - See which Portal will indeed be used given the configuration Visible only for debugging purpose.