Search code examples
flutter

Wrap two fields in a row in Flutter


I have the following Flutter component that renders some fields inside a form:

import 'package:flutter/material.dart';
import 'package:frontend/data/models/person_model.dart';
import 'package:frontend/data/models/product.dart';
import 'package:frontend/services/my_store.dart';
import 'package:provider/provider.dart';

class ClientForm extends StatefulWidget {
  const ClientForm({this.client, super.key});

  final Person? client;

  @override
  State<ClientForm> createState() => _ClientFormState();
}

class _ClientFormState extends State<ClientForm> {
  @override
  Widget build(BuildContext context) {
    final List<Product> products = Provider.of<GymmanStore>(context).products;
    final appColors = Provider.of<GymmanStore>(context).appColors;
    final levels = ["Básico", "Intermedio", "Avanzado"];
    final closeDays = List<int>.generate(31, (i) => i + 1).toList().map((int day) => day.toString()).toList();

    return Scaffold(
      appBar: AppBar(
        title: widget.client == null?
          Text('Nuevo Cliente'):
          Text('Editar Cliente'),
      ),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.center,
          children: [
            Form(
              child: Column(
                children: [
                  TextFormField(
                    style: TextStyle(
                      color: appColors.textColor,
                    ),
                    decoration: InputDecoration(
                      labelText: 'Nombre',
                      hintText: 'Nombre del cliente',
                    ),
                  ),
                  TextFormField(
                    style: TextStyle(
                      color: appColors.textColor,
                    ),
                    keyboardType: TextInputType.phone,
                    decoration: InputDecoration(
                      labelText: 'Teléfono',
                      hintText: 'Teléfono del cliente',
                    ),
                  ),
                  DropdownButtonFormField(
                    style: TextStyle(
                      color: appColors.textColor,
                    ),
                    decoration: InputDecoration(
                      labelText: 'Producto',
                      hintText: 'Producto adquirido',
                    ),
                    items: products.map((Product product) {
                      return DropdownMenuItem(
                        value: product,
                        child: Text(
                          style: TextStyle(
                            color: appColors.dialogTextColor,
                          ),
                          product.name
                        ),
                      );
                    }).toList(),
                    onChanged: (value) {},
                  ),
                  DropdownButtonFormField(
                    style: TextStyle(
                      color: appColors.textColor,
                    ),
                    decoration: InputDecoration(
                      labelText: 'Nivel',
                      hintText: 'Nivel del cliente.',
                    ),
                    items: levels.map((String level) {
                      return DropdownMenuItem(
                        value: level,
                        child: Text(
                          style: TextStyle(
                            color: appColors.dialogTextColor,
                          ),
                          level
                        ),
                      );
                    }).toList(),
                    onChanged: (value) {},
                  ),
                  DropdownButtonFormField(
                    style: TextStyle(
                      color: appColors.textColor,
                    ),
                    decoration: InputDecoration(
                      labelText: 'Día de Cierre',
                      hintText: 'Día en que se vence el pago del cliente.',
                    ),
                    items: closeDays.map((String closeDay) {
                      return DropdownMenuItem(
                        value: closeDay,
                        child: Text(
                          style: TextStyle(
                            color: appColors.dialogTextColor,
                          ),
                          closeDay
                        ),
                      );
                    }).toList(),
                    onChanged: (value) {},
                  ),
                  TextButton(
                    onPressed: () {
                      // Save the client
                    },
                    child: const Text('Guardar'),
                  ),
                ],
              )
            ),
          ],
        ),
      ),
    );
  }
}

The script runs with no problem, but I want two of the fields to be in the same row in order to save space, so I added a Row widget and placed both fields in it, like this:

return Scaffold(
      appBar: AppBar(
        title: widget.client == null?
          Text('Nuevo Cliente'):
          Text('Editar Cliente'),
      ),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.center,
          children: [
            Form(
              child: Column(
                children: [
                  TextFormField(
                    style: TextStyle(
                      color: appColors.textColor,
                    ),
                    decoration: InputDecoration(
                      labelText: 'Nombre',
                      hintText: 'Nombre del cliente',
                    ),
                  ),
                  TextFormField(
                    style: TextStyle(
                      color: appColors.textColor,
                    ),
                    keyboardType: TextInputType.phone,
                    decoration: InputDecoration(
                      labelText: 'Teléfono',
                      hintText: 'Teléfono del cliente',
                    ),
                  ),
                  DropdownButtonFormField(
                    style: TextStyle(
                      color: appColors.textColor,
                    ),
                    decoration: InputDecoration(
                      labelText: 'Producto',
                      hintText: 'Producto adquirido',
                    ),
                    items: products.map((Product product) {
                      return DropdownMenuItem(
                        value: product,
                        child: Text(
                          style: TextStyle(
                            color: appColors.dialogTextColor,
                          ),
                          product.name
                        ),
                      );
                    }).toList(),
                    onChanged: (value) {},
                  ),
                  Row(                       <<< ------------- New widget
                    children: [
                      DropdownButtonFormField(
                        style: TextStyle(
                          color: appColors.textColor,
                        ),
                        decoration: InputDecoration(
                          labelText: 'Nivel',
                          hintText: 'Nivel del cliente.',
                        ),
                        items: levels.map((String level) {
                          return DropdownMenuItem(
                            value: level,
                            child: Text(
                              style: TextStyle(
                                color: appColors.dialogTextColor,
                              ),
                              level
                            ),
                          );
                        }).toList(),
                        onChanged: (value) {},
                      ),
                      DropdownButtonFormField(
                        style: TextStyle(
                          color: appColors.textColor,
                        ),
                        decoration: InputDecoration(
                          labelText: 'Día de Cierre',
                          hintText: 'Día en que se vence el pago del cliente.',
                        ),
                        items: closeDays.map((String closeDay) {
                          return DropdownMenuItem(
                            value: closeDay,
                            child: Text(
                              style: TextStyle(
                                color: appColors.dialogTextColor,
                              ),
                              closeDay
                            ),
                          );
                        }).toList(),
                        onChanged: (value) {},
                      ),
                    ]
                  ),
                  TextButton(
                    onPressed: () {
                      // Save the client
                    },
                    child: const Text('Guardar'),
                  ),
                ],
              )
            ),
          ],
        ),
      ),
    );
  }
}

The problem is that, as soon as I wrap the fields inside the Row widget, I get the this error:

════════ Exception caught by rendering library ═════════════════════════════════
RenderBox was not laid out: RenderTransform#74346 NEEDS-LAYOUT NEEDS-PAINT
'package:flutter/src/rendering/box.dart':
Failed assertion: line 2176 pos 12: 'hasSize'

I found this answer and tried adding an Expanded widget, like this:

                 Expanded(
                    child: Row(
                      children: [
                        DropdownButtonFormField(
                          style: TextStyle(
                            color: appColors.textColor,
                          ),
                          decoration: InputDecoration(
                            labelText: 'Nivel',
                            hintText: 'Nivel del cliente.',
                          ),
                          items: levels.map((String level) {
                            return DropdownMenuItem(
                              value: level,
                              child: Text(
                                style: TextStyle(
                                  color: appColors.dialogTextColor,
                                ),
                                level
                              ),
                            );
                          }).toList(),
                          onChanged: (value) {},
                        ),
                        DropdownButtonFormField(
                          style: TextStyle(
                            color: appColors.textColor,
                          ),
                          decoration: InputDecoration(
                            labelText: 'Día de Cierre',
                            hintText: 'Día en que se vence el pago del cliente.',
                          ),
                          items: closeDays.map((String closeDay) {
                            return DropdownMenuItem(
                              value: closeDay,
                              child: Text(
                                style: TextStyle(
                                  color: appColors.dialogTextColor,
                                ),
                                closeDay
                              ),
                            );
                          }).toList(),
                          onChanged: (value) {},
                        ),
                      ]
                    ),
                  )

And I also tried wrapping the whole column with an Expanded wrapper, but the error persists.


Solution

  • Try to wrap each of DropdownButtonFormField inside the Row widget with IntrinsicWidth.
    This answer might be helpful.

    (Not related to your question, the first Column widget looks unnecessary.)