flutterdartdatatablewidgetsinglechildscrollview

SingleChildScrollView is not scrolling in a datatable


i trying to do a dynamic table when the user touches add botton a row will be displayed,in the final column i have a group of bottons, when the user touches the add botton will be add a form widget, but if add so much, the scrolling dont work, i show you my code and some pictures.

Before add widgets

After add widgets

GIF how work

import 'package:flutter/material.dart';
import 'package:simao/themes/themes.dart';
import 'package:simao/widgets/widgets.dart';

class FormLicitacion  extends StatefulWidget {
  
  FormLicitacion ({super.key});

  @override
  State<FormLicitacion> createState() => _FormLicitacionState();
}

class _FormLicitacionState extends State<FormLicitacion> {
  final _formKey = GlobalKey<FormState>();

  // List<Map <String, List<String>>> fechasOfEquipos = [{"0":['2023-02-15',]}];
  List<Map<String, String>> formEquipo = [];
  List<List<String>> datesOfEquipos = [];
  Map<String, String> formContrato = {};
  Map<String, String> formFilterValues = {};
  Map<String, String> formValues = {};
  List<Widget> rowsEquipos = [];
  List<int> equiposCount = [];
  List<double> columnsWidth = [100, 200, 200, 200, 200, 200, 250, 200, 200, 200];
  List<String> columnsName = ['No','Clave','Identificador','Nombre','Descripcion','Cantidad','Categoria','Fechas', 'Actividades', 'Acciones'];

  void resetFormFilters(){
    setState(() {
      formFilterValues = {
        'clave_filtro': '',
        'identificador_filtro': '',
        'nombre_filtro': '',
        'descripcion_filtro': '',
        'categorias_filtro': '',
        'subcategorias_filtro':'',
      }; 
    });
  }

  void resetFormContrato(){
    setState(() {
      formContrato = {
        'id': '',
        'empresas_id': '',
        'clientes_id': '',
        'periodicidad_id': '',
        'sucursal_id': '',
        'fecha_inicio': '',
        'fecha_final': '',
        'clave': '',
        'nombre': '',
        'monto': '',
        'descripcion': '',
      };
    });
  }

  void initEquipos({int index = 0}){
    setState(() {
      rowsEquipos = [
        CustomInputField(formProperty: "No", formValues: formEquipo[index], heightOfSizedBox: 0, readOnly: true, labelText: "No", hintText: "No"),
        CustomInputField(formProperty: "clave", formValues: formEquipo[index], heightOfSizedBox: 0, labelText: "Clave", hintText: "Clave"),
        CustomInputField(formProperty: "identificador", formValues: formEquipo[index], heightOfSizedBox: 0, labelText: "Identificador", hintText: "Identificador"),
        CustomInputField(formProperty: "nombre", formValues: formEquipo[index], heightOfSizedBox: 0, maxLines: 5, labelText: "Nombre", hintText: "Nombre"),
        CustomInputField(formProperty: "descripcion", formValues: formEquipo[index], heightOfSizedBox: 0, maxLines: 5, labelText: "Descripcion", hintText: "Descripcion"),
        CustomInputField(formProperty: "cantidad", formValues: formEquipo[index], heightOfSizedBox: 0, onlyNumber: true, keyboardType: TextInputType.number, labelText: "Cantidad", hintText: "Cantidad"),
        Column(
          children: [
            const SizedBox(height: 10),
            CustomDropDownCustom1(message: "Selecciona Categoria", formValues: formEquipo[index], formProperty: "categorias_id", heightOfSizedBox: 0, ),
            Text("Sub Categoria", style: TextStyle(fontWeight: FontWeight.bold)),
            CustomDropDownCustom1(message: "Selecciona Sub Categoria", formValues: formEquipo[index], formProperty: "subcategorias_id", heightOfSizedBox: 0,),
            Text("Precio Unitario", style: TextStyle(fontWeight: FontWeight.bold)),
            CustomInputField(formProperty: "precio_unitario", formValues: formEquipo[index], heightOfSizedBox: 0, onlyNumber: true, keyboardType: TextInputType.number, labelText: "Precio Unitario", hintText: "Precio Unitario"),
          ],
        ),

        CustomInputField(formProperty: "tareas_id", formValues: formEquipo[index], heightOfSizedBox: 0,),
        

        // THIS IS THE PROBLEM
        // THE SINGLE CHILD SCROLL VIEW IS NOT SCROLLING
        SingleChildScrollView(
          scrollDirection: Axis.vertical,
          child: Column(
            children: [
              for(var wid in initWidgetDates(index: index)) wid,
            ],
          ),
        ),
   
        Column(
          children: [
            TextButton(child: Icon(Icons.add, color: Color.fromARGB(255, 255, 255, 255)), onPressed: (){addDate(index: index);}, style: TextButton.styleFrom(backgroundColor: AppTheme.successColor),),
            TextButton(child: Icon(Icons.remove, color: Color.fromARGB(255, 255, 255, 255)), onPressed: (){deleteDate(index: index);},  style: TextButton.styleFrom(backgroundColor: AppTheme.successColor)),
            TextButton(child: Icon(Icons.delete, color: Color.fromARGB(255, 255, 255, 255)), onPressed: (){deleteEquipo(index: index);}, style: TextButton.styleFrom(backgroundColor: AppTheme.dangerColor)),
            TextButton(child: Icon(Icons.edit, color: Color.fromARGB(255, 255, 255, 255)), onPressed: (){}, style: TextButton.styleFrom(backgroundColor: AppTheme.primaryColor)),
            TextButton(child: Icon(Icons.qr_code, color: Color.fromARGB(255, 255, 255, 255)), onPressed: (){}, style: TextButton.styleFrom(backgroundColor: AppTheme.primaryBulmaColor)),
          ],
        ),
      ];
    });
  }

