skip to Main Content

I have a wrap widget with some form fields in it like so for my most recent attempt with constrained box:

Wrap(
        children: [
          ConstrainedBox(
            constraints: BoxConstraints(minWidth: 100),
            child: createTextField(
              context,
              'Ref. Company (Insurer)',
              'reference_company',
              initialValue: widget.form.referenceCompany ?? '',
              onChange: (val) => widget.form.referenceCompany = val,
              enabled: bo.insurerInfo.insurer.isNullOrEmpty(),
              // expanded: true,
            ),
          ),
          const SizedBox(width: 4),
          ConstrainedBox(
            constraints: BoxConstraints(minWidth: 100),
            child: createTextField(
              context,
              'Community Id',
              'community_id',
              initialValue: widget.form.communityId ?? '',
              onChange: (val) => widget.form.communityId = val,
              // expanded: true,
            ),
          ),
          const SizedBox(width: 4),
          ConstrainedBox(
            constraints: BoxConstraints(minWidth: 100),
            child: createTextField(
              context,
              'Community Name',
              'community_name',
              initialValue: widget.form.communityName ?? '',
              onChange: (val) => widget.form.communityName = val,
              // expanded: true,
            ),
          ),
        ],
      )

This will render the form fields on separate lines with an expanded width.

I want this to happen on smaller devices (perhaps a phone) and for larger devices, I want them to be on a single line (perhaps an iPad). For in-between devices I want the fields to be able to have a minWidth and if all three won’t fit, I want the first two two render on the first line with expanded widths and the third to render on the second line as expandned.

I have a great deal of forms to create and varying sizes and widths where it needs to work on multiple device sizes. I am working alone and am a little stuck on how to do this.

see this image for details on what I want:
enter image description here

EDIT:
I recently found pub.dev/packages/flex_list Which is close to what I want, however I can’t use a TextField as a child it seems since it does not implement the computeDryLayout method… I can’t find a way around that yet. Wrapping it in a Container or SizedBox does not work to give it a width.

EDIT: got it working in my answer below with some workarounds.

2

