mydomain
No ADS
No ADS

FlutterArtist ListXData

  1. Problem and Solution
  2. ListXData<ID, ITEM>
In FlutterArtist, you frequently work with the FilterModel/FilterPanel and FormModel/FormView pairs. Some of their abstract methods involve the XData/ListXData data types. This article will clarify what XData/ListXData are and why we need them.
FilterModel/FilterPanel:
First, let's look at an example of FilterModel/FilterPanel:
FormModel/FormView:
Next, let's look at another example with FormModel/FormView:
FormModel
FormView
Registering the structure of a FormModel, where we define the attributes of the FormModel.
Each input field on the FormView corresponds to a FormProp defined in the FormModelStructure.
return FormModelStructure(
  simplePropDefs: [
    SimpleFormPropDef<int>(propName: "id"),
    SimpleFormPropDef<String>(propName: "name"),
    SimpleFormPropDef<String>(propName: "email"), 
		...
  ],
  multiOptPropDefs: [
    // Multi Option Single Selection Prop.
    MultiOptFormPropDef<SupplierTypeInfo>.singleSelection(
      propName: 'supplierType',
    ),
  ],
);
FaFormBuilderTextField(
  name: "email",
  maxLines: 1,
  labelText: "Email", 
), 
FaFormBuilderDropdown(
  name: "supplierType",
  labelText: "Supplier Type",
  items: formModel.getMultiOptPropData("supplierType") ?? [],
  getItemText: (item) {
    return item.name;
  }, 
),
The FormModel.performLoadMultiOptPropXData() method is used to load data for a MultiOptFormProp, such as supplierType.
FormModel.performLoadMultiOptPropXData()
@override
Future<XData?> performLoadMultiOptPropXData({
  required String multiOptPropName,
  required EmptyFilterCriteria filterCriteria,
  required EmptyExtraFormInput? extraFormInput,
  required Object? parentMultiOptPropValue,
}) async {
  if (multiOptPropName == "supplierType") {
    ApiResult<SupplierTypeInfoPage> result =
        await _supplierTypeRestProvider.queryAll();
    // IMPORTANT:
    result.throwIfError();
    // IMPORTANT: Generics should be declared explicitly.
    return ListXData<int, SupplierTypeInfo>.fromPageData(
      pageData: result.data,
      getItemId: (item) => item.id,
    );
  }
  return null;
}
In the case of supplierType, this method returns a ListXData<int, SupplierTypeInfo>, which contains a List<SupplierTypeInfo> - the actual data for the aforementioned Dropdown.

1. Problem and Solution

Consider the situation above: a user is interacting with a Form to modify a Supplier. The Form data includes the name, email address, supplier type, etc. Among them, the supplierType can be changed via a Dropdown.
The selectable data in the Dropdown (supplierType) includes:
API get supplierTypes
{
   "items": [
      {
        "id": 1,
        "code": "MF",
        "name": "Manufacturers"
      },
      {
        "id": 2,
        "code": "DI",
        "name": "Distributors"
      },
      {
        "id": 3,
        "code": "WS",
        "name": "Wholesalers"
      }
   ]
}
The SupplierData record being modified was created a long time ago, with data like this:
{
   "id": 0,
   "name": "Some Supplier",
   "email": "somesupplier@example.com",
   "supplierType": {
      "id": 0,
      "code": "DS",
      "name": "Dropshippers"
   },
   "imagePath": "...",
   "active": true,
   "description": "...",
}
Notice the supplierType attribute of the record above. This supplier type, for some reason, is no longer supported by the application and thus will not be returned from the Dropdown("supplierType") data API. This will cause an error in the Dropdown, and the user will receive the message:
 "The initial value is not in the drop down list".
To solve this problem, FlutterArtist uses ListXData<ID, ITEM> as a wrapper for a List<ITEM> (the actual data of a Dropdown).
ListXData
class ListXData<ID, ITEM> extends XData<ID, ITEM, List<ITEM>> {
  final List<ITEM> _items;

  List<ITEM> get items => [..._items];

  ...
}
With ListXData<ID, ITEM>, FlutterArtist ensures that if an ITEM is not present in the Dropdown<ITEM>, it will be automatically added.
ListXData is a class that extends from the XData class and implements all its abstract methods.
XData
abstract class XData<ID, ITEM, DATA> {
  final ID Function(ITEM item) _getItemId;

  final Map<ID, ITEM> _orphanItemsMap = {};

  List<ITEM> get orphanItems => List.unmodifiable(_orphanItemsMap.values);

  XData({
    required ID Function(ITEM item) getItemId,
  }) : _getItemId = getItemId;

  DATA get data;

  Type get itemType => ITEM;

  Type get itemIdType => ID;

  ITEM? findInternalItemById(ID? id);

  void addOrphanItem(ITEM item);

  void removeOrphanItem(ITEM item);

	...
}	
ListXData<ID, ITEM>
ListXData appears in the abstract method FormModel.performLoadMultiOptPropXData(), a method automatically called by the FlutterArtist library to load data for MultiOptFormProp(s).
FormModel.performLoadMultiOptPropXData()
@override
Future<XData?> performLoadMultiOptPropXData({
  required String multiOptPropName,
  required EmptyFilterCriteria filterCriteria,
  required EmptyExtraFormInput? extraFormInput,
  required Object? parentMultiOptPropValue,
}) async {
  if (multiOptPropName == "supplierType") {
    ApiResult<SupplierTypeInfoPage> result =
        await _supplierTypeRestProvider.queryAll();
    // IMPORTANT:
    result.throwIfError();
    // IMPORTANT: Generics should be declared explicitly.
    return ListXData<int, SupplierTypeInfo>.fromPageData(
      pageData: result.data,
      getItemId: (item) => item.id,
    );
  }
  return null;
}
Thus, to load data for a widget like FormBuilderDropdown or FormBuilderMultiDropdown, you only need to call an API that returns a ListXData<ID, ITEM> instead of a List<ITEM>, and nothing else is required.
  • FlutterArtist ApiResult
  • FlutterArtist PageData
  • FlutterArtist XData

2. ListXData<ID, ITEM>

The constructors of ListXData<ID,ITEM>:
ListXData({
  required List<ITEM> items,
  required super.getItemId,
})

ListXData.fromPageData({
  required PageData<ITEM>? pageData,
  required super.getItemId,
})

ListXData.ofItems({
  required List<ITEM> items,
  required super.getItemId,
})

ListXData.ofItem({
  required ITEM item,
  required super.getItemId,
})

ListXData.empty({
  required super.getItemId,
})
ListXData has several methods inherited from XData, which are essentially used by the FlutterArtist library and you don't need to use them at all.
@override
List<ITEM> get data  

@override
ITEM? findInternalItemById(ID? id) 

@override
List<ITEM> findInternalItems({required List<ITEM?>? items}) 

@override
void addOrphanItem(ITEM item) 

@override
void removeOrphanItem(ITEM item) 
  • FlutterArtist PageData
No ADS
No ADS