FlutterArtist FilterModel MultiOptFilterCriterion ex1
In this article, we will explore how to configure a filter with parent-child criteria, such as the relationship between "Company" and "Department."

The core discussion revolves around the Demo03a example in FlutterArtist Demo. In this case, Employee03aFilterModel includes three search criteria: Employee Name, Company, and Department — where Company and Department share a hierarchical relationship. Specifically, when the selected Company changes, the department list in the second Dropdown must automatically reload based on that selection.
Chú ý: Nếu bạn mới bắt đầu với FlutterArtist Filter, hãy xem ví dụ dưới đây trước thay vì ví dụ này.
- Download FlutterArtist Demo
No ADS
2. Employee03aShelf
In this example, the structure of the Shelf is quite simple, consisting only of a FilterModel and a Block:
employee03a_shelf.dart
class Employee03aShelf extends Shelf {
@override
ShelfStructure defineShelfStructure() {
return ShelfStructure(
filterModels: {
Employee03aFilterModel.filterName: Employee03aFilterModel(),
},
blocks: [
Employee03aBlock(
name: Employee03aBlock.blkName,
description: null,
config: BlockConfig(),
filterModelName: Employee03aFilterModel.filterName,
formModel: null,
childBlocks: [],
),
],
);
}
Employee03aFilterModel findEmployee03aFilterModel() {
return findFilterModel(Employee03aFilterModel.filterName)
as Employee03aFilterModel;
}
Employee03aBlock findEmployee03aBlock() {
return findBlock(Employee03aBlock.blkName) as Employee03aBlock;
}
}3. Employee03aFilterModel
First, let's look at how the filter structure is defined. Pay close attention to the "company" and "department" criteria:
Employee03aFilterModel.defineFilterModelStructure()
@override
FilterModelStructure defineFilterModelStructure() {
return FilterModelStructure(
criteriaStructure: FilterCriteriaStructure(
simpleCriterionDefs: [
SimpleFilterCriterionDef<String>(criterionBaseName: "searchText"),
],
multiOptCriterionDefs: [
// Multi Options Single Selection Criterion.
MultiOptFilterCriterionDef<CompanyInfo>.singleSelection(
criterionBaseName: "company",
fieldName: 'companyId',
toFieldValue: (CompanyInfo? rawValue) {
return SimpleVal.ofInt(rawValue?.id);
},
children: [
// Multi Options Single Selection Criterion.
MultiOptFilterCriterionDef<DepartmentInfo>.singleSelection(
criterionBaseName: "department",
fieldName: 'departmentId',
toFieldValue: (DepartmentInfo? rawValue) {
return SimpleVal.ofInt(rawValue?.id);
},
),
],
),
],
),
conditionStructure: FilterConditionStructure(
connector: FilterConnector.and,
conditionDefs: [
FilterConditionDef.simple(
tildeCriterionName: "searchText~",
operator: FilterOperator.containsIgnoreCase,
),
FilterConditionDef.simple(
tildeCriterionName: "company~",
operator: FilterOperator.equalTo,
),
FilterConditionDef.simple(
tildeCriterionName: "department~",
operator: FilterOperator.equalTo,
),
],
),
);
}No ADS

How to define a multi-option criterion where the user can select at most one option. singleSelection | ![]() |
How to define a multi-option criterion that allows the user to select multiple options. multiSelection | ![]() |
Based on the defined criteria and condition structures, three TildeFilterCriterion(s) are created. Each corresponds 1-1 with an input field on the FilterPanel.

