material_table_view icon indicating copy to clipboard operation
material_table_view copied to clipboard

Tooltip unusable when row reordering is enabled

Open wisamidris77 opened this issue 3 months ago • 2 comments

Let's say I added a button with tooltip inside the row it throws this exception:

Click to expand the exception
════════ Exception caught by rendering library ═════════════════════════════════
The following assertion was thrown during paint():
'package:material_table_view/src/table_painting_context.dart': Failed assertion: line 84 pos 12: '!child.needsCompositing': is not true.

The relevant error-causing widget was:
    TableSection TableSection:file:///C:/Users/???/Desktop/Github/material_table_view2/lib/src/table_view.dart:521:12

When the exception was thrown, this was the stack:
#2      TablePaintingContext.paintChild (package:material_table_view/src/table_painting_context.dart:84:12)
table_painting_context.dart:84
#3      RenderProxyBoxMixin.paint (package:flutter/src/rendering/proxy_box.dart:140:13)
proxy_box.dart:140
#4      RenderTableSection._customPaint (package:material_table_view/src/render_table_section.dart:325:11)
render_table_section.dart:325
#5      RenderTableSection.paint (package:material_table_view/src/render_table_section.dart:255:60)
render_table_section.dart:255
#6      RenderObject._paintWithContext (package:flutter/src/rendering/object.dart:3423:7)
object.dart:3423
#7      PaintingContext._repaintCompositedChild (package:flutter/src/rendering/object.dart:176:11)
object.dart:176
#8      PaintingContext.repaintCompositedChild (package:flutter/src/rendering/object.dart:121:5)
object.dart:121
#9      PipelineOwner.flushPaint (package:flutter/src/rendering/object.dart:1309:31)
object.dart:1309
#10     PipelineOwner.flushPaint (package:flutter/src/rendering/object.dart:1319:15)
object.dart:1319
#11     RendererBinding.drawFrame (package:flutter/src/rendering/binding.dart:631:23)
binding.dart:631
#12     WidgetsBinding.drawFrame (package:flutter/src/widgets/binding.dart:1261:13)
binding.dart:1261
#13     RendererBinding._handlePersistentFrameCallback (package:flutter/src/rendering/binding.dart:495:5)
binding.dart:495
#14     SchedulerBinding._invokeFrameCallback (package:flutter/src/scheduler/binding.dart:1434:15)
binding.dart:1434
#15     SchedulerBinding.handleDrawFrame (package:flutter/src/scheduler/binding.dart:1347:9)
binding.dart:1347
#16     SchedulerBinding._handleDrawFrame (package:flutter/src/scheduler/binding.dart:1200:5)
binding.dart:1200
#17     _invoke (dart:ui/hooks.dart:330:13)
hooks.dart:330
#18     PlatformDispatcher._drawFrame (dart:ui/platform_dispatcher.dart:444:5)
platform_dispatcher.dart:444
#19     _drawFrame (dart:ui/hooks.dart:302:31)
hooks.dart:302
(elided 2 frames from class _AssertionError)