  List<Widget> initWidgetDates({required int index}){
    List<Widget> widgetDates = [];
    for (var i = 0; i < datesOfEquipos[index].length; i++) {
      widgetDates.add(CustomDatePickerField(hintText: "Fecha", labelText: "Fecha",  onSelectedDate: (date){setState(() {
        datesOfEquipos[index][i] = date.toString();
      });}, value: datesOfEquipos[index][i],));
    }
    return widgetDates;
  }

  void addDate({required int index}){
    datesOfEquipos[index].add("");
    initEquipos(index: index);
    setState(() {});
  }

  void deleteDate({required int index}){
    if((datesOfEquipos[index].length - 1) > 0){
      datesOfEquipos[index].removeLast();
      initEquipos(index: index);
      setState(() {});
    }
  }

  void newEquipo() {
    int count = equiposCount.length;
    equiposCount.add((count+1));
    datesOfEquipos.add([""]);
    addFormEquipo();
    setState(() {});
  }
  
  void addFormEquipo(){
    setState(() {
      formEquipo.add({
        "No" : "",
        "clave" : "",
        "identificador" : "",
        "nombre" : "",
        "descripcion" : "",
        "cantidad" : "",
        "categorias_id" : "",
        "subcategorias_id" : "",
        "precio_unitario" : "",
        "tareas_id" : "",
      });
    });
  }

  void deleteEquipo({required int index}){
    equiposCount.removeAt(index);
    formEquipo.removeAt(index);
    datesOfEquipos.removeAt(index);
    setState(() {});
  }
  
  @override
  void initState() {
    // TODO: implement initState
    super.initState();
    resetFormContrato();
    resetFormFilters();
  }

  @override
  Widget build(BuildContext context) {
    return DefaultTabController(
      length: 2,
      child: Scaffold(
        appBar: AppBar(
          title: Text("LicitaciĆ³n"),
          bottom: TabBar(
            tabs: [
              Tab(text: "Contrato"),
              Tab(text: "Equipos"),
            ],
          ),
        ),
        body: TabBarView(
          children: [
            SingleChildScrollView(child: Contrato()),
            SingleChildScrollView(child: Equipos()),
          ],
        ),
        persistentFooterButtons: [
          Row(
            mainAxisAlignment: MainAxisAlignment.spaceEvenly,
            children:[
              ElevatedButton(
                  onPressed: () {
                    Navigator.pop(context);
                  },
                  child: Text("Cancelar"),
                  style: ElevatedButton.styleFrom(
                    backgroundColor: AppTheme.dangerColor,
                  ),
                ),
                ElevatedButton(
                  onPressed: () {

                  },
                  child: Text("Guardar"),
                  style: ElevatedButton.styleFrom(
                    backgroundColor: AppTheme.successColor,
                  ),
                ),
            ]
          )
        ],
      ),
    );
  }

  Column Contrato() => Column(
    children: [
      const SizedBox(height: 10),
      CustomDropDownCustom1(
        message: "Selecciona Empresa", 
        formValues: formContrato, 
        formProperty: "Empresa",
        heightOfSizedBox: 20,
      ),
      CustomDropDownCustom1(
        message: "Selecciona Cliente", 
        formValues: formContrato, 
        formProperty: "Cliente",
        heightOfSizedBox: 20,
      ),
      CustomDropDownCustom1(
        message: "Selecciona Periodicidad", 
        formValues: formContrato, 
        formProperty: "Periodicidad",
        heightOfSizedBox: 20,
      ),
      CustomDropDownCustom1(
        message: "Selecciona Sucursal", 
        formValues: formContrato, 
        formProperty: "Sucursal",
        heightOfSizedBox: 20,
      ),
      CustomDatePickerField(
        hintText: "Fecha", 
        labelText: "Fecha",
        heightOfSizedBox: 20,
        onSelectedDate: (date){

        }),
      CustomInputField(
        formProperty: "clave", 
        formValues: formContrato,
        hintText: "Clave",  
        labelText: "Clave",
        onlyLettersAndNumbersNoSpaces: true,        
      ),
      CustomInputField(
        formProperty: "nombre", 
        formValues: formContrato,
        hintText: "Nombre",  
        labelText: "Nombre",
        onlyLettersAndNumbersNoSpaces: true,        
      ),
      CustomInputField(
        formProperty: "monto", 
        formValues: formContrato,
        hintText: "Monto",  
        labelText: "Monto",
        onlyNumber: true,
        keyboardType: TextInputType.number,
        minValueInt: 1,
      ),
      CustomInputField(
        formProperty: "descripcion", 
        formValues: formContrato,
        hintText: "Descripcion",  
        labelText: "Descripcion",
        onlyLettersAndNumbersNoSpaces: true,
        maxLines: 3,
      ),
    ],
  );