Answers


  1. Chosen as BEST ANSWER

    The flex_list package may be used here with some modifications.

    The FlexList will space each element just like a wrap might, but will then expand each element to completely fill up a row. The caveat here is that each element needs to implement a computeDryLayout method, which a TextField does not.

    A workaround to this is to wrap each TextField in a render object that use the child's intrinsic dimensions instead of invoking the child's dry layout method. see this github issue for a discussion around the topic.

    the object we need to wrap each text field may be added to the project like so:

    import 'package:flutter/material.dart';
    import 'package:flutter/rendering.dart';
    
    /// NOTE ref: https://github.com/flutter/flutter/issues/71687 & https://gist.github.com/matthew-carroll/65411529a5fafa1b527a25b7130187c6
    /// Same as `IntrinsicWidth` except that when this widget is instructed
    /// to `computeDryLayout()`, it doesn't invoke that on its child, instead
    /// it computes the child's intrinsic width.
    ///
    /// This widget is useful in situations where the `child` does not
    /// support dry layout, e.g., `TextField` as of 01/02/2021.
    class DryIntrinsicWidth extends SingleChildRenderObjectWidget {
      const DryIntrinsicWidth({Key? key, Widget? child}) : super(key: key, child: child);
    
      @override
      RenderDryIntrinsicWidth createRenderObject(BuildContext context) => RenderDryIntrinsicWidth();
    }
    
    class RenderDryIntrinsicWidth extends RenderIntrinsicWidth {
      @override
      Size computeDryLayout(BoxConstraints constraints) {
        if (child != null) {
          final width = child!.computeMinIntrinsicWidth(constraints.maxHeight);
          final height = child!.computeMinIntrinsicHeight(width);
          return Size(width, height);
        } else {
          return Size.zero;
        }
      }
    }
    
    /// NOTE ref: https://github.com/flutter/flutter/issues/71687 & https://gist.github.com/matthew-carroll/65411529a5fafa1b527a25b7130187c6
    /// Same as `IntrinsicHeight` except that when this widget is instructed
    /// to `computeDryLayout()`, it doesn't invoke that on its child, instead
    /// it computes the child's intrinsic height.
    ///
    /// This widget is useful in situations where the `child` does not
    /// support dry layout, e.g., `TextField` as of 01/02/2021.
    class DryIntrinsicHeight extends SingleChildRenderObjectWidget {
      const DryIntrinsicHeight({Key? key, Widget? child}) : super(key: key, child: child);
    
      @override
      RenderDryIntrinsicHeight createRenderObject(BuildContext context) => RenderDryIntrinsicHeight();
    }
    
    class RenderDryIntrinsicHeight extends RenderIntrinsicHeight {
      @override
      Size computeDryLayout(BoxConstraints constraints) {
        if (child != null) {
          final height = child!.computeMinIntrinsicHeight(constraints.maxWidth);
          final width = child!.computeMinIntrinsicWidth(height);
          return Size(width, height);
        } else {
          return Size.zero;
        }
      }
    }
    

    Now with these new objects we can use the same given list like below. Instead of a flex factor, the width of each SizedBox may be used to make it behave similar to a flex on an Expanded object.

    (e.x. width of 200 and 400 would be like a flex of 1 and 2)

    FlexList(
                verticalSpacing: 0,
                horizontalSpacing: 4,
                children: [
                  DryIntrinsicWidth(
                    child: SizedBox(
                      width: 400,
                      child: createTextField(
                        context,
                        'Ref. Company (Insurer)',
                        'reference_company',
                        initialValue: widget.form.referenceCompany ?? '',
                        onChange: (val) => widget.form.referenceCompany = val,
                        enabled: bo.insurerInfo.insurer.isNullOrEmpty(),
                      ),
                    ),
                  ),
                  DryIntrinsicWidth(
                    child: SizedBox(
                      width: 200,
                      child: createTextField(
                        context,
                        'Community Id',
                        'community_id',
                        initialValue: widget.form.communityId ?? '',
                        onChange: (val) => widget.form.communityId = val,
                        // expanded: true,
                      ),
                    ),
                  ),
                  DryIntrinsicWidth(
                    child: SizedBox(
                      width: 200,
                      child: createTextField(
                        context,
                        'Community Name',
                        'community_name',
                        initialValue: widget.form.communityName ?? '',
                        onChange: (val) => widget.form.communityName = val,
                        // expanded: true,
                      ),
                    ),
                  ),
                ],
              ),
    

    Note: The width on the SizedBox is treated like a minWidth and this will be expanded by the FlexList if more space is available.


  2. Maybe you could try a layout builder

          class MyHomePage extends StatefulWidget {
          const MyHomePage({super.key});
        
          @override
          State<MyHomePage> createState() => _MyHomePageState();
        }
        
        class _MyHomePageState extends State<MyHomePage> {
          @override
          Widget build(BuildContext context) {
            return Scaffold(
              body: LayoutBuilder(builder: (context, constraints) {
                return Center(
    //remove padding if u don't want the border around the page....
                  child: Padding(
                    padding: const EdgeInsets.symmetric(horizontal: 30.0),
                    child: Column(
                      crossAxisAlignment: CrossAxisAlignment.center,
                      mainAxisAlignment: MainAxisAlignment.center,
                      children: [
                        const SizedBox(height: 20),
                        if (constraints.maxWidth >= 768) ...[
                          Row(
                            children: [
                              Expanded(
                                child: TextFormField(
                                  decoration: inputDecoration(),
                                ),
                              ),
                              const SizedBox(width: 4),
                              Expanded(
                                child: TextFormField(
                                  decoration: inputDecoration(),
                                ),
                              ),
                              const SizedBox(width: 4),
                              Expanded(
                                child: TextFormField(
                                  decoration: inputDecoration(),
                                ),
                              ),
                            ],
                          ),
                        ] else ...[
                          Column(
                            children: [
                              TextFormField(
                                decoration: inputDecoration(),
                              ),
                              const SizedBox(height: 10),
                              TextFormField(
                                decoration: inputDecoration(),
                              ),
                              const SizedBox(height: 10),
                              TextFormField(
                                decoration: inputDecoration(),
                              ),
                            ],
                          ),
                        ]
                      ],
                    ),
                  ),
                );
              }),
            );
          }
        
          InputDecoration inputDecoration() {
            return const InputDecoration(
                enabledBorder:
                    OutlineInputBorder(borderSide: BorderSide(color: Colors.green)));
          }
        }
    

    enter image description here

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search