MouseTracker no longer attaches annotations
Summary
#Removed MouseTracker's methods attachAnnotation, detachAnnotation, and isAnnotationAttached.
Context
#Mouse events, such as when a mouse pointer has entered a region, exited, or is hovering over a region, are detected with the help of MouseTrackerAnnotations that are placed on interested regions during the render phase. Upon each update (a new frame or a new event), MouseTracker compares the annotations hovered by the mouse pointer before and after the update, then dispatches callbacks accordingly.
The MouseTracker class, which manages the state of mouse pointers, used to require MouseRegion to attach annotations when mounted, and detach annotations when unmounted. This was used by MouseTracker to perform the mounted-exit check (for example, MouseRegion.onExit must not be called if the exit was caused by the unmounting of the widget), in order to prevent calling setState of an unmounted widget and throwing exceptions (explained in detail in Issue #44631).
This mechanism has been replaced by making MouseRegion a stateful widget, so that it can perform the mounted-exit check by itself by blocking the callback when unmounted. Therefore, these methods have been removed, and MouseTracker no longer tracks all annotations on the screen.
Description of change
#The MouseTracker class has removed three methods related to attaching annotations:
class MouseTracker extends ChangeNotifier {
// ...
void attachAnnotation(MouseTrackerAnnotation annotation) {/* ... */}
void detachAnnotation(MouseTrackerAnnotation annotation) {/* ... */}
@visibleForTesting
bool isAnnotationAttached(MouseTrackerAnnotation annotation) {/* ... */}
}RenderMouseRegion and MouseTrackerAnnotation no longer perform the mounted-exit check, while MouseRegion still does.
Migration guide
#Calls to MouseTracker.attachAnnotation and detachAnnotation should be removed with little to no impact:
- Uses of
MouseRegionshould not be affected at all. - If your code directly uses
RenderMouseRegionorMouseTrackerAnnotation, be aware thatonExitis now called when the exit is caused by events that used to callMouseTracker.detachAnnotation. This should not be a problem if no states are involved, otherwise you might want to add the mounted-exit check, especially if the callback is leaked so that outer widgets might callsetStatein it. For example:
Code before migration:
class MyMouseRegion extends SingleChildRenderObjectWidget {
const MyMouseRegion({this.onHoverChange});
final ValueChanged<bool> onHoverChange;
@override
RenderMouseRegion createRenderObject(BuildContext context) {
return RenderMouseRegion(
onEnter: (_) { onHoverChange(true); },
onExit: (_) { onHoverChange(false); },
);
}
@override
void updateRenderObject(BuildContext context, RenderMouseRegion renderObject) {
renderObject
..onEnter = (_) { onHoverChange(true); }
..onExit = (_) { onHoverChange(false); };
}
}Code after migration:
class MyMouseRegion extends SingleChildRenderObjectWidget {
const MyMouseRegion({this.onHoverChange});
final ValueChanged<bool> onHoverChange;
@override
RenderMouseRegion createRenderObject(BuildContext context) {
return RenderMouseRegion(
onEnter: (_) { onHoverChange(true); },
onExit: (_) { onHoverChange(false); },
);
}
@override
void updateRenderObject(BuildContext context, RenderMouseRegion renderObject) {
renderObject
..onEnter = (_) { onHoverChange(true); }
..onExit = (_) { onHoverChange(false); };
}
@override
void didUnmountRenderObject(RenderMouseRegion renderObject) {
renderObject
..onExit = onHoverChange == null ? null : (_) {};
}
}Calls to MouseTracker.isAnnotationAttached must be removed. This feature is no longer technically possible, since annotations are no longer tracked. If you somehow need this feature, please submit an issue.
Timeline
#Landed in version: 1.15.4
In stable release: 1.17
References
#API documentation:
Relevant PRs:
- MouseTracker no longer requires annotations attached, which made the change
- Improve MouseTracker lifecycle: Move checks to post-frame, which first introduced the mounted-exit change, explained at The change to onExit.
Unless stated otherwise, the documentation on this site reflects the latest stable version of Flutter. Page last updated on 2025-10-10. View source or report an issue.