title: Flutter custom layout, decide widget's size based on its child
body: <p>The <a href="https://flutter.dev/docs/development/ui/layout/constraints" rel="noreferrer">understanding constraints</a> article is very useful in understanding how layout works. However, I'm having trouble creating a custom layout that works the way it is explained there.</p>
  <p>The article says that size flows up, first we obtain the sizes of the children and then we can decide on our own size based on the sizes of the children.</p>
  <p>It seems <a href="https://api.flutter.dev/flutter/widgets/CustomSingleChildLayout-class.html" rel="noreferrer"><code>CustomSingleChildLayout</code></a> is the way to create a custom layout with one child. In it, based on the article I mentioned above, I would like to get my child's size, and then decide on my own size based on that. However, this seems to be impossible. <a href="https://api.flutter.dev/flutter/rendering/SingleChildLayoutDelegate/getSize.html" rel="noreferrer"><code>getSize()</code></a> method is called with the constraints, therefore I'm supposed to decide on my widgets size based on the constraints that I receive from above. I am able to obtain the child's size in <a href="https://api.flutter.dev/flutter/rendering/SingleChildLayoutDelegate/getPositionForChild.html" rel="noreferrer"><code>getPositionForChild()</code></a>, but this called after I give my size in <code>getSize()</code>. It made sense to trigger a relayout at this point, but it seems I'm not allowed to do that from a function called by the layout system.</p>
  <p>So, the question is, what's the best way to implement a custom layout, with the layout behavior of constraints flowing down and sizes flowing up, as explained in <a href="https://flutter.dev/docs/development/ui/layout/constraints" rel="noreferrer">understanding constraints</a>?</p>
  <p><strong>Edit:</strong> Here is some code to explain what I really want.</p>
  <p>This is the look that I'm going for. Here, I'm manually supplying the width and height of the blue square. I don't want to do that. I want the child's size to determine the size of the square. I want the size to flow up from the text and the blue square should decide on an edge length of <code>max(width, height)</code>.</p>
  <p><a href="https://codepen.io/gazialankus/pen/abdKyVR" rel="noreferrer">https://codepen.io/gazialankus/pen/abdKyVR</a></p>
  <pre><code>import 'package:flutter/material.dart';
  
  class AppRoot extends StatelessWidget {
    @override
    Widget build(BuildContext context) {
      return MaterialApp(
        title: 'Flutter Demo', // Appears in app switcher
        theme: ThemeData(
          primaryColor: Colors.grey.shade300,
          visualDensity: VisualDensity.adaptivePlatformDensity,
        ),
        home: MyPage(),
      );
    }
  }
  
  class MyPage extends StatelessWidget {
    @override
    Widget build(BuildContext context) {
      return Scaffold(
        body: Center(
          child: Container(
            color: Colors.yellow,
            child: SizedBox(
              width: 180,
              height: 180,
              child: Container(
                color: Colors.blue,
                child: AspectRatio(
                  aspectRatio: 1,
                  child: Text(
                    &quot;I want to be in a tight square.&quot;,
                    style: TextStyle(
                      backgroundColor: Colors.red
                    ),
                  ),
                ),
              ),
            ),
          ),
        ),
      );
    }
  }
  
  void main() {
    runApp(AppRoot());
  }
  
  </code></pre>
  <p>It was suggested that I could wrap it with <code>Align</code>. Here I tried, but <code>Align</code> grows up. <code>Align</code>'s <code>widthFactor</code> and <code>heightFactor</code> make use of the child's size, but can't make it a square that way. Removing the blue <code>Container</code> has no effect:</p>
  <p><a href="https://codepen.io/gazialankus/pen/MWKXvpO" rel="noreferrer">https://codepen.io/gazialankus/pen/MWKXvpO</a></p>
  <pre><code>import 'package:flutter/material.dart';
  
  class AppRoot extends StatelessWidget {
    @override
    Widget build(BuildContext context) {
      return MaterialApp(
        title: 'Flutter Demo', // Appears in app switcher
        theme: ThemeData(
          primaryColor: Colors.grey.shade300,
          visualDensity: VisualDensity.adaptivePlatformDensity,
        ),
        home: MyPage(),
      );
    }
  }
  
  class MyPage extends StatelessWidget {
    @override
    Widget build(BuildContext context) {
      return Scaffold(
        body: Center(
          child: Container(
            color: Colors.yellow,
            child: Align(
              alignment: Alignment.center,
              child: Container(
                color: Colors.blue,
                child: Text(
                  &quot;I want to be in a square.&quot;,
                  style: TextStyle(
                    backgroundColor: Colors.red
                  ),
                ),
              ),
            ),
          ),
        ),
      );
    }
  }
  
  void main() {
    runApp(AppRoot());
  }
  
  </code></pre>
  <p>Here I'm trying to use <code>AspectRatio</code> to make the square, but it does not care about the child's size and just grows:</p>
  <p><a href="https://codepen.io/gazialankus/pen/KKVevXv" rel="noreferrer">https://codepen.io/gazialankus/pen/KKVevXv</a></p>
  <pre><code>import 'package:flutter/material.dart';
  
  class AppRoot extends StatelessWidget {
    @override
    Widget build(BuildContext context) {
      return MaterialApp(
        title: 'Flutter Demo', // Appears in app switcher
        theme: ThemeData(
          primaryColor: Colors.grey.shade300,
          visualDensity: VisualDensity.adaptivePlatformDensity,
        ),
        home: MyPage(),
      );
    }
  }
  
  class MyPage extends StatelessWidget {
    @override
    Widget build(BuildContext context) {
      return Scaffold(
        body: Center(
          child: Container(
            color: Colors.yellow,
            child: Align(
              alignment: Alignment.center,
              child: Container(
                color: Colors.blue,
                child: AspectRatio(
                  aspectRatio: 1,
                  child: Text(
                    &quot;I want to be in a tight square.&quot;,
                    style: TextStyle(
                      backgroundColor: Colors.red
                    ),
                  ),
                ),
              ),
            ),
          ),
        ),
      );
    }
  }
  
  void main() {
    runApp(AppRoot());
  }
  
  </code></pre>
  <p>And here I'm trying to do a custom layout, but I don't get to use the child's size to decide on my size. I have to decide on my size before receiving my child's. Trying to force a relayout once I learn my child's size throws an exception, which is at the end of the code. Codepen fails silently.</p>
  <p><a href="https://codepen.io/gazialankus/pen/LYGrjxM" rel="noreferrer">https://codepen.io/gazialankus/pen/LYGrjxM</a></p>
  <pre><code>import 'package:flutter/material.dart';
  
  class AppRoot extends StatelessWidget {
    @override
    Widget build(BuildContext context) {
      return MaterialApp(
        title: 'Flutter Demo', // Appears in app switcher
        theme: ThemeData(
          primaryColor: Colors.grey.shade300,
          visualDensity: VisualDensity.adaptivePlatformDensity,
        ),
        home: MyPage(),
      );
    }
  }
  
  class MyPage extends StatelessWidget {
    @override
    Widget build(BuildContext context) {
      return Scaffold(
        body: Center(
          child: Container(
            color: Colors.yellow,
            child: CustomSingleChildLayout(
              delegate: MyDelegate(relayout: ValueNotifier&lt;int&gt;(0)),
              child: Text(
                &quot;I want to be in a square.&quot;,
                style: TextStyle(
                  backgroundColor: Colors.red
                ),
              ),
            ),
          ),
        ),
      );
    }
  }
  
  
  class MyDelegate extends SingleChildLayoutDelegate {
    ValueNotifier&lt;int&gt; relayout;
    MyDelegate({ this.relayout }) : super(relayout: relayout);
  
    @override
    bool shouldRelayout(SingleChildLayoutDelegate oldDelegate) {
      return desiredWidth == 100;
    }
  
    @override
    BoxConstraints getConstraintsForChild(BoxConstraints constraints) {
      print('getConstraintsForChild');
      return super.getConstraintsForChild(constraints);
    }
  
    double desiredWidth = 300;
  
    @override
    Offset getPositionForChild(Size size, Size childSize) {
      print('getPositionForChild');
      print(size);
      print(childSize);
      if (size.width &gt; childSize.width) {
        desiredWidth = childSize.width;
        relayout.value++;
        // ^trying to force a relayout
        // throws exception, output is given at the bottom in comments
        print(&quot;RELAYOUT&quot;);
      }
      return super.getPositionForChild(size, childSize);
    }
  
    @override
    Size getSize(BoxConstraints constraints) {
      print('getSize');
      return Size(desiredWidth, 100);
    }
  }
  
  void main() {
    runApp(AppRoot());
  }
  
  /*
  Performing hot reload...
  Syncing files to device LG H990...
  I/flutter (19850): getSize
  I/flutter (19850): getConstraintsForChild
  I/flutter (19850): getPositionForChild
  I/flutter (19850): Size(300.0, 100.0)
  I/flutter (19850): Size(148.0, 16.0)
  
  ════════ Exception caught by foundation library ════════════════════════════════════════════════════
  The following assertion was thrown while dispatching notifications for ValueNotifier&lt;int&gt;:
  'package:flutter/src/rendering/object.dart': Failed assertion: line 1527 pos 12: '_debugCanPerformMutations': 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=BUG.md
  
  When the exception was thrown, this was the stack: 
  #2      RenderObject.markNeedsLayout (package:flutter/src/rendering/object.dart:1527:12)
  #3      RenderBox.markNeedsLayout (package:flutter/src/rendering/box.dart:2053:11)
  #4      ChangeNotifier.notifyListeners (package:flutter/src/foundation/change_notifier.dart:207:21)
  #5      ValueNotifier.value= (package:flutter/src/foundation/change_notifier.dart:274:5)
  #6      MyDelegate.getPositionForChild (package:m_health/ui/app_root.dart:64:16)
  ...
  The ValueNotifier&lt;int&gt; sending notification was: ValueNotifier&lt;int&gt;#ac2fb(1)
  ════════════════════════════════════════════════════════════════════════════════════════════════════
  Reloaded 2 of 522 libraries in 1,390ms.
  I/flutter (19850): RELAYOUT
  
   */
  </code></pre>