mz_collection 0.0.1 copy "mz_collection: ^0.0.1" to clipboard
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.

pub package License: BSD codecov CI

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 #

  1. Controller owns data - CRUD operations go directly to controller
  2. Managers are optional - Use only what you need
  3. Key-based operations - update(key, fn), remove(key) for O(1) lookup
  4. Local/Remote transforms - Each filter/sort declares where it runs
  5. 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:

  1. Read the contribution guidelines
  2. Fork the repository
  3. Create a feature branch
  4. Write tests for new features
  5. Ensure all tests pass
  6. Submit a pull request

License #

This package is released under the BSD-3-Clause License.

Credits #

Developed and maintained by Pankaj Koirala.

Support #

Changelog #

See CHANGELOG.md for version history.

0
likes
150
points
60
downloads

Publisher

verified publisherkopan7.com

Weekly Downloads

Pure Dart collection state management for handling lists, grids, tables, trees and more. Includes filtering, sorting, grouping, selection, pagination, aggregation, and fuzzy search.

Repository (GitHub)
View/report issues
Contributing

Documentation

API reference

License

MIT (license)

Dependencies

meta

More

Packages that depend on mz_collection