FilterModel.performLoadMultiOptTildeCriterionXData()
This method is invoked sequentially for each MultiOptTildeFilterCriterion to fetch its data. The rule is that root criteria are loaded first, followed by child criteria, and finally the leaf criteria.
performLoadMultiOptTildeCriterionXData()
@override
Future<XData?> performLoadMultiOptTildeCriterionXData({
required String multiOptTildeCriterionName,
required String multiOptCriterionBaseName,
required Object? parentMultiOptTildeCriterionValue,
required SelectionType selectionType,
required EmptyFilterInput? filterInput,
}) async {
if (multiOptTildeCriterionName == "company~") {
// ApiResult<PageData<CompanyInfo>>
ApiResult<CompanyInfoPage> result = await _companyProvider.queryAll();
// IMPORTANT: Throw ApiError.
result.throwIfError();
// IMPORTANT: Generics should be declared explicitly.
var companyListXData = ListXData<int, CompanyInfo>.fromPageData(
pageData: result.data,
getItemId: (item) => item.id,
);
return companyListXData;
} else if (multiOptTildeCriterionName == "department~") {
//
// Because "department" is child of "company".
// So [parentMultiOptTildeCriterionValue] value is definitely non-null,
// this guaranteed by the library.
//
CompanyInfo companyInfo =
parentMultiOptTildeCriterionValue as CompanyInfo;
// ApiResult<PageData<DepartmentInfo>>
ApiResult<DepartmentInfoPage> result = await _departmentProvider
.queryAllByCompanyId(companyId: companyInfo.id);
// IMPORTANT: Throw ApiError if API Error.
result.throwIfError();
// IMPORTANT: Generics should be declared explicitly.
return ListXData<int, DepartmentInfo>.fromPageData(
pageData: result.data,
getItemId: (item) => item.id,
);
}
return null;
}No ADS
For root criterion "company~":
- Initially, this method is invoked to load data for "company~". The complete list of companies retrieved from the API is wrapped into a ListXData object, making it ready for display.
For the Child Criterion "department~":
- The key highlight is here: the "department~" criterion has a direct dependency on "company~". When a user selects a specific company, the system automatically triggers this method to fetch data for "department~".
Library Guarantee:
- You don't need to worry about null-checking the parent data. The library guarantees that if a criterion is defined as a "child," its data-loading method is only invoked once the "parent" criterion holds a valid value.
- FlutterArtist ApiResult
- FlutterArtist XData
No ADS
Debug Filter Model Viewer
The Debug Filter Model Viewer is a tool that allows you to view a list of criteria defined in the FilterModel and their values. This tool is accessed via the FilterControlBar or code.

- FlutterArtist Debug Filter Model Viewer
FilterModel.createNewFilterCriteria()

When a user performs a Block or Scalar query, this method is called to instantiate the FilterCriteria object (in this case, Employee03aFilterCriteria).
createNewFilterCriteria()
@override
Employee03aFilterCriteria createNewFilterCriteria({
required Map<String, dynamic> tildeCriteriaMap,
}) {
return Employee03aFilterCriteria(
searchText: tildeCriteriaMap["searchText~"], // String?
company: tildeCriteriaMap["company~"], // CompanyInfo?
department: tildeCriteriaMap["department~"], // DepartmentInfo?
);
}No ADS
Finally, the resulting FilterCriteria object is passed as a parameter to the performQuery() method of the Block or Scalar.
// Block:
Future<ApiResult<PageData<ITEM>?>> performQuery({
required Object? parentBlockCurrentItem,
required FILTER_CRITERIA filterCriteria,
required SortableCriteria sortableCriteria,
required Pageable pageable,
});
// Scalar:
Future<ApiResult<VALUE>> performQuery({
required Object? parentScalarValue,
required FILTER_CRITERIA filterCriteria,
});4. Employee03aBlock
Please pay attention to the performQuery() method within Employee03aBlock. The filterCriteria parameter is a Dart object containing the selected criteria values, which are used directly to query the data.
employee03a_block.dart (*)
class Employee03aBlock
extends
Block<
int, //
EmployeeInfo,
EmployeeData,
EmptyFilterInput,
Employee03aFilterCriteria,
EmptyFormInput,
EmptyAdditionalFormRelatedData
> {
static const blkName = "employee03a-block";
final employeeRestProvider = EmployeeRestProvider();
Employee03aBlock({
required super.name,
required super.description,
required super.config,
required super.filterModelName,
required super.formModel,
required super.childBlocks,
});
@override
Future<ApiResult<PageData<EmployeeInfo>?>> performQuery({
required Object? parentBlockCurrentItem,
required Employee03aFilterCriteria filterCriteria,
required SortableCriteria sortableCriteria,
required Pageable pageable,
}) async {
return await employeeRestProvider.query(
searchText: filterCriteria.searchText,
companyId: filterCriteria.company?.id,
departmentId: filterCriteria.department?.id,
pageable: pageable,
);
}
...
}
- FlutterArtist FilterCriteria
5. Employee03aFilterPanel