The following RenderObject was being processed when the exception was fired: RenderTableSection#3dd5a relayoutBoundary=up15
    needs compositing
    parentData: <none> (can use size)
    constraints: BoxConstraints(0.0<=w<=1264.0, 0.0<=h<=495.0)
    layer: OffsetLayer#0ac33
        engine layer: OffsetEngineLayer#32223
        handles: 2
        offset: Offset(0.0, 0.0)
    size: Size(1264.0, 495.0)
    child: _RenderTheater#bc61e relayoutBoundary=up16 NEEDS-PAINT
        needs compositing
        parentData: <none> (can use size)
        constraints: BoxConstraints(0.0<=w<=1264.0, 0.0<=h<=495.0)
        size: Size(1264.0, 495.0)
        skipCount: 0
        textDirection: ltr
        onstage 1: _RenderTableViewport#055cb NEEDS-PAINT
            parentData: not positioned; offset=Offset(0.0, 0.0) (can use size)
            constraints: BoxConstraints(w=1264.0, h=495.0)
            size: Size(1264.0, 495.0)
            axisDirection: down
            crossAxisDirection: right
            offset: ScrollPositionWithSingleContext#9c163(offset: 0.0, range: 0.0..85899345395.0, viewport: 495.0, ScrollableState, ClampingScrollPhysics -> RangeMaintainingScrollPhysics, IdleScrollActivity#2334b, ScrollDirection.idle)
            anchor: 0.0
            center child: RenderSliverPadding#25416 relayoutBoundary=up1 NEEDS-PAINT
                parentData: paintOffset=Offset(0.0, 0.0) (can use size)
                constraints: SliverConstraints(AxisDirection.down, GrowthDirection.forward, ScrollDirection.idle, scrollOffset: 0.0, precedingScrollExtent: 0.0, remainingPaintExtent: 495.0, crossAxisExtent: 1264.0, crossAxisDirection: AxisDirection.right, viewportMainAxisExtent: 495.0, remainingCacheExtent: 745.0, cacheOrigin: 0.0)
                geometry: SliverGeometry(scrollExtent: 85899345890.0, paintExtent: 495.0, maxPaintExtent: 85899345890.0, hasVisualOverflow: true, cacheExtent: 745.0)
                    scrollExtent: 85899345890.0
                    paintExtent: 495.0
                    maxPaintExtent: 85899345890.0
                    hasVisualOverflow: true
                    cacheExtent: 745.0
                padding: EdgeInsets(0.0, 0.0, 0.0, 10.0)
                textDirection: ltr
                child: RenderSliverFixedExtentList#38b6b relayoutBoundary=up2 NEEDS-PAINT
                    parentData: paintOffset=Offset(0.0, 0.0) (can use size)
                    constraints: SliverConstraints(AxisDirection.down, GrowthDirection.forward, ScrollDirection.idle, scrollOffset: 0.0, precedingScrollExtent: 0.0, remainingPaintExtent: 495.0, crossAxisExtent: 1264.0, crossAxisDirection: AxisDirection.right, viewportMainAxisExtent: 495.0, remainingCacheExtent: 745.0, cacheOrigin: 0.0)
                    geometry: SliverGeometry(scrollExtent: 85899345880.0, paintExtent: 495.0, maxPaintExtent: 85899345880.0, hasVisualOverflow: true, cacheExtent: 745.0)
                        scrollExtent: 85899345880.0
                        paintExtent: 495.0
                        maxPaintExtent: 85899345880.0
                        hasVisualOverflow: true
                        cacheExtent: 745.0
                    currently live children: 0 to 18
        onstage 1 - 1: _RenderDeferredLayoutBox#d58c8 NEEDS-PAINT
            needs compositing
            parentData: not positioned; offset=Offset(0.0, 0.0)
            constraints: BoxConstraints(w=1264.0, h=495.0)
            size: Size(1264.0, 495.0)
            child: RenderCustomSingleChildLayoutBox#23783 NEEDS-PAINT
                needs compositing
                parentData: top=0.0; right=0.0; bottom=0.0; left=0.0; offset=Offset(0.0, 0.0) (can use size)
                constraints: BoxConstraints(w=1264.0, h=495.0)
                size: Size(1264.0, 495.0)
                child: RenderIgnorePointer#b2d83 relayoutBoundary=up1 NEEDS-PAINT
                    needs compositing
                    parentData: offset=Offset(1173.4, 284.0) (can use size)
                    constraints: BoxConstraints(0.0<=w<=1264.0, 0.0<=h<=495.0)
                    size: Size(80.6, 25.0)
                    ignoring: true
                    ignoringSemantics: null
        no offstage children
