mz_collection 0.0.1
mz_collection: ^0.0.1 copied to clipboard
Pure Dart collection state management for handling lists, grids, tables, trees and more. Includes filtering, sorting, grouping, selection, pagination, aggregation, and fuzzy search.
mz_collection #
Pure Dart collection state management for lists, tables, trees, and more. Includes filtering, sorting, grouping, selection, pagination, aggregation, and fuzzy search.
Features #
| Feature | Description |
|---|---|
| Filtering | Multi-filter support with AND/OR/NOT expressions, local and remote processing |
| Sorting | Multi-level sorting with ascending/descending order and custom comparators |
| Grouping | Single and multi-value grouping with nested hierarchies and aggregations |
| Selection | Multi-scope selection with single/multi modes and tri-state support |
| Pagination | Cursor and offset pagination with infinite scroll and bidirectional loading |
| Aggregation | Built-in count, sum, avg, min, max, first, last, and custom aggregators |
| Fuzzy Search | Levenshtein-based fuzzy matching with configurable scoring thresholds |
| Tree Structure | Hierarchical data representation with expand/collapse and lazy loading |
| Virtualization | SlotManager for efficient large list rendering with expand/collapse |
| State Serialization | Save/restore state for deep linking, URL query params, and persistence |
Installation #
Add to your pubspec.yaml:
dependencies:
mz_collection: ^0.0.1
Then run:
dart pub get
Quick Start #
Basic Collection #
import 'package:mz_collection/mz_collection.dart';
// Create a controller with your data type
final controller = CollectionController<Task>(
keyOf: (task) => task.id,
);
// Add data
controller.addAll(tasks);
// Access items
print('Total: ${controller.length}');
for (final task in controller.items) {
print(task.title);
}
// CRUD operations
controller.add(newTask);
controller.update('task-1', (t) => t.copyWith(done: true));
controller.remove('task-1');
Filtering #
// Define filters
final filter = FilterManager<Task>(
filters: [
Filter<Task, String>(
id: 'status',
test: (task, value) => task.status == value,
),
Filter<Task, String>(
id: 'priority',
test: (task, value) => task.priority == value,
),
],
);
// Create controller with filter
final controller = CollectionController<Task>(
keyOf: (task) => task.id,
filter: filter,
);
controller.addAll(tasks);
// Apply filters
filter['status']!.add('active'); // Show only active tasks
filter['priority']!.add('high'); // AND high priority
// Clear a filter
filter['status']!.clear();
Sorting #
// Define sort options
final sort = SortManager<Task>(
options: [
SortOption<Task, DateTime>(
id: 'createdAt',
sortIdentifier: (task) => task.createdAt,
),
SortOption<Task, String>(
id: 'title',
sortIdentifier: (task) => task.title,
),
SortOption<Task, int>(
id: 'priority',
sortIdentifier: (task) => task.priorityLevel,
),
],
);
// Create controller with sort
final controller = CollectionController<Task>(
keyOf: (task) => task.id,
sort: sort,
);
// Apply sorting
sort.setCurrent(sort['createdAt']!);
sort.setOrder(SortOrder.descending); // Newest first
Grouping #
// Define grouping options
final group = GroupManager<Task>(
options: [
GroupOption<Task, String>(
id: 'status',
valueBuilder: (task) => task.status,
labelBuilder: (status) => status.toUpperCase(),
),
],
);
// Create controller with grouping
final controller = CollectionController<Task>(
keyOf: (task) => task.id,
group: group,
);
controller.addAll(tasks);
// Access grouped data via tree structure
for (final groupNode in controller.root.children.values) {
print('${groupNode.id}: ${groupNode.length} tasks');
for (final task in groupNode.items) {
print(' - ${task.title}');
}
}
Selection #
final controller = CollectionController<Task>(
keyOf: (task) => task.id,
selection: SelectionManager(mode: SelectionMode.multi),
);
controller.addAll(tasks);
// Select items
controller.selection.select('task-1');
controller.selection.select('task-2');
// Check selection
if (controller.selection.isSelected('task-1')) {
print('Task 1 is selected');
}
// Get selected items
final selectedKeys = controller.selection.selectedKeys;
final selectedTasks = controller.getAll(selectedKeys.toList());
// Toggle selection
controller.selection.toggle('task-3');
// Clear selection
controller.selection.clear();
Pagination with Data Loader #
final controller = CollectionController<Task>(
keyOf: (task) => task.id,
dataLoader: (request) async {
// Fetch from your API
final response = await api.fetchTasks(
offset: request.token is OffsetToken
? (request.token as OffsetToken).offset
: 0,
limit: request.limit,
filters: request.filters,
sort: request.sort,
);
return PageResponse(
items: response.tasks,
nextToken: response.hasMore
? PageToken.offset(response.nextOffset)
: PageToken.end,
);
},
);
// Initial load
await controller.load();
// Load more (infinite scroll)
await controller.load(); // Automatically uses next token
// Force refresh
await controller.refresh();
// Check pagination state
if (controller.pagination.canLoad(PaginationEdge.trailing.id)) {
await controller.load();
}
State Serialization #
// Capture current state
final snapshot = controller.captureState();
// Convert to JSON for persistence
final json = snapshot.toJson();
await storage.save('collection_state', json);
// Or convert to URL query string
final queryString = snapshot.toQueryString();
// e.g., "filter.status=active&sort=createdAt&order=desc"
// Restore from JSON
final savedJson = await storage.load('collection_state');
controller.restoreState(CollectionSnapshot.fromJson(savedJson));
// Or restore from URL query string
controller.restoreState(CollectionSnapshot.fromQueryString(queryString));
Architecture #
CollectionController<T>
|
+-- FilterManager<T> (optional) - Multi-filter with AND/OR/NOT
+-- SortManager<T> (optional) - Multi-level sorting
+-- GroupManager<T> (optional) - Hierarchical grouping
+-- SelectionManager (built-in) - Selection tracking
+-- PaginationState (built-in) - Pagination tracking
|
+-- root: Node<T> (output) - Tree structure for display
|
+-- SlotManager<T> (optional) - Virtualized list rendering
Key Design Principles #
- Controller owns data - CRUD operations go directly to controller
- Managers are optional - Use only what you need
- Key-based operations -
update(key, fn),remove(key)for O(1) lookup - Local/Remote transforms - Each filter/sort declares where it runs
- Tree output - All data exposed as
Node<T>tree for flexible rendering
Documentation #
| Resource | Description |
|---|---|
| Architecture | Detailed architecture and design patterns |
| API Reference | Complete API documentation |
Features in Detail #
FilterManager #
Multi-filter support with flexible composition:
- Multiple filters with independent values
- AND/OR/NOT filter expressions
- Local, remote, or combined processing via
TransformSource - Search filter with fuzzy matching support
Use case: Filtering tables, lists, search results
SortManager #
Multi-level sorting with full control:
- Multiple sort options with priority ordering
- Ascending/descending toggle per option
- Custom comparators for complex types
- Local or remote sorting
Use case: Sortable tables, ordered lists
GroupManager #
Hierarchical grouping with aggregations:
- Single-value grouping (item in one group)
- Multi-value grouping (item in multiple groups)
- Nested multi-level grouping
- Group-level aggregations (count, sum, etc.)
Use case: Grouped lists, category views, pivot tables
SelectionManager #
Flexible selection tracking:
- Single and multi-selection modes
- Tri-state support for hierarchical selection
- Scope-based selection for grouped items
- Selection change notifications
Use case: Multi-select lists, bulk actions, tree selection
Pagination #
Cursor and offset pagination:
- Leading and trailing edge pagination
- Automatic "load more" detection
- Bidirectional loading for chat-style UIs
- Integration with remote data sources
Use case: Infinite scroll, paginated APIs, large datasets
Node Tree #
Hierarchical data structure:
- Flat or grouped item organization
- Expand/collapse state management
- Lazy loading of children
- Efficient tree traversal
Use case: File explorers, org charts, nested menus
SlotManager #
Virtualized rendering support:
- Flattens tree to indexed slots
- Handles expand/collapse efficiently
- O(1) index-to-node lookup
- Minimal memory footprint
Use case: Large lists, virtualized scroll views
Examples #
Example 1: Task Manager with Filtering and Grouping #
import 'package:mz_collection/mz_collection.dart';
class Task {
final String id;
final String title;
final String status;
final DateTime dueDate;
Task({
required this.id,
required this.title,
required this.status,
required this.dueDate,
});
}
void main() {
// Setup managers
final filter = FilterManager<Task>(
filters: [
Filter<Task, String>(
id: 'status',
test: (task, value) => task.status == value,
),
],
);
final sort = SortManager<Task>(
options: [
SortOption<Task, DateTime>(
id: 'dueDate',
sortIdentifier: (task) => task.dueDate,
),
],
);
final group = GroupManager<Task>(
options: [
GroupOption<Task, String>(
id: 'status',
valueBuilder: (task) => task.status,
),
],
);
// Create controller
final controller = CollectionController<Task>(
keyOf: (task) => task.id,
filter: filter,
sort: sort,
group: group,
);
// Add tasks
controller.addAll([
Task(id: '1', title: 'Review PR', status: 'todo', dueDate: DateTime.now()),
Task(id: '2', title: 'Fix bug', status: 'in_progress', dueDate: DateTime.now().add(Duration(days: 1))),
Task(id: '3', title: 'Write tests', status: 'todo', dueDate: DateTime.now().add(Duration(days: 2))),
]);
// Apply transformations
sort.setCurrent(sort['dueDate']!);
sort.setOrder(SortOrder.ascending);
// Print grouped results
for (final groupNode in controller.root.children.values) {
print('\n${groupNode.id.toUpperCase()}:');
for (final task in groupNode.items) {
print(' - ${task.title} (due: ${task.dueDate})');
}
}
// Cleanup
controller.dispose();
}
Example 2: Paginated API Integration #
import 'package:mz_collection/mz_collection.dart';
class User {
final String id;
final String name;
final String email;
User({required this.id, required this.name, required this.email});
}
class UserApi {
Future<({List<User> users, int nextOffset, bool hasMore})> fetchUsers({
required int offset,
required int limit,
}) async {
// Simulated API call
await Future.delayed(Duration(milliseconds: 500));
final users = List.generate(
limit,
(i) => User(
id: 'user-${offset + i}',
name: 'User ${offset + i}',
email: 'user${offset + i}@example.com',
),
);
return (
users: users,
nextOffset: offset + limit,
hasMore: offset + limit < 100,
);
}
}
void main() async {
final api = UserApi();
final controller = CollectionController<User>(
keyOf: (user) => user.id,
dataLoader: (request) async {
final offset = request.token is OffsetToken
? (request.token as OffsetToken).offset
: 0;
final result = await api.fetchUsers(
offset: offset,
limit: request.limit,
);
return PageResponse(
items: result.users,
nextToken: result.hasMore
? PageToken.offset(result.nextOffset)
: PageToken.end,
);
},
);
// Initial load
await controller.load();
print('Loaded ${controller.length} users');
// Load more
await controller.load();
print('Total: ${controller.length} users');
// Refresh (reload from beginning)
await controller.refresh();
print('After refresh: ${controller.length} users');
controller.dispose();
}
Testing #
mz_collection is fully tested with comprehensive test coverage:
- Unit tests for all managers and controllers
- Integration tests for complex scenarios
- Edge case coverage for pagination and state
Run tests:
dart test
Requirements #
- Dart SDK: ^3.5.0
Contributing #
Contributions are welcome! Please:
- Read the contribution guidelines
- Fork the repository
- Create a feature branch
- Write tests for new features
- Ensure all tests pass
- Submit a pull request
License #
This package is released under the BSD-3-Clause License.
Credits #
Developed and maintained by Pankaj Koirala.
Support #
- Issues: GitHub Issues
- Discussions: GitHub Discussions
- Repository: GitHub
Related Packages #
- mz_core - Flutter utilities for state management, logging, and more
- collection - Dart collections utilities
- fast_immutable_collections - Immutable collections
Changelog #
See CHANGELOG.md for version history.