Repository: flutter
Issue: LayoutBuilder throwing assertion on hot reload, when parent is dynamically determining the children during the build phase
Labels: c: crash,framework,t: hot reload,a: desktop,a: error message,has reproducible steps,P2,team-framework,triaged-framework,found in release: 3.24,found in release: 3.27

### Steps to reproduce

1. create a new project with "flutter create bug"
2. paste the minimal reproducible sample in the main.dart
3. resize the window, so the container doesnt fit the constrains
4. perform a hot reload or hot restart

### Expected results

No assertion is thrown,.

The use of AppChildFitter is to determine, if the child would need the builder to fit the constrains. In the real application, there is also custom ui added, that change the constrains of the child, because they take up space. Because this Widget needs to be used multiple times, build-in solutions like Intrinsics/ LayoutBuilder/ TextRenderer dont work for me. I have also tried the [boxy package](https://pub.dev/packages/boxy) with no succes. I didn't encounter any issues with "normal" building. Only hot reload/ restart seemed to trigger the exception.

### Actual results

`assert(element._inDirtyList);` evaluates to false and an exception is thrown.


### Code sample

<details open><summary>Code sample</summary>

```dart
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';

void main() => runApp(const MyApp());

final ScrollController _scrollController = ScrollController();

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return Directionality(
      textDirection: TextDirection.ltr,
      child: AppChildFitter(
        childFitter: (child) {
          // return Scrollbar(
          //     controller: _scrollController,
          //     child: SingleChildScrollView(
          //       controller: _scrollController,
          //       child: child,
          //     ));
          return LayoutBuilder(builder: (context, constraints) {
            return Scrollbar(
                controller: _scrollController,
                child: SingleChildScrollView(
                  controller: _scrollController,
                  child: child,
                ));
          });
        },
        child: const Test1(),
      ),
    );
  }
}

class Test1 extends StatefulWidget {
  const Test1({
    super.key,
  });

  @override
  State<Test1> createState() => _Test1State();
}

class _Test1State extends State<Test1> {
  Color color = const Color.fromARGB(255, 134, 26, 116);
  @override
  Widget build(BuildContext context) {
    return Align(
      child: Container(
        width: 200,
        height: 200,
        color: color,
        child: Align(
          child: ElevatedButton(
            onPressed: changeColor,
            child: const Text('Change color'),
          ),
        ),
      ),
    );
  }

  void changeColor() {
    setState(() {
      color = color == const Color.fromARGB(255, 134, 26, 116)
          ? const Color.fromARGB(255, 61, 80, 252)
          : const Color.fromARGB(255, 134, 26, 116);
    });
  }
}

/// A Widget with a custom Element and RenderObject, that builds the [child] with the supplied [childFitter]
/// The [Element] relies internally on [GlobalKey]s, so that the subtree can be cached and the state of the child and fitter can be preserved over a frame
/// The [RenderObject] checks if the child fits the constraints, and if not, applies the fitter and re-layouts
/// Using this widget nested or with intrinsic widgets (such as [IntrinsicWidth]) can result in a long build time, as the build time goes up exponentially with amount of intrinsics/ AppChildFitter in the tree
/// Also some Widgets might cause exceptions, when their underlying renderbox does not support getMinIntrinsicHeight, such as [LayoutBuilder] or [GridView]/[ListView]
class AppChildFitter extends RenderObjectWidget {
  final Widget child;
  final Widget Function(Widget child) childFitter;
  const AppChildFitter({
    super.key,
    required this.child,
    required this.childFitter,
  });

  @override
  RenderObject createRenderObject(BuildContext context) =>
      AppChildFitterRenderObject();

  @override
  RenderObjectElement createElement() => AppChildFitterElement(this);
}

class AppChildFitterElement extends RenderObjectElement {
  AppChildFitterElement(AppChildFitter super.widget);

  late final GlobalKey childKey;
  late final GlobalKey childFitterKey;

  @override
  AppChildFitterRenderObject get renderObject =>
      super.renderObject as AppChildFitterRenderObject;

  @override
  void mount(Element? parent, Object? newSlot) {
    super.mount(parent, newSlot);
    // Link this element to the render box
    renderObject.element = this;
    childKey = GlobalKey();
    childFitterKey = GlobalKey();
    debugPrint("childKey: $childKey");
    debugPrint("childFitterKey: $childFitterKey");
  }

  @override
  void update(RenderObjectWidget newWidget) {
    renderObject.markNeedsLayout();
    super.update(newWidget);
  }

  @override
  void unmount() {
    // Clean up when unmounting
    renderObject.element = null;
    super.unmount();
  }

  Element? _currentChildElement;
  set currentChildElement(Element? value) {
    if (_currentChildElement == value) {
      return;
    }
    _currentChildElement = value;
  }

  Element? get currentChildElement => _currentChildElement;

  @override
  void visitChildren(ElementVisitor visitor) {
    if (_currentChildElement != null) {
      visitor(_currentChildElement!);
    }
  }

  @override
  void forgetChild(Element child) {
    if (child == _currentChildElement) {
      _currentChildElement = null;
    } else {
      assert(false,
          "Forget child called with child that is not a child. At $this");
    }
    super.forgetChild(child);
  }

  RenderBox? generateWidgetsDuringLayout({bool useFittedChild = false}) {
    //TODO: Sometimes error on hot reload when child changes params
    Widget newChildWidget = KeyedSubtree(
      key: childKey,
      child: (widget as AppChildFitter).child,
    );

    // Use the childFitter if required
    if (useFittedChild) {
      //TODO: assert, that old child is in subtree
      try {
        //forgetChild(currentChildElement!);
        newChildWidget = KeyedSubtree(
          key: childFitterKey,
          child: (widget as AppChildFitter).childFitter(newChildWidget),
        );
      } catch (e) {
        FlutterError.reportError(
          FlutterErrorDetails(exception: e, stack: StackTrace.current),
        );
        rethrow;
      }
    }
    owner!.buildScope(this, () {
      // Dynamically generate or update child widgets
      currentChildElement =
          updateChild(currentChildElement, newChildWidget, null)!;
    });

    // return the render object of the child
    //TODO: add checks
    return currentChildElement!.renderObject as RenderBox?;
  }

  @override
  void insertRenderObjectChild(
      covariant RenderObject child, covariant Object? slot) {
    renderObject.box = child as RenderBox;
  }

  @override
  void moveRenderObjectChild(covariant RenderObject child,
      covariant Object? oldSlot, covariant Object? newSlot) {
    renderObject.box = child as RenderBox;
  }

  @override
  void removeRenderObjectChild(
      covariant RenderObject child, covariant Object? slot) {
    if (renderObject.box == child) {
      renderObject.box = null;
    }
  }
}

class AppChildFitterRenderObject extends RenderBox {
  AppChildFitterElement? element;

  RenderBox? _box;
  RenderBox? get box => _box;
  set box(RenderBox? value) {
    if (_box == value) return;

    if (_box != null) {
      dropChild(_box!);
    }
    _box = value;
    if (_box != null) {
      adoptChild(_box!);
    }
  }

  @override
  void performLayout() {
    invokeLayoutCallback<BoxConstraints>((constraints) {
      if (element == null) {
        size = constraints.smallest;
        return;
      }
      // First, try to lay out the child normally
      box = element!.generateWidgetsDuringLayout();

      if (box == null) {
        size = constraints.smallest;
        return;
      }
      // Test if the child will fit the height of constraints
      final minIntrinsicHeight =
          box!.getMinIntrinsicHeight(constraints.maxWidth);
      if (minIntrinsicHeight <= constraints.maxHeight) {
        // Child fits, so lay it out normally
        box!.layout(constraints, parentUsesSize: true);
      } else {
        // Child doesn't fit, apply childFitter and re-layout
        box = element!.generateWidgetsDuringLayout(useFittedChild: true);
        box!.layout(constraints, parentUsesSize: true);
        assert(box!.size.width <= constraints.maxWidth,
            "Width overflow in AppChildFitter");
      }
      size = constraints.constrain(box!.size);
    });
  }

  @override
  void paint(PaintingContext context, Offset offset) {
    // Ensure the box (child) is not null before painting
    if (box == null) return;

    context.paintChild(box!, offset);
  }

  @override
  void visitChildren(RenderObjectVisitor visitor) {
    if (box == null) return;

    visitor(box!);
  }

  @override
  bool hitTestChildren(BoxHitTestResult result, {required Offset position}) {
    if (box == null) return false;

    return box!.hitTest(result, position: position);
  }

  @override
  void attach(PipelineOwner owner) {
    super.attach(owner);
    if (_box != null) {
      _box!.attach(owner);
    }
  }

  @override
  void detach() {
    super.detach();
    if (_box != null) {
      _box!.detach();
    }
  }

  @override
  void redepthChildren() {
    if (_box != null) {
      redepthChild(_box!);
    }
    super.redepthChildren();
  }
}

```

</details>


### Screenshots or Video

_No response_

### Logs

<details open><summary>Logs</summary>

```console
Launching lib/main.dart on Linux in debug mode...
✓ Built build/linux/x64/debug/bundle/bug

Connecting to VM Service at ws://127.0.0.1:37633/JCFZASEUYn8=/ws
Connected to the VM Service.
flutter: childKey: [GlobalKey#562e4]
flutter: childFitterKey: [GlobalKey#65ea6]

════════ Exception caught by rendering library ═════════════════════════════════
The following assertion was thrown during performLayout():
'package:flutter/src/widgets/framework.dart': Failed assertion: line 2703 pos 12: 'element._inDirtyList': is not true.

Either the assertion indicates an error in the framework itself, or we should provide substantially more information in this error message to help you determine and fix the underlying cause.
In either case, please report this assertion by filing a bug on GitHub:
  https://github.com/flutter/flutter/issues/new?template=2_bug.yml

The relevant error-causing widget was:
    LayoutBuilder LayoutBuilder:file:///<path-to-repos>/repos/test/bug/lib/main.dart:23:18

When the exception was thrown, this was the stack:
#2      BuildScope._tryRebuild (package:flutter/src/widgets/framework.dart:2703:12)
#3      BuildScope._flushDirtyElements (package:flutter/src/widgets/framework.dart:2783:11)
#4      BuildOwner.buildScope (package:flutter/src/widgets/framework.dart:3090:18)
#5      _LayoutBuilderElement._rebuildWithConstraints (package:flutter/src/widgets/layout_builder.dart:247:12)
#6      RenderObject.invokeLayoutCallback.<anonymous closure> (package:flutter/src/rendering/object.dart:2719:59)
#7      PipelineOwner._enableMutationsToDirtySubtrees (package:flutter/src/rendering/object.dart:1098:15)
#8      RenderObject.invokeLayoutCallback (package:flutter/src/rendering/object.dart:2719:14)
#9      RenderConstrainedLayoutBuilder.rebuildIfNecessary (package:flutter/src/widgets/layout_builder.dart:299:5)
#10     _RenderLayoutBuilder.performLayout (package:flutter/src/widgets/layout_builder.dart:397:5)
#11     RenderObject.layout (package:flutter/src/rendering/object.dart:2608:7)
#12     AppChildFitterRenderObject.performLayout.<anonymous closure> (package:bug/main.dart:254:14)
#13     RenderObject.invokeLayoutCallback.<anonymous closure> (package:flutter/src/rendering/object.dart:2719:59)
#14     PipelineOwner._enableMutationsToDirtySubtrees (package:flutter/src/rendering/object.dart:1098:15)
#15     RenderObject.invokeLayoutCallback (package:flutter/src/rendering/object.dart:2719:14)
#16     AppChildFitterRenderObject.performLayout (package:bug/main.dart:233:5)
#17     RenderObject.layout (package:flutter/src/rendering/object.dart:2608:7)
#18     RenderView.performLayout (package:flutter/src/rendering/view.dart:281:14)
#19     RenderObject._layoutWithoutResize (package:flutter/src/rendering/object.dart:2446:7)
#20     PipelineOwner.flushLayout (package:flutter/src/rendering/object.dart:1052:18)
#21     PipelineOwner.flushLayout (package:flutter/src/rendering/object.dart:1065:15)
#22     RendererBinding.drawFrame (package:flutter/src/rendering/binding.dart:602:23)
#23     WidgetsBinding.drawFrame (package:flutter/src/widgets/binding.dart:1164:13)
#24     RendererBinding._handlePersistentFrameCallback (package:flutter/src/rendering/binding.dart:468:5)
#25     SchedulerBinding._invokeFrameCallback (package:flutter/src/scheduler/binding.dart:1397:15)
#26     SchedulerBinding.handleDrawFrame (package:flutter/src/scheduler/binding.dart:1318:9)
#27     SchedulerBinding.scheduleWarmUpFrame.<anonymous closure> (package:flutter/src/scheduler/binding.dart:1040:9)
#28     PlatformDispatcher.scheduleWarmUpFrame.<anonymous closure> (dart:ui/platform_dispatcher.dart:837:16)
#32     _RawReceivePort._handleMessage (dart:isolate-patch/isolate_patch.dart:184:12)
(elided 5 frames from class _AssertionError, class _Timer, and dart:async-patch)

The following RenderObject was being processed when the exception was fired: _RenderLayoutBuilder#096db NEEDS-LAYOUT NEEDS-PAINT NEEDS-COMPOSITING-BITS-UPDATE
    needs compositing
    parentData: offset=Offset(0.0, 0.0) (can use size)
    constraints: BoxConstraints(w=1280.0, h=138.0)
    size: Size(1280.0, 138.0)
    child: RenderRepaintBoundary#48338 NEEDS-LAYOUT NEEDS-PAINT NEEDS-COMPOSITING-BITS-UPDATE
        needs compositing
        parentData: offset=Offset(0.0, 0.0) (can use size)
        constraints: BoxConstraints(w=1280.0, h=138.0)
        layer: OffsetLayer#b90bb
            engine layer: OffsetEngineLayer#a8d40
            handles: 2
            offset: Offset(0.0, 0.0)
        size: Size(1280.0, 138.0)
        metrics: 91.7% useful (1 bad vs 11 good)
        diagnosis: this is an outstandingly useful repaint boundary and should definitely be kept
        child: RenderPointerListener#8c8fb NEEDS-LAYOUT NEEDS-PAINT NEEDS-COMPOSITING-BITS-UPDATE
            needs compositing
            parentData: <none> (can use size)
            constraints: BoxConstraints(w=1280.0, h=138.0)
            size: Size(1280.0, 138.0)
            behavior: deferToChild
            listeners: signal
            child: RenderSemanticsGestureHandler#5729c NEEDS-LAYOUT NEEDS-PAINT NEEDS-COMPOSITING-BITS-UPDATE
                needs compositing
                parentData: <none> (can use size)
                constraints: BoxConstraints(w=1280.0, h=138.0)
                size: Size(1280.0, 138.0)
                behavior: deferToChild
                gestures: <none>
                child: RenderPointerListener#97316 NEEDS-LAYOUT NEEDS-PAINT NEEDS-COMPOSITING-BITS-UPDATE
                    needs compositing
                    parentData: <none> (can use size)
                    constraints: BoxConstraints(w=1280.0, h=138.0)
                    size: Size(1280.0, 138.0)
                    behavior: deferToChild
                    listeners: down, panZoomStart
RenderObject: _RenderLayoutBuilder#096db NEEDS-LAYOUT NEEDS-PAINT NEEDS-COMPOSITING-BITS-UPDATE
    needs compositing
    parentData: offset=Offset(0.0, 0.0) (can use size)
    constraints: BoxConstraints(w=1280.0, h=138.0)
    size: Size(1280.0, 138.0)
    child: RenderRepaintBoundary#48338 NEEDS-LAYOUT NEEDS-PAINT NEEDS-COMPOSITING-BITS-UPDATE
        needs compositing
        parentData: offset=Offset(0.0, 0.0) (can use size)
        constraints: BoxConstraints(w=1280.0, h=138.0)
        layer: OffsetLayer#b90bb
            engine layer: OffsetEngineLayer#a8d40
            handles: 2
            offset: Offset(0.0, 0.0)
        size: Size(1280.0, 138.0)
        metrics: 91.7% useful (1 bad vs 11 good)
        diagnosis: this is an outstandingly useful repaint boundary and should definitely be kept
        child: RenderPointerListener#8c8fb NEEDS-LAYOUT NEEDS-PAINT NEEDS-COMPOSITING-BITS-UPDATE
            needs compositing
            parentData: <none> (can use size)
            constraints: BoxConstraints(w=1280.0, h=138.0)
            size: Size(1280.0, 138.0)
            behavior: deferToChild
            listeners: signal
            child: RenderSemanticsGestureHandler#5729c NEEDS-LAYOUT NEEDS-PAINT NEEDS-COMPOSITING-BITS-UPDATE
                needs compositing
                parentData: <none> (can use size)
                constraints: BoxConstraints(w=1280.0, h=138.0)
                size: Size(1280.0, 138.0)
                behavior: deferToChild
                gestures: <none>
                child: RenderPointerListener#97316 NEEDS-LAYOUT NEEDS-PAINT NEEDS-COMPOSITING-BITS-UPDATE
                    needs compositing
                    parentData: <none> (can use size)
                    constraints: BoxConstraints(w=1280.0, h=138.0)
                    size: Size(1280.0, 138.0)
                    behavior: deferToChild
                    listeners: down, panZoomStart
════════════════════════════════════════════════════════════════════════════════
Reloaded 1 of 709 libraries in 191ms (compile: 11 ms, reload: 73 ms, reassemble: 84 ms).

```

</details>


### Flutter Doctor output

<details open><summary>Doctor output</summary>

```console
[✓] Flutter (Channel stable, 3.24.3, on Fedora Linux 40 (Workstation Edition) 6.10.12-200.fc40.x86_64, locale en_US.UTF-8)
    • Flutter version 3.24.3 on channel stable at <path-to-flutter>/flutter/flutter
    • Upstream repository https://github.com/flutter/flutter.git
    • Framework revision 2663184aa7 (5 weeks ago), 2024-09-11 16:27:48 -0500
    • Engine revision 36335019a8
    • Dart version 3.5.3
    • DevTools version 2.37.3

[✗] Android toolchain - develop for Android devices
    ✗ Unable to locate Android SDK.
      Install Android Studio from: https://developer.android.com/studio/index.html
      On first launch it will assist you in installing the Android SDK components.
      (or visit https://flutter.dev/to/linux-android-setup for detailed instructions).
      If the Android SDK has been installed to a custom location, please use
      `flutter config --android-sdk` to update to that location.


[✗] Chrome - develop for the web (Cannot find Chrome executable at google-chrome)
    ! Cannot find Chrome. Try setting CHROME_EXECUTABLE to a Chrome executable.

[✓] Linux toolchain - develop for Linux desktop
    • clang version 18.1.8 (Fedora 18.1.8-1.fc40)
    • cmake version 3.28.2
    • ninja version 1.12.1
    • pkg-config version 2.1.1

[!] Android Studio (not installed)
    • Android Studio not found; download from https://developer.android.com/studio/index.html
      (or visit https://flutter.dev/to/linux-android-setup for detailed instructions).

[✓] VS Code (version 1.94.1)
    • VS Code at /usr/share/code
    • Flutter extension version 3.98.0

[✓] Connected device (1 available)
    • Linux (desktop) • linux • linux-x64 • Fedora Linux 40 (Workstation Edition) 6.10.12-200.fc40.x86_64

[✓] Network resources
    • All expected network resources are available.

! Doctor found issues in 3 categories.
```

</details>