  Column Equipos() => Column(
    children: [
      Filters(),
      PrintVARS(),
      newEquipoButton(),
      if(equiposCount.length != 0)...[
        TableOfEquipos()
      ],
    ],
  );

  TextButton PrintVARS() {
    return TextButton(onPressed: (){
      print("CONTADOR EQUIPOS ----> $equiposCount");
      print("FORM EQUIPOS ----> $formEquipo");
      print("FORM EQUIPOS ----> $formEquipo");
      print("DATESOFEQUIPOS ----> $datesOfEquipos");
    }, child: Text("TEST PRINT"));
  }

  SingleChildScrollView TableOfEquipos(){
    return SingleChildScrollView(
      scrollDirection: Axis.horizontal,
      child: DataTable(
        dataRowHeight: 270,
        columns: [
          for(var text in columnsName)...[
            DataColumn(label: Text("$text", textAlign: TextAlign.center)),
          ]
        ], 
        rows: [
          for (var equipo in equiposCount)...[
            DataRow(
              cells: [
                for(var i = 0; i < columnsWidth.length; i++)...[
                  DataCell(
                    Column(
                      mainAxisAlignment: MainAxisAlignment.center,
                      crossAxisAlignment: CrossAxisAlignment.center,
                      children: [
                        Container(
                          width: columnsWidth[i],
                          child: widgetOfTable(index: equiposCount.indexOf(equipo), indexOfWidget: i)
                        )
                      ],
                    )
                  ),
                ],
              ]
            ),
          ],
        ]
        
      )
    );
  }

  TextButton newEquipoButton() {
    return TextButton.icon(
      onPressed: () {
        newEquipo();
      },
      icon: Icon(Icons.add),
      label: const Text("Nuevo Equipo"),
      style: TextButton.styleFrom(
          backgroundColor: AppTheme.primaryColor,
          foregroundColor: Colors.white),
    );
  }

  Form Filters() {
    return Form(
      key: _formKey,
      child: ExpansionTile(
        title: Text("Filtros"),
        children: [
          const SizedBox(height: 5),
          CustomInputField(
            formProperty: "clave", 
            formValues: formFilterValues,
            hintText: "Clave",
            labelText: "Clave",
            onlyNumber: true,
            keyboardType: TextInputType.number,
          ),
          CustomInputField(
            formProperty: "identificador", 
            formValues: formFilterValues,
            hintText: "Identificador",
            labelText: "Identificador",
          ),
          CustomInputField(
            formProperty: "nombre", 
            formValues: formFilterValues,
            hintText: "Nombre",
            labelText: "Nombre",
          ),
          CustomInputField(
            formProperty: "descripcion", 
            formValues: formFilterValues,
            hintText: "Descripcion",
            labelText: "Descripcion",
          ),
          CustomDropDownCustom1(
            message: "Seleccione categorias", 
            formValues: formFilterValues, 
            formProperty: "active",
            items: {
              "1": "Activo",
              "0": "Inactivo",
            },
          ),
          CustomDropDownCustom1(
            message: "Seleccione subcategorias", 
            formValues: formFilterValues, 
            formProperty: "active",
            items: {
              "1": "Activo",
              "0": "Inactivo",
            },
          ),

          ButtonsOfFilters()
        ],
      ),
    );
  }

  Row ButtonsOfFilters(){
    return Row(
      mainAxisAlignment: MainAxisAlignment.spaceEvenly,
      children: [
        TextButton.icon(
          onPressed: () {
          },
          icon: Icon(Icons.search),
          label: const Text("Buscar"),
          style: TextButton.styleFrom(
              backgroundColor: AppTheme.primaryColor,
              foregroundColor: Colors.white),
        ),
        TextButton.icon(
          onPressed: () {

          },
          icon: Icon(Icons.clear),
          label: const Text("Limpiar"),
          style: TextButton.styleFrom(backgroundColor: AppTheme.dangerColor,foregroundColor: Colors.white),
        ),
      ],
    );
  }

  Widget widgetOfTable({required int index, required int indexOfWidget}) {
    initEquipos(index: index);
    return rowsEquipos[indexOfWidget];
  }

}

i tried, use a chatgpt and github coopilot and dont work :(


Solution

  • Wrap SingleChildScrollView with SizedBox to define fixed height for scroll area.

    SizedBox(
      height: 200,
      child: SingleChildScrollView(
        child: Column(
          children: [...]
        ),
      ),
    )