FlutterArtist Filter Tree FormBuilderField ex1
As we know, in FlutterArtist, all data fields used in FormView or FilterPanel must comply with the standards of the flutter_form_builder library. Among them, FormBuilderDropdown is a familiar and common data field, which can be used in FilterPanel to allow users to select an option from a list of options.
Let's see, you can select a Company in a FormBuilderDropdown like this: | ![]() |
And selecting a Company on a tree widget like this. The question is: which method can provide a better user experience? | ![]() |
Basically, the tree widget above is not an input field, nor does it comply with flutter_form_builder standards. However, if you wrap the tree widget in a FormBuilderField, both of these conditions are met.

In this example, we will wrap the tree widget in a FormBuilderField so it can be used as an input field on the FilterPanel, replacing the familiar FormBuilderDropdown.
2. CompanyTreeView
To create a TreeView, you can use the animated_tree_view library.
pubspect.yaml (*)
dependencies:
animated_tree_view: ^2.3.0CompanyTreeView in this example creates a tree structure based on the CompanyTree parameter. The full source code for this example is available on FlutterArtist Demo.
CompanyTreeView
class CompanyTreeView extends StatefulWidget {
final CompanyTree? companyTree;
final CompanyTreeItem? selectedCompanyTreeItem;
final Function(CompanyTreeItem companyTreeItem) onSelectCompanyTreeItem;
final EdgeInsets padding;
const CompanyTreeView({
super.key,
required this.companyTree,
this.padding = EdgeInsets.zero,
required this.selectedCompanyTreeItem,
required this.onSelectCompanyTreeItem,
})
...
}The full source code for CompanyTreeView is available in "FlutterArtist Demo".
- Download FlutterArtist Demo
No ADS
CompanyTree / CompanyTreeItem (Model)
@JsonSerializable()
class CompanyTree {
@JsonKey(name: 'rootCompanyTreeItems', defaultValue: [])
List<CompanyTreeItem> rootCompanyTreeItems = [];
..
}
@JsonSerializable()
class CompanyTreeItem implements Identifiable<int> {
@override
@JsonKey(name: 'id')
late int id;
@JsonKey(name: 'name')
late String name;
@JsonKey(name: 'children', defaultValue: [])
List<CompanyTreeItem> children = [];
@JsonKey(name: 'imagePath')
String? imagePath;
....
} 3. Employee04aShelf
The structure of Employee04aShelf is simple, consisting of only a FilterModel and a Block:
employee04a_shelf.dart
class Employee04aShelf extends Shelf {
@override
ShelfStructure defineShelfStructure() {
return ShelfStructure(
filterModels: {
Employee04aFilterModel.filterName: Employee04aFilterModel(),
},
blocks: [
Employee04aBlock(
name: Employee04aBlock.blkName,
description: null,
config: BlockConfig(),
filterModelName: Employee04aFilterModel.filterName,
formModel: null,
childBlocks: [],
),
],
);
}
Employee04aBlock findEmployee04aBlock() {
return findBlock(Employee04aBlock.blkName) as Employee04aBlock;
}
}4. Employee04aFilterModel
FilterModel structure:
defineFilterModelStructure()
@override
FilterModelStructure defineFilterModelStructure() {
return FilterModelStructure(
criteriaStructure: FilterCriteriaStructure(
simpleCriterionDefs: [
SimpleFilterCriterionDef<String>(criterionBaseName: "searchText"),
],
multiOptCriterionDefs: [
MultiOptFilterCriterionDef<CompanyTreeItem>.singleSelection(
criterionBaseName: "company",
fieldName: 'companyId',
toFieldValue: (CompanyTreeItem? 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,
),
],
),
);
}performLoadMultiOptTildeCriterionXData()
The performLoadMultiOptTildeCriterionXData() method is used to load data for a MultiOptTildeFilterCriterion. In this case, the expected data you might be thinking of is a CompanyTree. However, this method requires returning an XData, so we need to wrap the CompanyTree within a TreeXData.
No ADS
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<CompanyTree> result = await _companyTreeProvider
.getCompanyTree();
// Throw ApiError
result.throwIfError();
//
CompanyTree? companyTree = result.data;
if (companyTree == null) {
return null;
}
return TreeXData<int, CompanyTreeItem, CompanyTree>(
treeData: companyTree,
getItemId: (item) => item.id,
getRootTreeItems: () {
return companyTree.rootCompanyTreeItems;
},
getChildren: (CompanyTreeItem item) {
return item.children;
},
addOrphanTreeItem : (CompanyTreeItem item) {
companyTree.addOrphanCompany(item);
},
removeOrphanTreeItem: (CompanyTreeItem item) {
companyTree.removeOrphanCompany(item);
},
);
}
//
return null;
}In this example, XData is not a ListXData as seen in typical FormBuilderDropdown cases. Here, XData is a TreeXData, which is particularly useful in a FormModel because it effectively handles "orphan data" (items that were previously selected but no longer exist in the current list). TreeXData simply appends these orphan TreeItem(s) as a final root Node of the tree to ensure display data integrity.
Note: The reason why the FilterModel.performLoadMultiOptTildeCriterionXData() method is designed to return an XData is explained in detail in the following articles:
- FlutterArtist XData
5. Employee04aFilterPanel
CompanyTreeView is just a regular widget; it is not a data field and does not comply with flutter_form_builderstandards. To transform it into an input field, you need to wrap it in a FormBuilderField like this:

No ADS
FormBuilderField(CompanyTreeView)
FormBuilderField<CompanyTreeItem>(
name: "company~",
builder: (FormFieldState<CompanyTreeItem> field) {
return CompanyTreeView(
companyTree: filterModel.getMultiOptTildeCriterionData(
"company~",
),
selectedCompanyTreeItem: field.value,
onSelectCompanyTreeItem: (CompanyTreeItem item) {
// IMPORTANT (If you wrap a CompanyTreeView in a FormBuilderField):
field.didChange(item);
//
_onSelectCompanyTreeItem(item);
},
);
},
),When the user changes the CompanyTreeItem on the CompanyTreeView, you must call field.didChange(item)within the onSelectCompanyTreeItem() method. This is the way to synchronize the state between CompanyTreeView and FormBuilderField.
Below is the full source code for Employee04aFilterPanel:
employee04a_filter_panel.dart
class Employee04aFilterPanel extends FilterPanel<Employee04aFilterModel> {
const Employee04aFilterPanel({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 filterModel.queryAll();
},
),
SizedBox(height: 10),
Expanded(
child: FormBuilderField<CompanyTreeItem>(
name: "company~",
builder: (FormFieldState<CompanyTreeItem> field) {
return CompanyTreeView(
companyTree: filterModel.getMultiOptTildeCriterionData(
"company~",
),
selectedCompanyTreeItem: field.value,
onSelectCompanyTreeItem: (CompanyTreeItem item) {
// IMPORTANT (If you wrap a CompanyTreeView in a FormBuilderField):
field.didChange(item);
//
_onSelectCompanyTreeItem(item);
},
);
},
),
),
],
);
}
Future<void> _onSelectCompanyTreeItem(CompanyTreeItem treeItem) async {
FlutterArtist.codeFlowLogger.addMethodCall(
ownerClassInstance: this,
currentStackTrace: StackTrace.current,
parameters: {"treeItem": treeItem},
);
//
await filterModel.queryAll();
}
}Using FormBuilderField allows you to create complex input fields and adhere to flutter_form_builder standards. Furthermore, this approach makes maintenance extremely easy. If you later want to replace animated_tree_view with another library or a custom solution, you only need to change the code inside the FormBuilderField builder without modifying any logic in the FilterModel or the defined Criterion(s).
6. Employee04aBlock
Employee04aBlock.performQuery()
@override
Future<ApiResult<PageData<EmployeeInfo>?>> performQuery({
required Object? parentBlockCurrentItem,
required Employee04aFilterCriteria filterCriteria,
required SortableCriteria sortableCriteria,
required Pageable pageable,
}) async {
return await employeeRestProvider.query(
companyId: filterCriteria.companyTreeItem?.id,
searchText: filterCriteria.searchText,
pageable: pageable,
);
}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




