The idea of designing filter models in FlutterArtist
In FlutterArtist, FilterModel is a class that allows you to define criteria and conditional structures for data filtering. The data for these criteria is also stored and managed within the FilterModel, which can be displayed on the UI via the FilterPanel.
Essentially, FilterModel is designed generically to create complex filter models with multiple nested conditions through a Field-Based JSON structure.
Field-Based JSON
{
"connector": "AND",
"conditions": [
{
"connector": "OR",
"conditions": [
{
"field": "searchText",
"value": "vinfast",
"operator": "containsIgnoreCase"
},
{
"field": "searchText",
"value": "honda",
"operator": "containsIgnoreCase"
},
]
},
{
"field": "price",
"value": 100,
"operator": "greaterThan"
},
{
"field": "price",
"value": 200,
"operator": "lessThan"
}
]
}1. FilterModel
First, let's examine the definition of the FilterModel class, an abstract class with two Generics parameters.
abstract class FilterModel<
FILTER_INPUT extends FilterInput,
FILTER_CRITERIA extends FilterCriteria
> FILTER_INPUT | An input that provides hints to automatically specify values for criteria instead of user manual operations on the FilterPanel.
|
FILTER_CRITERIA | FilterCriteria is a smaller concept compared to FilterModel; it holds the values entered or selected by the user for each criterion. FilterCriteria can export a Field-Based JSON with a conditional structure to be sent to the server for data filtering. Field-Based JSON In FlutterArtist, the aforementioned JSON is also used to check whether the filter criteria have changed after user actions on the FilterPanel. |
No ADS
2. TildeFilterCriterion
"Tilde Criterion" is a special criterion in FlutterArtist. This term was introduced to solve technical issues related to the filter model and may be non-existent or unfamiliar compared to popular libraries.
To understand "Tilde Criterion", consider a scenario: You want to filter products within a price range. On the UI, you design two input fields for MIN and MAX values. These two fields share the same prefix "price" and have the suffixes "~min" and "~max" respectively.

Consequently, the Tilde-Based JSON accurately describes what the user sees on the UI because each TildeFilterCriterion has a 1-1 correspondence with an input field.
Tilde-Based JSON
{
"connector": "AND",
"conditions": [
{
"tildeCriterionName": "category~",
"value": "CategoryInfo(2, Electric Motorbike)",
"operator": "equalTo"
},
{
"tildeCriterionName": "price~min",
"value": 1000,
"operator": "greaterThan"
},
{
"tildeCriterionName": "price~max",
"value": 20000,
"operator": "lessThan"
}
]
}No ADS
Criteria-Based JSON is a simplified conditional structure of Tilde-Based JSON where tildeCriterionName(s) are replaced by criterionName(s), while the logical values remain unchanged.
Criteria-Based JSON
{
"connector": "AND",
"conditions": [
{
"criterionName": "category",
"value": "CategoryInfo(2, Electric Motorbike)",
"operator": "equalTo"
},
{
"criterionName": "price",
"value": 1000,
"operator": "greaterThan"
},
{
"criterionName": "price",
"value": 20000,
"operator": "lessThan"
}
]
}In FlutterArtist, each FilterCriterion can have one or more TildeFilterCriterion(s). TildeFilterCriterion(s) belonging to the same FilterCriterion share the same name prefix.

Finally, to send data to the server, we need to "flatten" the JSON by converting complex data types (such as CategoryInfo) into simple values (int, double, bool, String) via the toFieldValue() method in MultiOptFilterCriterionDef.
Field-Based JSON
{
"connector": "AND",
"conditions": [
{
"field": "categoryId",
"operator": "equalTo",
"value": 2
},
{
"field": "price",
"operator": "greaterThan",
"value": 1000
},
{
"field": "price",
"operator": "lessThan",
"value": 20000
}
]
}3. registerFilterModelStructure()
defineFilterModelStructure() is a crucial abstract method of FilterModel, allowing you to define criteria, their data types, and the condition structure.
FilterModelStructure defineFilterModelStructure();First, let's look at the filter interface mentioned above.

Define the filter structure using the defineFilterModelStructure() method:
defineFilterModelStructure() (Demo77a)
@override
FilterModelStructure defineFilterModelStructure() {
return FilterModelStructure(
criteriaStructure: FilterCriteriaStructure(
simpleCriterionDefs: [
SimpleFilterCriterionDef<double>(criterionBaseName: "price"),
],
multiOptCriterionDefs: [
// Multi Options Single Selection Criterion.
MultiOptFilterCriterionDef<CategoryInfo>.singleSelection(
criterionBaseName: "category",
fieldName: 'categoryId',
toFieldValue: (CategoryInfo? rawValue) {
return SimpleVal.ofInt(rawValue?.id);
},
),
],
),
conditionStructure: FilterConditionStructure(
connector: FilterConnector.and,
conditionDefs: [
FilterConditionDef.simple(
tildeCriterionName: "category~",
operator: FilterOperator.equalTo,
),
FilterConditionDef.simple(
tildeCriterionName: "price~min",
operator: FilterOperator.greaterThan,
),
FilterConditionDef.simple(
tildeCriterionName: "price~max",
operator: FilterOperator.lessThan,
),
],
),
);
}Using the toFieldValue() function to convert complex data types to simple ones is the "secret" of this design. It ensures that the FilterModel can work with high-level objects (ensuring Type Safety), while the server only receives primitive data types to optimize performance.
No ADS
toFieldValue()
MultiOptFilterCriterionDef<CategoryInfo>.singleSelection(
criterionBaseName: "category",
fieldName: 'categoryId',
toFieldValue: (CategoryInfo? rawValue) {
return SimpleVal.ofInt(rawValue?.id);
},
),Next, let's examine a more complex structure, illustrating how FlutterArtist handles nested condition groups.

In this example, we use FilterConditionDef.group to create an "OR" logic for two different Tilde Criterion(s) that both belong to the same root Criterion - album.
defineFilterModelStructure() (Demo56a)
@override
FilterModelStructure defineFilterModelStructure() {
return FilterModelStructure(
criteriaStructure: FilterCriteriaStructure(
simpleCriterionDefs: [
SimpleFilterCriterionDef<String>(criterionBaseName: "searchText"),
],
multiOptCriterionDefs: [
// Multi Options Single Selection Criterion.
MultiOptFilterCriterionDef<AlbumInfo>.singleSelection(
criterionBaseName: "album",
fieldName: 'albumId',
toFieldValue: (AlbumInfo? rawValue) {
return SimpleVal.ofInt(rawValue?.id);
},
),
],
),
conditionStructure: FilterConditionStructure(
connector: FilterConnector.and,
conditionDefs: [
FilterConditionDef.simple(
tildeCriterionName: "searchText~",
operator: FilterOperator.containsIgnoreCase,
),
FilterConditionDef.group(
groupName: "G2",
connector: FilterConnector.or,
conditionDefs: [
FilterConditionDef.simple(
tildeCriterionName: "album~1",
operator: FilterOperator.equalTo,
),
FilterConditionDef.simple(
tildeCriterionName: "album~2",
operator: FilterOperator.equalTo,
),
],
),
],
),
);
}The resulting output across the JSON transformation layers is as follows:
Reflects the G2 group structure with distinct identifying tildeCriterionName(s).
Tilde-Based JSON
{
"connector": "AND",
"conditions": [
{
"tildeCriterionName": "searchText~",
"value": "love",
"operator": "containsIgnoreCase"
},
{
"connector": "OR",
"conditions": [
{
"tildeCriterionName": "album~1",
"value": "AlbumInfo(1, Favorite English Songs)",
"operator": "equalTo"
},
{
"tildeCriterionName": "album~2",
"value": "AlbumInfo(2, Uncategorized)",
"operator": "equalTo"
}
]
}
]
}All tildeCriterionName(s) are standardized to the same criterionName, album.
Criteria-Based JSON
{
"connector": "AND",
"conditions": [
{
"criterionName": "searchText",
"value": "love",
"operator": "containsIgnoreCase"
},
{
"connector": "OR",
"conditions": [
{
"criterionName": "album",
"value": "AlbumInfo(1, Favorite English Songs)",
"operator": "equalTo"
},
{
"criterionName": "album",
"value": "AlbumInfo(2, Uncategorized)",
"operator": "equalTo"
}
]
}
]
}The final result with data flattened into albumId and primitive values (int), ready for SQL WHERE clauses or API queries.
Field-Based JSON
{
"connector": "AND",
"conditions": [
{
"field": "searchText",
"operator": "containsIgnoreCase",
"value": "love"
},
{
"connector": "OR",
"conditions": [
{
"field": "albumId",
"operator": "equalTo",
"value": 1
},
{
"field": "albumId",
"operator": "equalTo",
"value": 2
}
]
}
]
}No ADS
If you want more advanced examples of FilterModelStructure, please read the articles and examples below:
- Flutter FilterModelStructure (***)
- FlutterArtist FilterModelStructure ex2 (***)
Debug Filter Model Viewer
The Debug Filter Model Viewer is a tool that allows you to examine the state and data of a FilterModel. This tool can be opened via a button on the FilterControlBar or through code.

- FlutterArtist Debug Filter Model Viewer
4. Summary of JSON Layering Benefits
Layered design, progressing from Tilde-Based to Criteria-Based and finally Field-Based, offers three core benefits for large-scale application development:
Decoupling UI and Logic | Developers can modify the UI (adding/removing ~ input fields) without changing the Database or API structure. Tilde-Based JSON acts as the perfect buffer zone. |
Type Safety | The entire processing on Mobile occurs with high-level objects (e.g., AlbumInfo). Flattening data via toFieldValue() only happens at the final step, minimizing errors from incorrect formatting. |
Backend Query Optimization | The server only receives Field-Based JSON with primitive types and standard operators. This allows the Backend to easily convert JSON into SQL commands or NoSQL queries without additional complex logic. |
No ADS
FlutterArtist
- Basic concepts in Flutter Artist
- FlutterArtist Block ex1
- FlutterArtist Filter Example
- FlutterArtist Form ex1
- The idea of designing filter models in FlutterArtist
- FlutterArtist FormModel.patchFormFields() Ex1
- FlutterArtist BlockQuickItemUpdateAction Ex1
- FlutterArtist BlockNumberPagination Ex1
- FlutterArtist BlockQuickMultiItemCreationAction Ex1
- FlutterArtist ListView Infinite Scroll Pagination Example
- FlutterArtist Pagination
- FlutterArtist Sort DropdownSortPanel Example
- FlutterArtist Dio
- FlutterArtist BackgroundWebDownloadAction ex1
- FlutterArtist Block External Shelf Event ex1
- FlutterArtist Master-detail Blocks ex1
- FlutterArtist Scalar ex1
- FlutterArtist Pagination Davi table Infinite Scroll Ex1
- FlutterArtist Filter FormBuilderField 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 ex3
- FlutterArtist Internal Shelf Event ex1
- FlutterArtist Deferring External Shelf Events (Ex1)
- FlutterArtist DropdownSortPanel
Show More