Build a sort-plus-filter state machine for a FlutterFlow e-commerce app. Page State tracks the active sort field, sort direction, and applied filters. A Backend Query composes the Firestore query dynamically based on these states, handling Firestore's composite index requirements. When Firestore cannot combine an inequality filter with a different orderBy field, a Custom Function fetches a broader result set and sorts client-side as a fallback.
Building Dynamic Sort and Filter for E-Commerce in FlutterFlow
E-commerce apps need both sorting (by price, rating, date) and filtering (by category, price range, availability). The challenge is that Firestore has strict rules about combining inequality filters with orderBy on different fields. This tutorial builds a state machine that composes queries when possible and falls back to client-side sorting when Firestore limitations prevent server-side execution.
Prerequisites
- A FlutterFlow project with Firestore configured
- A products collection with fields: name, price, rating, category, createdAt, inStock
- Understanding of Firestore query limitations and composite indexes
- Familiarity with Page State and Custom Functions in FlutterFlow
Step-by-step guide
Set up Page State variables for sort and filter tracking
Set up Page State variables for sort and filter tracking
Create the following Page State variables on your product listing page: sortField (String, default 'createdAt'), sortDirection (String, default 'desc'), filterCategory (String, default empty for no filter), filterMinPrice (double, default 0), filterMaxPrice (double, default 99999), filterInStock (Boolean, default false). These variables form the state machine that drives the query composition. Every sort or filter UI change updates one of these variables, which triggers a query refresh.
Expected result: Page State contains all sort and filter parameters ready to drive dynamic Firestore queries.
Build the sort selector with direction toggle
Build the sort selector with direction toggle
Add a Row at the top of the product listing page. Include a DropDown with sort options: Newest (createdAt desc), Price Low to High (price asc), Price High to Low (price desc), Highest Rated (rating desc), Most Popular (viewCount desc). When a sort option is selected, update both sortField and sortDirection in Page State. Add an IconButton next to the DropDown showing an up or down arrow based on sortDirection. Tapping it toggles between asc and desc and updates the Page State.
Expected result: Users select a sort criterion from the dropdown and toggle ascending or descending order with the arrow button.
Build the filter panel with category, price range, and stock toggle
Build the filter panel with category, price range, and stock toggle
Add a filter section using ChoiceChips for category selection (All, Electronics, Clothing, Home, Sports). When a chip is selected, update filterCategory. Add a RangeSlider for price range that updates filterMinPrice and filterMaxPrice. Add a Switch labeled In Stock Only that updates filterInStock. Display the active filters below the controls as colored removable chips: each chip shows the filter value with an X button that resets that specific filter to its default value.
Expected result: Users apply category, price range, and stock filters with visual feedback showing active filter chips.
Compose the Firestore query dynamically from Page State
Compose the Firestore query dynamically from Page State
Create a Backend Query on the products collection. Build the query based on Page State: if filterCategory is not empty, add whereEqualTo on category. If filterInStock is true, add whereEqualTo inStock == true. For price range, add where price >= filterMinPrice and price <= filterMaxPrice. Add orderBy using sortField and sortDirection. Important: when using a price range filter (inequality on price), Firestore requires the first orderBy to be on the price field. If the user wants to sort by rating while filtering by price, the query will fail without a composite index or a workaround.
Expected result: The product grid updates dynamically as users change sort and filter settings via Firestore query recomposition.
Handle Firestore limitations with client-side sort fallback
Handle Firestore limitations with client-side sort fallback
Firestore requires the first orderBy field to match the inequality filter field. When a user filters by price range but sorts by rating, this conflicts. Detect this case: if sortField is not 'price' AND price range filter is active, fetch products with the price filter and orderBy price, then apply a Custom Function that re-sorts the results by the desired sortField client-side. The Custom Function accepts the product list and sortField and sortDirection, then returns the re-sorted list. Display the Custom Function output instead of the raw query results.
1// Custom Function: sortProducts2List<dynamic> sortProducts(3 List<dynamic> products,4 String sortField,5 String sortDirection,6) {7 final sorted = List<dynamic>.from(products);8 sorted.sort((a, b) {9 final aVal = a[sortField] ?? 0;10 final bVal = b[sortField] ?? 0;11 if (aVal is num && bVal is num) {12 return sortDirection == 'asc'13 ? aVal.compareTo(bVal)14 : bVal.compareTo(aVal);15 }16 return 0;17 });18 return sorted;19}Expected result: All sort and filter combinations work, with Firestore handling what it can and client-side sorting covering the rest.
Display results count and add a clear all filters button
Display results count and add a clear all filters button
Below the filter chips, show a Text displaying the results count: '{n} products found'. Add a Clear All button that resets all Page State filter variables to their defaults and sortField to 'createdAt' desc. This refreshes the query to show all products in default order. Also add empty state handling: when the query returns zero results, show a Container with an illustration and text 'No products match your filters. Try adjusting your criteria.' with a Clear Filters button.
Expected result: Users see how many products match their criteria and can clear all filters with one tap.
Complete working example
1FIRESTORE DATA MODEL:2 products/{productId}3 name: String4 price: double5 rating: double (0-5)6 category: String7 createdAt: Timestamp8 inStock: Boolean9 viewCount: int10 imageUrl: String1112 COMPOSITE INDEXES (create in Firebase Console):13 products: category ASC, price ASC14 products: category ASC, rating DESC15 products: category ASC, createdAt DESC16 products: inStock ASC, price ASC1718PAGE STATE:19 sortField: String = 'createdAt'20 sortDirection: String = 'desc'21 filterCategory: String = '' (empty = all)22 filterMinPrice: double = 023 filterMaxPrice: double = 9999924 filterInStock: Boolean = false2526QUERY COMPOSITION LOGIC:27 1. Start with products collection reference28 2. If filterCategory != '' → .where('category', isEqualTo: filterCategory)29 3. If filterInStock == true → .where('inStock', isEqualTo: true)30 4. If filterMinPrice > 0 OR filterMaxPrice < 9999931 → .where('price', isGreaterThanOrEqualTo: filterMinPrice)32 → .where('price', isLessThanOrEqualTo: filterMaxPrice)33 → MUST orderBy 'price' first (Firestore rule)34 → If sortField != 'price' → fetch with price order, then sort client-side35 5. Else → .orderBy(sortField, descending: sortDirection == 'desc')3637WIDGET TREE:38 Column39 ├── Row (Sort controls)40 │ ├── DropDown (sort options: Newest, Price ↑, Price ↓, Rating, Popular)41 │ └── IconButton (asc/desc toggle arrow)42 ├── SizedBox (8)43 ├── Row (Filter controls)44 │ ├── ChoiceChips (category: All, Electronics, Clothing, Home, Sports)45 │ ├── RangeSlider (price range)46 │ └── Switch (In Stock Only)47 ├── Row (Active filters)48 │ ├── Wrap (filter chips with X remove)49 │ ├── Spacer50 │ ├── Text ('{n} products')51 │ └── TextButton 'Clear All'52 ├── SizedBox (16)53 └── GridView (2 columns, sorted/filtered products)54 └── Container (product card)55 Column56 ├── Image (productUrl)57 ├── Text (name)58 ├── Text (price, bold)59 └── Row (rating stars + category chip)Common mistakes when implementing Dynamic Sorting and Filtering in a FlutterFlow E-Commerce App
Why it's a problem: Ordering by rating while filtering by price range without handling the Firestore constraint
How to avoid: Detect the conflict: when an inequality filter is active on one field and sort is on a different field, fetch with the required orderBy and re-sort client-side using a Custom Function.
Why it's a problem: Not creating composite indexes for common filter-sort combinations
How to avoid: Create composite indexes in Firebase Console for every filter-sort combination you support. Check the Firestore error log for missing index suggestions.
Why it's a problem: Fetching all products and filtering entirely on the client
How to avoid: Use Firestore server-side filtering for the heavy lifting (category, price range, stock) and only fall back to client-side for the sort when Firestore constraints prevent it.
Best practices
- Use Firestore server-side filtering for category and equality filters to reduce data transfer
- Fall back to client-side sorting only when Firestore orderBy constraints conflict with filters
- Create composite indexes for common filter and sort combinations
- Show active filters as removable chips so users can see and modify their criteria
- Display the results count to give users feedback on filter effectiveness
- Add an empty state with a clear filters call-to-action when no products match
- Limit client-side fallback to fetching at most 100 pre-filtered results for performance
Still stuck?
Copy one of these prompts to get a personalized, step-by-step explanation.
I want to build a dynamic sorting and filtering system for a FlutterFlow e-commerce app. Products can be sorted by price, rating, or date, and filtered by category, price range, and stock status. Firestore has constraints on combining inequality filters with orderBy on different fields. Show me how to compose queries dynamically and fall back to client-side sorting when needed.
Create a product listing page with a sort dropdown and direction toggle at the top, category filter chips, a price range slider, an in-stock toggle, active filter chips with remove buttons, and a product grid below.
Frequently asked questions
How do I add text search to the sort and filter system?
Add a search TextField that updates a Page State searchText. Use Firestore prefix query (name >= searchText) for basic search, or integrate Algolia via FlutterFlow's built-in Algolia Backend Query for full-text search with typo tolerance.
Can I save user filter preferences for next visit?
Store the filter state in App State with persistence enabled. On page load, initialize Page State from the persisted App State values so users return to their preferred filter settings.
How many composite indexes do I need?
One per unique filter-sort combination. With 5 categories, price filter, and 4 sort options, you may need 10 to 15 indexes. Firestore allows up to 200 composite indexes per database.
What if I need to filter by multiple categories at once?
Use whereIn for up to 10 categories or whereArrayContainsAny if products have a categories array field. For more than 10, batch into multiple queries and merge results.
How do I handle pagination with dynamic sort and filters?
Use Firestore query cursors with startAfterDocument. When the sort or filter changes, reset the cursor and load from the beginning. FlutterFlow's Infinite Scroll handles this when properly configured.
Can RapidDev build advanced filtering for my e-commerce app?
Yes. RapidDev can build sophisticated filter systems with multi-faceted search, Algolia integration, saved filter presets, and optimized Firestore query composition.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation