Skip to content

DpadFocusable with region: leaks ghost nodes when used inside ListView/GridView #6

@mimranfaruqi

Description

@mimranfaruqi

When DpadFocusable widgets with a region: are used inside a lazy ListView or GridView, off-screen items get recycled by Flutter and their elements are deactivated then disposed. At that point _unregisterFromRegionManager() (called from dispose()) fails silently because the element is already detached from the tree — DpadNavigator.regionManagerOf(context) can't resolve and the existing catch (_) swallows the error. The focus node is never removed from the region registry.

These ghost nodes accumulate as the user scrolls. When directional navigation later iterates candidates and calls .rect on them, this throws:

  1. Use DpadFocusable(region: 'content', ...) inside a GridView with enough items to scroll (e.g. 50+ items)
  2. Scroll down past the first screen of items so Flutter recycles early widgets
  3. Navigate with D-pad — the exception above is thrown

What Worked For Us

Moving _unregisterFromRegionManager() from dispose() to an overridden deactivate() method. deactivate() is called before the element is removed from the widget tree, so context is still valid and the lookup succeeds.

// lib/src/focus/dpad_focusable.dart

@override
void deactivate() {
  _unregisterFromRegionManager();
  super.deactivate();
}

@override
void dispose() {
  // unregister is now handled in deactivate() above
  _focusNode.removeListener(_onFocusChange);
  // ... rest unchanged

This completely eliminates the ghost nodes. No other changes were needed — region_navigation.dart does not need any guards.

Environment:

  • dpad: 2.0.2
  • Flutter 3.x, Android TV (API 36 emulator)
  • Large grid of media items (~500 items)

Thank you for reading this through, please let me know if this is really a problem or am I mistaken? if this is really a problem, would it be okay to craete the PR or are you going to fix this with next version release?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions