Flutter_form_builder: Dynamic forms issue

Created on 9 Dec 2020  路  3Comments  路  Source: danvick/flutter_form_builder

I have created table view for form elements. On click of Add row I am creating form elements-> adding form elements table row -> adding row in table. This is working perfectly. Only issue is delete row. At the end of each row I have added delete button. On click of it that row should deleted from table. But when I press delete button row is deleted from UI but data which I entered previously in that row is still shown. I have checked, row is properly deleted from array which holds data rows of table. I also unregister form elements which was in that deleted row.

Please help me.

I have attached gif for steps.

Screen_Recording_20201209-183436

Below is my source code

import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_form_builder/flutter_form_builder.dart';
import 'package:demo/core/constants/color_constants.dart';
import 'package:demo/core/constants/common_constants.dart';
import 'package:demo/core/constants/image_constants.dart';
import 'package:demo/core/constants/language_constants.dart';
import 'package:demo/core/global/app_localizations.dart';
import 'package:demo/core/utils/common_utils.dart';
import 'package:demo/core/widgets/loader.dart';
import 'package:demo/core/widgets/no_result_found.dart';
import 'package:demo/core/widgets/primary_button.dart';
import 'package:demo/features/forms/blocs/dynamic_table/bloc.dart';
import 'package:demo/features/forms/data/models/dynamic_table_form_element_model.dart';
import 'package:demo/features/forms/data/models/form_model.dart';
import 'package:demo/features/forms/data/repositories/form_repository.dart';
import 'package:demo/features/forms/ui/widgets/delete_dynamic_table_row.dart';


class DynamicTableFormWidget extends StatefulWidget {
  FormRepository formRepository;
  Properties dynamicTableProperties;
  FormFieldState formFieldState;
  bool readOnly=false;
  String tableId;
  ListFunction(List,bool isInsideTable) getRowWidgets;
  Map> elementsIdList = new Map();
  DynamicTableFormElementModel dynamicTableFormElementData;
  DynamicTableFormWidgetState formWidgetState = new DynamicTableFormWidgetState();
  GlobalKey fbKey;

  //this function called when value transformer called in custom table widget which return data in expected format for server
  String getValueForSave(){
    List saveDataRows;
    if(dynamicTableFormElementData.dataRows != null){
      saveDataRows = new List();
      for(DynamicTableRow row in dynamicTableFormElementData.dataRows){
        DynamicTableRow saveDataRow = new DynamicTableRow();
        saveDataRow.parentFormMetaID = row.parentFormMetaID;
        saveDataRow.formSubmissionID = row.formSubmissionID;
        saveDataRow.operationType = row.operationType;
        saveDataRow.parentFormSubmissionID = row.parentFormSubmissionID;

        Map requestBody = new Map();

        //rows
        for(String rowId in elementsIdList.keys) {
          if(row.rowId == rowId){
            List ids = elementsIdList[rowId];
            for(int index =0; index< ids.length; index++){
              dynamic value = fbKey.currentState.fields[ids[index]]?.currentState?.value;
              if(value != null){
                if(dynamicTableFormElementData.headerRow[index].type == CommonConstants.DATE_PICKER_FORM_ELEMENT)
                  value = CommonUtils.convertStringDate(date: value.toString(),toFormat: CommonConstants.SERVER_DATE_FORMAT);
                else if(dynamicTableFormElementData.headerRow[index].type == CommonConstants.TIME_PICKER_FORM_ELEMENT)
                  value = CommonUtils.convertStringDate(date: value.toString(),toFormat: CommonConstants.SERVER_TIME_FORMAT);
                else if(dynamicTableFormElementData.headerRow[index].type == CommonConstants.PROGRESS_BAR_FORM_ELEMENT)
                  value = value.round();
                else if(dynamicTableFormElementData.headerRow[index].type == CommonConstants.LABEL_FORM_ELEMENT)
                  value = dynamicTableFormElementData.headerRow[index].label;
              }

              requestBody[dynamicTableFormElementData.headerRow[index].id] = value;
            }
            break;
          }
        }
        saveDataRow.requestBody = requestBody;
        saveDataRows.add(saveDataRow);
      }
    }
    return jsonEncode(saveDataRows);
  }

  DynamicTableFormWidget({Key key,this.fbKey,this.formFieldState,this.readOnly=false,this.dynamicTableProperties,this.formRepository,this.getRowWidgets,@required this.tableId})
  :  assert(formFieldState != null),
        super(key: key);

  @override
  DynamicTableFormWidgetState createState() => formWidgetState;
}