The Employee03aFilterPanel class is responsible for building the user interface, allowing users to input and select various filtering criteria.
employee03a_filter_panel.dart
class Employee03aFilterPanel extends FilterPanel<Employee03aFilterModel> {
const Employee03aFilterPanel({required super.filterModel, super.key});
@override
Widget buildContent(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
buildFilterBar(context),
Divider(),
FaFormBuilderTextField.topLabel(
name: "searchText~",
labelText: "Search Text",
maxLines: 1,
onChanged: (_) async {
await _queryEmployee03a();
},
),
SizedBox(height: 10),
FaFormBuilderDeselectableDropdown<CompanyInfo>.topLabel(
name: "company~",
labelText: "Company",
items: filterModel.getMultiOptTildeCriterionData("company~") ?? [],
getItemText: (item) => item.name,
onChanged: (_) async {
await _queryEmployee03a();
},
),
SizedBox(height: 10),
FaFormBuilderDeselectableDropdown<DepartmentInfo>.topLabel(
name: "department~",
labelText: "Department",
items: filterModel.getMultiOptTildeCriterionData("department~") ?? [],
getItemText: (item) => item.name,
onChanged: (_) async {
await _queryEmployee03a();
},
),
],
);
}
Future<void> _queryEmployee03a() async {
FlutterArtist.codeFlowLogger.addMethodCall(
ownerClassInstance: this,
currentStackTrace: StackTrace.current,
parameters: null,
);
//
await filterModel.queryAll();
}
}
- FlutterArtist Fa FormBuilder
No ADS
FlutterArtist
- Basic concepts in Flutter Artist
- FlutterArtist Block ex1
- FlutterArtist Filter Example
- FlutterArtist FilterModel MultiOptFilterCriterion ex1
- FlutterArtist FilterInput Example 1
- FlutterArtist Form ex1
- The idea of designing filter models in FlutterArtist
- FlutterArtist FormModel.patchFormFields() Ex1
- FlutterArtist BlockQuickItemUpdateAction Example
- FlutterArtist BlockNumberPagination Ex1
- FlutterArtist GridView Infinite Scroll Example
- FlutterArtist BlockQuickMultiItemCreationAction Example
- FlutterArtist ListView Infinite Scroll Pagination Example
- FlutterArtist Pagination
- FlutterArtist Sort DropdownSortPanel Example
- FlutterArtist Dio
- FlutterArtist BlockBackendAction Example
- FlutterArtist BackgroundWebDownloadAction Example
- FlutterArtist StorageBackendAction ex1
- FlutterArtist Block External Shelf Event ex1
- FlutterArtist Filter FormBuilderMultiDropDown Ex1
- FlutterArtist Master-detail Blocks ex1
- FlutterArtist Scalar ex1
- FlutterArtist Pagination Davi table Infinite Scroll Ex1
- FlutterArtist Filter Tree FormBuilderField ex1
- FlutterArtist Filter FormBuilderRadioGroup ex1
- FlutterArtist Form Parent-child MultiOptFormProp ex1
- FlutterArtist Manual Sorting ReorderableGridView Example
- FlutterArtist Manual Sorting ReorderableListView
- FlutterArtist Scalar External Shelf Event ex1
- FlutterArtist Code Flow Viewer
- FlutterArtist Log Viewer
- FlutterArtist config
- FlutterArtist StorageStructure
- FlutterArtist Debug Storage Viewer
- FlutterArtist DebugMenu
- FlutterArtist Debug UI Components Viewer
- FlutterArtist Debug Shelf Structure Viewer
- FlutterArtist Context Provider Views
- FlutterArtist FilterModelStructure ex1
- FlutterArtist FilterModelStructure ex2
- FlutterArtist FilterModelStructure ex3
- FlutterArtist Internal Shelf Event ex1
- FlutterArtist Deferring External Shelf Events (Ex1)
- FlutterArtist DropdownSortPanel
Show More



