Skip to content

feature: Support covariance for Control lists (use Sequence/Mapping or Mutable ones) #6574

@Iaw4tch

Description

@Iaw4tch

Duplicate Check

Describe the requested feature

Currently, many Flet containers (like Column, Row, View, etc.) define their controls property strictly as list[Control]. Because Python's list is invariant, this causes significant issues when building custom structured UI architectures.

Suggest a solution

Change the type hinting for controls in constructors from list[Control] to a covariant alternative, like typing.Sequence[Control]. This allows passing lists of subclasses (e.g., list[MyCustomControl]) without breaking type safety or triggering linter warnings (e.g., Pylance(reportArgumentType)

Screenshots

No response

Additional details

I've ran into this problem writing my own Tabs class wrapper that coordinates specific child components.

class InertiaListViewResponsibleTabs(ft.Tabs):
    def __init__(
        self,
        tab_bar: EmphasisTabBar, # My own TabBar wrapper
        view_controls: list[ft.Control],
        ...
    ):
        super().__init__(
            on_change=self._on_change,
            ...,
            content=ft.Column(
                ...,
                controls=[tab_bar, ft.TabBarView(controls=view_controls)],
            ),
        )

The bad workaround I am forced to use:
To bypass this, I have to lower my type safety, accept any Control, and then manually pollute my runtime code with isinstance checks to filter out unexpected controls:

def __init__(
    self,
    ...,
    view_controls: list[ft.Control],
    ...
):
    super().__init__(...)

    # Runtime overhead and boilerplate just because of invariant list hints
    self._inertia_list_view_mask = [isinstance(control, InertiaListView) for control in view_controls]

    self._view_controls = view_controls

def _on_change(self, e: ft.Event[ft.Tabs]):
    if self._inertia_list_view_mask[e.control.selected_index]:
        # Cast is a bad tone
        list_view = cast(
            InertiaListView, self._view_controls[e.control.selected_index]
        )
        list_view.scroll_to(list_view.current_offset)

If ft.TabBarView accepted Sequence[Control], Python's type checkers would naturally allow writing:

def __init__(
    self,
    tab_bar: EmphasisTabBar,
    view_controls: list[InertiaListView],
    ...
):
    super().__init__(
        on_change=self._on_change,
        ...,
        content=ft.Column(
            ...,
            # Assignment without Pylance[reportArgumentType]
            controls=[tab_bar, ft.TabBarView(controls=view_controls)],
        ),
    )

This simplifies _on_change logic:

def _on_change(self, e: ft.Event[ft.Tabs]):
    list_view = self._view_controls[e.control.selected_index]
    list_view.scroll_to(list_view.current_offset)

Metadata

Metadata

Assignees

No one assigned

    Labels

    feature requestSuggestion/Request for additional feature

    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