class DynamicTableFormWidgetState extends State {
  String value;
  DynamicTableBloc _dynamicTableBloc;
  final double TABLE_COLUMN_WIDTH =125;
  int rowIndex=0;

  @override
  void initState() {
    widget.elementsIdList = new Map();
    value = widget.formFieldState.value;
    _dynamicTableBloc = DynamicTableBloc(formRepository: widget.formRepository);
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      child: BlocProvider(
        create: (BuildContext context) => _dynamicTableBloc,
        child: BlocListener(
          bloc: _dynamicTableBloc,
          listener: (BuildContext context, DynamicTableState state) {
            try {
            } catch (error) {
              CommonUtils.showToast(context, null);
            }
          },
          child: new BlocBuilder(
            bloc: _dynamicTableBloc,
            builder: (BuildContext context, DynamicTableState state) {
              if (state is DynamicTableInitial) {
                _startFetchingDynamicFormData();
                return _showLoader();
              }
              else if(state is GetDynamicTableFormDataInProgress){
                return _showLoader();
              }
              else if(state is GetDynamicTableFormDataSuccess){
                widget.dynamicTableFormElementData = state.dynamicTableFormElementData;
                if(widget.dynamicTableFormElementData.headerRow==null || widget.dynamicTableFormElementData.headerRow.length==0)
                  return _showNoResult(errorMessage: LanguageConstant.form_elements_are_empty);
                else{
                  if(widget.dynamicTableFormElementData.dataRows!=null && rowIndex == 0)
                    rowIndex = widget.dynamicTableFormElementData.dataRows.length;

                  return _getBodyWidget();
                }

              }
              else if(state is GetDynamicTableFormDataFailure){
                return _showNoResult(errorMessage: state.errorMessage, errorSubMessage: state.errorSubMessage,errorCode: state.errorCode,errorImage: state.errorImage);
              }
              return Container();
            },
          ),
        ),
      ),
      );
  }

  _startFetchingDynamicFormData(){
    _dynamicTableBloc.add(GetDynamicFormWidgetDataStarted(
        tableId:widget.tableId,
        formId: widget.dynamicTableProperties.parentFormMetaID,
        formSubmissionId: widget.dynamicTableProperties.parentFormSubmissionID),
    );
  }


  Widget _getBodyWidget() {
    return Container(
        child:
        Column(
          children: [
            SingleChildScrollView(
              scrollDirection: Axis.horizontal,
              child: Flexible(
                child: Table(defaultColumnWidth: FixedColumnWidth(TABLE_COLUMN_WIDTH),
                  border: TableBorder.all(color: ColorConstants.FORMS_WIDGET_BORDER_COLOR,width: 0.5),
                  children: _getTableRow()
                ),
              ),
            ),
            Row(
              children: [
                Expanded(child: PrimaryButton(text: "Add Row",onPressed: (value){_addNewRow();},)),
              ],
            )
          ],
        ),
    );
  }

  _addNewRow(){
    DynamicTableRow newRow = new DynamicTableRow();
    newRow.operationType = CommonConstants.DYNAMIC_TABLE_FORM_ELEMENT_ADD_OPERATION_TYPE;
    newRow.parentFormMetaID = widget.dynamicTableProperties.parentFormMetaID.toString();

    newRow.nestedSubmission = new NestedSubmission();
    newRow.nestedSubmission.elements = new List();
    newRow.nestedSubmission.elements = DynamicTableFormElementModel.fromJson(widget.dynamicTableFormElementData.toJson()).headerRow;
    List elementIds = new List();
    for(FormElement element in newRow.nestedSubmission.elements){
      element.id = widget.tableId +"_"+ element.id.toString()+ "_"+rowIndex.toString();
      elementIds.add(element.id);
    }
    newRow.rowId = CommonUtils.getUniqueIdFromCurrentTimeStamp(prefix: widget.dynamicTableFormElementData.dataRows.length.toString());
    widget.elementsIdList[newRow.rowId] = elementIds;
    widget.dynamicTableFormElementData.dataRows.add(newRow);
    rowIndex++;
    setState(() {});
  }

  _deleteRow({@required String rowId}){
    int rowIndex = _getIndexOfRowFromRowId(rowId:rowId);
    if(rowIndex != null){
      for(String ids in widget.elementsIdList[rowId])
        widget.fbKey.currentState.unregisterFieldKey(ids);

      if(widget.dynamicTableFormElementData.dataRows[rowIndex].operationType == CommonConstants.DYNAMIC_TABLE_FORM_ELEMENT_ADD_OPERATION_TYPE)
        {
          widget.dynamicTableFormElementData.dataRows.removeAt(rowIndex);
          widget.elementsIdList.remove(rowId);
        }
      else
        widget.dynamicTableFormElementData.dataRows[rowIndex].operationType = CommonConstants.DYNAMIC_TABLE_FORM_ELEMENT_DELETE_OPERATION_TYPE;


      setState(() {});
    }
    else{
      CommonUtils.showToast(context, "Invalid index for delete row");
    }

  }

  int _getIndexOfRowFromRowId({@required String rowId}){
    int index = 0;
    for(DynamicTableRow row in widget.dynamicTableFormElementData.dataRows){
      if(row.rowId == rowId){
        return index;
      }
      index++;
    }
    return null;
  }

  List _getTableRow() {
    List rows = new List();

    //make header
    ListheaderWidgets = new List();
    for(FormElement column in widget.dynamicTableFormElementData.headerRow) {
      headerWidgets.add(
          Container(padding: EdgeInsets.all(4),
              child: Expanded(
                child: Text(
                  column.label,
                  textAlign: TextAlign.center,
                  style: Theme
                      .of(context)
                      .textTheme
                      .bodyText1,
                ),
              )
          )
      );
    }
    //delete row column header
    headerWidgets.add(Container(padding: EdgeInsets.all(4),
        child: Expanded(
          child: Text(
            AppLocalizations.of(context).translate(LanguageConstant.delete),
            textAlign: TextAlign.center,
            style: Theme
                .of(context)
                .textTheme
                .bodyText1,
          ),
        )
    )
    );
    rows.add(TableRow(children: headerWidgets));

    //make remaining rows
    if(widget.dynamicTableFormElementData.dataRows != null){
      for(DynamicTableRow row in widget.dynamicTableFormElementData.dataRows){
        if(row.operationType==CommonConstants.DYNAMIC_TABLE_FORM_ELEMENT_DELETE_OPERATION_TYPE)
          continue;

        List elements = row?.nestedSubmission?.elements;
        if(elements != null){
          List elementIds = new List();
          for(FormElement element in elements)
            elementIds.add(element.id);
          widget.elementsIdList[row.rowId] = elementIds;

          Listwidgets = widget.getRowWidgets(elements,true);
          if(widgets!= null){
            List cells= new List();
            for(Widget widget in widgets){
              cells.add(Center(
                child: Container(
                    padding:EdgeInsets.all(4),
                    child: Column(
                      children: [
                        widget,
                      ],
                    )
                ),
              ));
            }
            //add delete cell
            cells.add( DeleteDynamicTableRow(rowId: row.rowId,onDeletePressed: (rowId){_deleteRow(rowId:rowId);},));
            rows.add(TableRow(children: cells));
          }
        }
      }
    }
    return rows;
  }

// show loader till table loads widgets from server
  Widget _showLoader() {
    return Loader();
  }

// show no result widget, if server did not respond or some error happened
  Widget _showNoResult({@required String errorMessage, String errorSubMessage, int errorCode, String errorImage, bool showActionButton=true}) {
    return  ConstrainedBox(constraints: BoxConstraints(minHeight: 300),
      child: NoResultFound(
          title: errorMessage != null ? AppLocalizations.of(context).translate(errorMessage):AppLocalizations.of(context).translate(LanguageConstant.oops_something_went_wrong) ,
          subtitle: errorSubMessage != null
              ? AppLocalizations.of(context).translate(errorSubMessage)
              : null,
          backGroundColor: ColorConstants.white,
          image: (errorCode != null &&
              errorCode == CommonConstants.NO_INTERNET_ERROR_CODE)
              ? ImageConstants.NOT_FOUND
              : errorImage ?? ImageConstants.WARNING,
          showActionButton: showActionButton,
          onActionButtonPressed: () {
            _startFetchingDynamicFormData();
          }),
    );
  }
}
invalid

All 3 comments

I'm pretty sure that this issue is due to how Flutter works, and has nothing to do specifically with _Flutter Form Builder_.

@SachinTanpure Please try specifying a key value for your TableRow elements. Then, Flutter should be able to do a better job of understanding that the middle row is being deleted, and not the last row. (Without the key, it really can't distinguish, and relies on type and ordinal position.)

I should clarify that you can't just use _Row index_ as your key value. You need something that will be bound to the row's data so that Flutter sees a transition like:

  • Before: [1, b, 2]
  • After: [1, 2]

A _unique key_ like a _Primary Key_ is an excellent choice.

(I hope this makes sense. I fear I may not be explaining this very well.)

@awhitford You are genius. Thanks, your solution worked perfectly. Instead of Primary Key, I have used object key for table row key because I am deleting object from array and then do setstate.

Was this page helpful?
0 / 5 - 0 ratings