RenderObject: RenderTableSection#3dd5a relayoutBoundary=up15
    needs compositing
    parentData: <none> (can use size)
    constraints: BoxConstraints(0.0<=w<=1264.0, 0.0<=h<=495.0)
    layer: OffsetLayer#0ac33
        engine layer: OffsetEngineLayer#32223
        handles: 2
        offset: Offset(0.0, 0.0)
    size: Size(1264.0, 495.0)
    child: _RenderTheater#bc61e relayoutBoundary=up16 NEEDS-PAINT
        needs compositing
        parentData: <none> (can use size)
        constraints: BoxConstraints(0.0<=w<=1264.0, 0.0<=h<=495.0)
        size: Size(1264.0, 495.0)
        skipCount: 0
        textDirection: ltr
        onstage 1: _RenderTableViewport#055cb NEEDS-PAINT
            parentData: not positioned; offset=Offset(0.0, 0.0) (can use size)
            constraints: BoxConstraints(w=1264.0, h=495.0)
            size: Size(1264.0, 495.0)
            axisDirection: down
            crossAxisDirection: right
            offset: ScrollPositionWithSingleContext#9c163(offset: 0.0, range: 0.0..85899345395.0, viewport: 495.0, ScrollableState, ClampingScrollPhysics -> RangeMaintainingScrollPhysics, IdleScrollActivity#2334b, ScrollDirection.idle)
            anchor: 0.0
            center child: RenderSliverPadding#25416 relayoutBoundary=up1 NEEDS-PAINT
                parentData: paintOffset=Offset(0.0, 0.0) (can use size)
                constraints: SliverConstraints(AxisDirection.down, GrowthDirection.forward, ScrollDirection.idle, scrollOffset: 0.0, precedingScrollExtent: 0.0, remainingPaintExtent: 495.0, crossAxisExtent: 1264.0, crossAxisDirection: AxisDirection.right, viewportMainAxisExtent: 495.0, remainingCacheExtent: 745.0, cacheOrigin: 0.0)
                geometry: SliverGeometry(scrollExtent: 85899345890.0, paintExtent: 495.0, maxPaintExtent: 85899345890.0, hasVisualOverflow: true, cacheExtent: 745.0)
                    scrollExtent: 85899345890.0
                    paintExtent: 495.0
                    maxPaintExtent: 85899345890.0
                    hasVisualOverflow: true
                    cacheExtent: 745.0
                padding: EdgeInsets(0.0, 0.0, 0.0, 10.0)
                textDirection: ltr
                child: RenderSliverFixedExtentList#38b6b relayoutBoundary=up2 NEEDS-PAINT
                    parentData: paintOffset=Offset(0.0, 0.0) (can use size)
                    constraints: SliverConstraints(AxisDirection.down, GrowthDirection.forward, ScrollDirection.idle, scrollOffset: 0.0, precedingScrollExtent: 0.0, remainingPaintExtent: 495.0, crossAxisExtent: 1264.0, crossAxisDirection: AxisDirection.right, viewportMainAxisExtent: 495.0, remainingCacheExtent: 745.0, cacheOrigin: 0.0)
                    geometry: SliverGeometry(scrollExtent: 85899345880.0, paintExtent: 495.0, maxPaintExtent: 85899345880.0, hasVisualOverflow: true, cacheExtent: 745.0)
                        scrollExtent: 85899345880.0
                        paintExtent: 495.0
                        maxPaintExtent: 85899345880.0
                        hasVisualOverflow: true
                        cacheExtent: 745.0
                    currently live children: 0 to 18
        onstage 1 - 1: _RenderDeferredLayoutBox#d58c8 NEEDS-PAINT
            needs compositing
            parentData: not positioned; offset=Offset(0.0, 0.0)
            constraints: BoxConstraints(w=1264.0, h=495.0)
            size: Size(1264.0, 495.0)
            child: RenderCustomSingleChildLayoutBox#23783 NEEDS-PAINT
                needs compositing
                parentData: top=0.0; right=0.0; bottom=0.0; left=0.0; offset=Offset(0.0, 0.0) (can use size)
                constraints: BoxConstraints(w=1264.0, h=495.0)
                size: Size(1264.0, 495.0)
                child: RenderIgnorePointer#b2d83 relayoutBoundary=up1 NEEDS-PAINT
                    needs compositing
                    parentData: offset=Offset(1173.4, 284.0) (can use size)
                    constraints: BoxConstraints(0.0<=w<=1264.0, 0.0<=h<=495.0)
                    size: Size(80.6, 25.0)
                    ignoring: true
                    ignoringSemantics: null
        no offstage children
════════════════════════════════════════════════════════════════════════════════

Then after all that it gets stuck in exception

_AssertionError ('package:flutter/src/rendering/layer.dart': Failed assertion: line 894 pos 12: 'picture != null': is not true.)

