appcraft_list_loading_flutter 0.2.0
appcraft_list_loading_flutter: ^0.2.0 copied to clipboard
Dispatcher for paginated list loading with offset/cursor support, debounced search and pluggable cancellation strategies.
appcraft_list_loading_flutter #
A single-purpose Flutter package for paginated list loading. It provides the
ACDispatcher, which encapsulates the loading lifecycle
(reload / loadMore / cancel / dispose), reusable parsers for plain List<T>
and DTO responses, parameter mixins for offset- and cursor-based pagination,
and ready-to-use strategies for debounced search and load cancellation.
Suitable for any list with pagination, a search field and pull-to-refresh —
without imposing any specific state-management library (it extends
ChangeNotifier).
Features #
- Offset pagination via
ACDefaultDispatcherandACOffsetParamsMixin. - Cursor pagination via a custom
cursorfield on your params class +ACCustomDispatcher+ any DTO with theACResultmixin. Usedispatcher.lastResult?.<your_cursor_field>to feed the nextloadMore. - DTO responses with explicit
hasMoreviaACCustomDispatcher+ACResultParser. - Debounced search with
minLengthviaACDebouncedSearchStrategy. - Cancellation strategies: the
ACCancelStrategycontract and a readyACOperationCancelStrategyimplementation on top ofpackage:async. - Integration with
ChangeNotifier— subscribe viaListenableBuilder,AnimatedBuilderoraddListener. - Reusable parsers:
ACParser,ACDefaultParser,ACResultParser.
Installation #
flutter pub add appcraft_list_loading_flutter
Usage #
1. Basic — ACDefaultDispatcher #
The simplest scenario: the loader returns a plain List<T>, offset-based
pagination, no search. hasMore is computed by the parser as
result.length >= params.limit.
import 'package:appcraft_list_loading_flutter/appcraft_list_loading_flutter.dart';
final class UserListParams
with ACParamsMixin, ACOffsetParamsMixin {
const UserListParams({this.offset, this.limit, this.query});
@override
final int? offset;
@override
final int? limit;
@override
final String? query;
}
final dispatcher = ACDefaultDispatcher<UserListParams, User>();
await dispatcher.reload(
params: const UserListParams(offset: 0, limit: 20),
load: (p) => api.fetchUsers(offset: p.offset, limit: p.limit),
);
// Load the next page:
await dispatcher.loadMore(
params: UserListParams(offset: dispatcher.items.length, limit: 20),
load: (p) => api.fetchUsers(offset: p.offset, limit: p.limit),
);
2. DTO with ACResult — ACCustomDispatcher #
If the backend returns a DTO with an explicit hasMore flag (or cursor),
the DTO mixes in ACResult<T> and the dispatcher will read
items and hasMore from it automatically.
import 'package:appcraft_list_loading_flutter/appcraft_list_loading_flutter.dart';
final class UserPage with ACResult<User> {
const UserPage({required this.items, required this.hasMore, this.nextCursor});
@override
final List<User> items;
@override
final bool hasMore;
final String? nextCursor;
}
final class UserCursorParams with ACParamsMixin {
const UserCursorParams({this.limit, this.cursor, this.query});
@override
final int? limit;
final String? cursor;
@override
final String? query;
}
final dispatcher =
ACCustomDispatcher<UserCursorParams, UserPage, User>();
await dispatcher.reload(
params: const UserCursorParams(limit: 20),
load: (p) => api.fetchUsersPage(cursor: p.cursor, limit: p.limit),
);
// Carry the next-page cursor through the dispatcher's lastResult getter:
await dispatcher.loadMore(
params: UserCursorParams(
limit: 20,
cursor: dispatcher.lastResult?.nextCursor,
),
load: (p) => api.fetchUsersPage(cursor: p.cursor, limit: p.limit),
);
3. Debounced search — ACDebouncedSearchStrategy #
The search strategy applies only in reload: for a query shorter than
minLength items are cleared, for a changed query loading starts after
debounce. In loadMore the search strategy is ignored.
import 'package:appcraft_list_loading_flutter/appcraft_list_loading_flutter.dart';
final dispatcher = ACDefaultDispatcher<UserListParams, User>(
searchStrategy: ACDebouncedSearchStrategy(
debounce: const Duration(milliseconds: 400),
minLength: 2,
),
);
// Every text change triggers a reload — the strategy will collapse
// frequent calls into a single one.
void onQueryChanged(String query) {
dispatcher.reload(
params: UserListParams(offset: 0, limit: 20, query: query),
load: (p) => api.searchUsers(query: p.query, offset: p.offset, limit: p.limit),
);
}
4. Custom cancel strategy — ACCancelStrategy #
If you need to integrate with your own cancellation system (for example a
Dio CancelToken), implement ACCancelStrategy and pass an instance to
reload / loadMore via the cancelStrategy parameter.
import 'package:appcraft_list_loading_flutter/appcraft_list_loading_flutter.dart';
import 'package:dio/dio.dart';
final class DioCancelStrategy implements ACCancelStrategy {
DioCancelStrategy() : _token = CancelToken();
final CancelToken _token;
bool _completed = false;
CancelToken get token => _token;
@override
Future<T?> run<T>(Future<T> future) async {
try {
final result = await future;
_completed = true;
return result;
} on DioException catch (e) {
if (CancelToken.isCancel(e)) return null;
rethrow;
}
}
@override
Future<void> cancel() async {
if (!_completed && !_token.isCancelled) _token.cancel();
}
@override
bool get isActive => !_completed && !_token.isCancelled;
}
await dispatcher.reload(
params: const UserListParams(offset: 0, limit: 20),
load: (p) => api.fetchUsers(offset: p.offset, limit: p.limit),
cancelStrategy: DioCancelStrategy(),
);
Extending the API #
All public concrete classes in appcraft_list_loading_flutter are open for
both extends and implements. This lets you customize loading, search,
parsing or cancellation behavior without copying the source.
Open classes:
ACDefaultDispatcherACCustomDispatcherACDefaultParserACResultParserACDebouncedSearchStrategyACOperationCancelStrategy
(The abstract classes ACDispatcher, ACParser,
ACSearchStrategy, ACCancelStrategy and the mixins were already open in
prior versions.)
Example: extending the default dispatcher #
class LoggingDispatcher<P extends ACOffsetParamsMixin, T>
extends ACDefaultDispatcher<P, T> {
LoggingDispatcher({super.searchStrategy});
@override
void notifyListeners() {
print('items: ${items.length}, isLoading: $isLoading');
super.notifyListeners();
}
}
When extending, respect the parent contract documented in the corresponding
class' API docs. In particular, ACDefaultDispatcher extends
ChangeNotifier — overrides of dispose() must call super.dispose().
API Reference #
ACDispatcher<P, R, T>— the core dispatcher withreload,loadMore,cancelanddisposemethods, plusitems,isLoading,hasMore, andlastResultgetters.lastResultexposes the rawRreturned by the most recent successful load (useful for cursor pagination or DTO metadata).ACDefaultDispatcher<P, T>— facade for offset pagination with a plainList<T>response.ACCustomDispatcher<P, R, T>— facade for DTOs that mix inACResult.ACParser<P, R, T>— strategy interface for parsing the loader result.ACDefaultParser<P, T>— parser implementation forList<T>.ACResultParser<P, R, T>— parser implementation for DTOs withACResult.ACResult<T>— DTO contract mixin (items,hasMore).ACParamsMixin— base parameters mixin (limit,query).ACOffsetParamsMixin— offset pagination mixin (offset).ACSearchStrategy— search strategy contract (schedule,cancel,dispose).ACDebouncedSearchStrategy— search strategy implementation with debounce andminLength.ACCancelStrategy— cancellation strategy contract (run,cancel,isActive).ACOperationCancelStrategy— cancellation implementation on top ofCancelableOperationfrompackage:async.
Detailed documentation is available in the dartdoc on pub.dev.
Example #
A complete working example is available in the example/
folder. It demonstrates offset pagination, debounced search,
pull-to-refresh and infinite scroll on a single screen.
To run it:
cd example
flutter pub get
flutter run
License #
MIT — see LICENSE.