Mobile Development 8 min read

Understanding Flutter's OverlayPortal: Architecture, Usage, and Differences from OverlayEntry

Flutter’s OverlayPortal adds a page‑scoped layer to the global Overlay, letting widgets like tooltips inherit parent state while remaining independent in the overlay hierarchy, and replaces many OverlayEntry use‑cases by using a controller, surrogate render objects, and automatic lifecycle management.

Sohu Tech Products
Sohu Tech Products
Sohu Tech Products
Understanding Flutter's OverlayPortal: Architecture, Usage, and Differences from OverlayEntry

In Flutter, an Overlay can be added under MaterialApp to create global layers such as floating widgets or page guides. Internally, Overlay works like a layer manager that contains a private _Theater . Each route page is displayed by inserting an OverlayEntry into this theater.

The navigation system ( Navigator ) itself uses an Overlay to host route pages, and every opened Route inserts an OverlayEntry into the overlay. All OverlayEntry objects are siblings and do not affect each other, which is why routes are independent.

Because OverlayEntry objects are independent, a new entry added from page A cannot directly share state with page A via InheritedWidget . For example, a Text('Hello') placed in a new OverlayEntry cannot inherit the Theme from the surrounding page.

OverlayPortal solves this limitation. It also adds a layer to the Overlay , but unlike OverlayEntry it can associate its state with the parent page while remaining independent in the layer hierarchy. This makes it suitable for in‑page floating windows, dialogs, and other UI overlays.

According to the Flutter team, OverlayPortal was inspired by the flutter_portal package and was merged into the framework in the flutter/flutter#105335 change.

The following example demonstrates a typical usage:

Define a DefaultTextStyle with a modified fontSize: 20 to be shared with the tooltip.

Add an OverlayPortal bound to an OverlayPortalController for show/hide control.

In overlayChildBuilder , return a positioned widget that displays a random tooltip text.

Provide a normal child button that toggles the tooltip via the controller.

class ClickableTooltipWidgetState extends State
{
  final OverlayPortalController _tooltipController = OverlayPortalController();

  final Random random = Random();

  @override
  Widget build(BuildContext context) {
    return Container(
      height: 50,
      width: 300,
      decoration: BoxDecoration(
          color: Colors.blue, borderRadius: BorderRadius.circular(10)),
      child: TextButton(
        ///点击 OverlayPortalController 实现展示和隐藏
        onPressed: _tooltipController.toggle,
        child: DefaultTextStyle(
          /// 共享了 DefaultTextStyle 的 fontSize: 20 修改
          style: DefaultTextStyle.of(context).style.copyWith(fontSize: 20),
          /// 使用了 OverlayPortal
          child: OverlayPortal(
            controller: _tooltipController,
            /// 通过 overlayChildBuilder 增加图层
            overlayChildBuilder: (BuildContext context) {
              return Positioned(
                right: random.nextInt(200).toDouble(),
                bottom: random.nextInt(500).toDouble(),
                child: const ColoredBox(
                  color: Colors.amberAccent,
                  child: Text('Text Everyone Wants to See'),
                ),
              );
            },
            /// 页面内的 child
            child: const Text('Press to show/hide'),
          ),
        ),
      ),
    );
  }
}

When the button is pressed, the tooltip generated by overlayChildBuilder can appear at any screen location because its layout is not constrained by the surrounding Container ; it is rendered in an independent overlay layer. The tooltip also inherits the style from the surrounding DefaultTextStyle , keeping its appearance in sync with the page.

If the page is closed while the tooltip is visible, the associated OverlayPortal is destroyed automatically, demonstrating its page‑scoped lifecycle.

Internally, OverlayPortal relies on two render objects: _RenderLayoutSurrogateProxyBox (placed in the widget tree) and _RenderDeferredLayoutBox (which performs layout and painting inside the global Overlay via the internal _Theater ). Each OverlayEntry maintains a LinkedList<_OverlayEntryLocation> _sortedTheaterSiblings . When an OverlayPortal is shown, it creates an _OverlayEntryLocation slot that is inserted into this list, and the slot triggers layout updates through _theater._addDeferredChild(child) .

From a layering perspective, an OverlayPortal is typically inserted right after the nearest OverlayEntry (usually the current route) and before the next one, allowing it to exist anywhere in the page without covering the subsequent page. When multiple OverlayPortal instances are attached to the same OverlayEntry , their rendering order follows the order in which OverlayPortalController.show is called.

In summary, OverlayPortal enriches Flutter's UI toolkit by providing a way to create page‑scoped global layers that keep state linked to the parent while remaining independent in the overlay hierarchy. It can replace OverlayEntry in many scenarios, enabling more flexible implementations of tooltips, guides, and even custom transition animations.

DartFlutterUIWidgetOverlaymobile-developmentoverlayportal
Sohu Tech Products
Written by

Sohu Tech Products

A knowledge-sharing platform for Sohu's technology products. As a leading Chinese internet brand with media, video, search, and gaming services and over 700 million users, Sohu continuously drives tech innovation and practice. We’ll share practical insights and tech news here.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.