How to do it is editing the main.dart in examples From line 597 in createRowBuilder method Like this


  /// Creates [TableRowBuilder] closure.
  TableRowBuilder createRowBuilder(
    BuildContext context,
    bool doExpansion, [
    int start = 0,
  ]) {
    final theme = Theme.of(context);
    final rowTextStyle = Theme.of(context).textTheme.bodyMedium;

    final cellPadding = stylingController.useRTL.value
        ? const EdgeInsets.only(right: 8.0)
        : const EdgeInsets.only(left: 8.0);

    final cellAlignment = stylingController.useRTL.value
        ? Alignment.centerRight
        : Alignment.centerLeft;

    // this can be freely inlined instead
    return (context, row, TableRowContentBuilder contentBuilder) {
      row += start;

      if (stylingController.doPlaceholders.value &&
          (row + placeholderOffsetIndex) % 99 < 33) {
        return null; // show off the placeholder
      }

      final selected = selection.contains(row);
      final textStyle = selected
          ? rowTextStyle?.copyWith(color: theme.colorScheme.onPrimaryContainer)
          : rowTextStyle;

      var cellBuilder = (BuildContext context, int column) {
        switch (columns[column].index) {
          case 0:
            return Checkbox(
                value: selection.contains(row),
                onChanged: (value) => setState(() => (value ?? false)
                    ? selection.add(row)
                    : selection.remove(row)));
          case -1:
            return Center(
              child: IconButton(
                icon: Icon(
                  Icons.more_vert,
                ),
                tooltip: 'You will not see me!',
                onPressed: () => print('Hello'),
              ),
            );
          default:
            return Padding(
              padding: cellPadding,
              child: Align(
                alignment: cellAlignment,
                child: Text(
                  '${(row + 2) * columns[column].index}',
                  style: textStyle,
                  overflow: TextOverflow.fade,
                  maxLines: 1,
                  softWrap: false,
                ),
              ),
            );
        }
      };

      if (stylingController.statefulRandomBackground.value) {
        final builder = cellBuilder;
        cellBuilder = (context, column) =>
            StatefulRandomBackground(child: builder(context, column));
      }

      // this is going to be our content
      var content = contentBuilder(context, cellBuilder);

      if (doExpansion) {
        // build expandable row
        content = ExpandableTableRow(
          vsync: this,
          duration: Duration(milliseconds: 200),
          expanded: selected,
          expandedChild: SizedBox(
            height: _rowHeight,
            child: contentBuilder(
              context,
              (context, column) {
                switch (columns[column].index) {
                  case 0:
                  case -1:
                    return SizedBox();
                  default:
                    return Padding(
                      padding: cellPadding,
                      child: Align(
                        alignment: cellAlignment,
                        child: Text(
                          '${sqrt((row + 2) * columns[column].index)}',
                          style: textStyle,
                          overflow: TextOverflow.fade,
                          maxLines: 1,
                          softWrap: false,
                        ),
                      ),
                    );
                }
              },
            ),
          ),
          child: SizedBox(
            height: _rowHeight,
            child: content,
          ),
        );
      }

      return _wrapRow(
        row,
        AnimatedContainer(
          duration: const Duration(milliseconds: 200),
          color:
              theme.colorScheme.primaryContainer.withAlpha(selected ? 0xFF : 0),
          child: Material(
            type: MaterialType.transparency,
            child: InkWell(
              onTap: () => setState(() {
                selection.clear();
                selection.add(row);
              }),
              child: content,
            ),
          ),
        ),
      );
    };
  }

wisamidris77 avatar Sep 11 '25 16:09 wisamidris77

Latest news, I found in table_painting_context.dart in method paintChild line 82 The method is doing a assert for assert(!child.needsCompositing); So I changed it from this

  @override
  void paintChild(RenderObject child, Offset offset) {
    assert(!child.isRepaintBoundary);
    assert(!child.needsCompositing);

    super.paintChild(child, offset);
  }

To this

  @override
  void paintChild(RenderObject child, Offset offset) {
    assert(!child.isRepaintBoundary);
    
    if (child.needsCompositing) {
      regular.fixed.paintChild(child, offset);
    } else {
      super.paintChild(child, offset);
    }
  }

I'm sure there could be another issues with removing that assert but now it's working fine.

wisamidris77 avatar Sep 11 '25 16:09 wisamidris77

I see your problem. Whenever row reordering feature is used the library inserts TableSectionOverlay between TableSection and the row widgets. Whenever tooltip is to be shown this overlay rather mistakenly gets used instead of the default one, making it require compositing. As per design described in the README this segment of the tree must remain free of such things.

I'm concerned about the solution you proposed undermining that strict rule. Even though it might work for your case, it would not be ideal for the project and all of its use cases.

I'll see if I can spare some time on investigating other potential solutions. Let me know if I've missed anything.

NikolayNIK avatar Sep 15 '25 09:09 NikolayNIK