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.

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();
}),
);
}
}
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:
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.