A library providing a base for building controllable paginated views powered by nestjs-paginate
on the backend.
PaginationController
provides a comprehensive and reactive interface for building paginated queries. It can be configured withPaginateConfig
, either hardcoded or received from the server directly, to empower client-side validation.PaginateController
supports all features ofnestjs-paginate
exceptselect
, as it breaks the majority of DTOs in Dart, but it may change in future updates.PaginatedView
utilises a pagination controller and user-defined widget builders to create a highly customisable presentation of data received from the server.Paginated<TDto>
- a wrapper for the paginated DTO sent by thenestjs-paginate
which includesPaginatedMetadata
and a list ofTDto
.
To start using the package, install it using pub:
flutter pub add flutter_nestjs_paginate
This will install the latest compatible version of the package and you're ready to go.
Create a PaginateConfig
by hard-coding it or by fetching it from your server:
final configJson = await get<Map>('/paginate_config');
final config = PaginateConfig.fromJson(configJson);
// or
// all parameters are optional
const config = PaginateConfig(
defaultSortBy: {'name': SortOrder.asc},
defaultLimit: 10,
filterableColumns: {
'name': {Eq, Ilike},
'population': {Gt, Lt, Eq, Btw},
},
sortableColumns: {'name', 'population'},
);
To control the pagination, a PaginationController
is used.
Create it and supply the created config:
// in your state
final _controller = PaginationController(paginateConfig: config);
While the config parameter is optional, passing it enables filtering and sorting validation. If you want to omit config and disable validation, pass validateColumns: false
to the controller constructor. There are also
Then create a PaginatedView
:
// in build()
return PaginatedView(
// provide the controller. its updates will make the
controller: _controller,
// fetcher is used to make a paginated request to your server
// it must return a Paginated<YourDto> - more
// information below and in the source docs
fetcher: (context, QueryParams params) { ... },
// error builder will be called to visualise
// the error, if it occurs
errorBuilder: (context, Object error) => YourErrorWidget(error),
// loading indicator will be built while the data is fetched
loadingIndicator: (context) => YourProgressIndicator(),
// view builder visualizes a list of TModels
// received from the fetcher
viewBuilder: (context, Paginated<YourDto> data) {
return YourDataView(data.data);
},
// you can also provide listeners for the
// fetch start end finish events. they are called
// in post-frame callbacks, so you can call setState
// inside
onFetch: () { ... },
onLoaded: (Paginated<YourDto> result) { ... },
);
Now, the pagination can be controlled with the controller
:
// in a button callback
// fetch next page
_controller.page++;
// add a filter using the operator Btw
_controller.addFilter(
'date',
const Btw('2023-12-20', '2023-12-26'),
);
// perform multiple operations simultaneously
controller.silently(
notifyAfter: true,
(controller) => controller
..page = 1
..clearFilters()
..addFilter('amount', const Gt(1000))
..addSort('amount', SortOrder.desc),
);
An enum defining the order of sorting: either ascending or descending:
enum SortOrder {
asc,
desc
}
A family of classes representing the filters supported by nestjs-paginate
:
- $eq -
Eq
- $not -
Not
- $null -
Null
- $in -
In
- $gt, $gte -
Gt
- for $gte, provideorEquals: true
to the constructor - $lt, $lte -
Lt
- for $lte, provideorEquals: true
to the constructor - $btw -
Btw
- $ilike -
Ilike
- $sw -
Sw
- $contains -
Contains
This class provides the reactive interface to control the pagination.
It features fields with the names of query parameters accepted by nestjs-paginate
and can be configured with client-side validation with PaginateConfig
objects.
Pagination controllers support column validation, which is enabled by default - validateColumns
It can also be enforced to prevent unexpected behaviour in production (disabled by default) - strictValidation
- it would throw StateErrors with meaningful messages to help you find the problem.
When validation is enabled, it will use the values from the provided PaginateConfig
(and only if it is provided).
A class that duplicates the structure of PaginateConfig from nestjs-paginate, omitting database- and backend-specific values, can be passed to a PaginationController constructor. It can then be used by the developer and the controller to enable validation.
The fields it contains:
- Set<String>
sortableColumns
- the names of columns to be accepted by the controller inaddSort
. Default:{}
- no columns allowed for sorting. - Map<String, Set<Type>>
filterableColumns
- a map of column names accepted by the backend for filtering to sets of their allowedFilterOperator
types. Default:{}
- no columns allowed for filtering. - int
maxLimit
- the maximum limit configured on the server. Default:100
. - int
defaultLimit
- the limit value to be set by default. Default:20
. - Map<String, SortOrder>
defaultSortBy
- the default sort queries.
PaginateConfig
has a fromJson
factory which supports deserializing nestjs-paginate
config syntax directly.
Make sure not to send backend-specific values that you do not need in your Flutter application to avoid potential disclosure of system details.
Note that no columns are allowed for sorting and filtering by default, so you should consider providing the paginateConfig or setting validateColumns to false in your controller.
To control the pagination, use the following:
- get/set int
page
- the page of the paginated data to be requested. Default:1
. - get/set int
limit
- the maximum amount of entries per page. Default:20
. If set to a value larger thanPaginateConfig.maxLimit
, it will be clamped to themaxLimit
. - get/set Object?
search
- the search query to be sent. Default:null
. Note that the search object must be string-serializable in a meaningful way withtoString
for it to be correctly received by your server.
If any of these fields are changed, the controller will notify its listeners (unless changed inside the silently
function).
To control the filters, use the following members of the controller:
- get Map<String, Set<FilterOperator>>
filters
- an unmodifiable view of the filters currently applied.nestjs-paginate
supports multiple filters per single column. The map has the following structure:
{
'column': { FilterOperator, FilterOperator, ... },
...
}
addFilter(String field, FilterOperator operator)
- adds a filter by the given field with the givenFilterOperator
to the query. If the filter set is changed, notifies listeners.removeFilter(String field, [FilterOperator? operator])
- if the operator is not given, remove all filters for the given field. Otherwise, remove a filter by that operator from the field.clearFilters()
- removes all filters from the query. If the filter set is not empty, notifies listeners.
To control sorting, use the following members:
- get Map<String, SortOrder>
sorts
- an unmodifiable view of the sorts currently applied. The map has the following structure:
{
'column': SortOrder,
...
}
addSort(String field, SortOrder order)
- updates the sort query to include the sort in the given order by the given field. If the sort set is changed, notifies listeners.removeSort(String field)
- removes a sort query by the given field. If such a query is present, notifies listeners.clearFilters()
- removes all sorts from the query. If the sort set is not empty, notifies listeners.
By default, changing any fields of the controller will fire the notification and make the PaginatedView
call its fetcher. However, if you need to change multiple parameters simultaneously and don't need to notify listeners, you can use silently(Function(PaginationController) fn, {bool notifyAfter = false})
.
Refer to the docs in the source code for more information.
This is a generic wrapper for the responses received from paginated endpoints.
Matching the PaginateConfig
from nestjs-paginate
, it contains data
- a list of your DTOs, and meta
- PaginatedMetadata
.
It can be deserialize using Paginated.fromJson<TDto>(json, decoder)
, where decoder
is a function taking a Map<String, dynamic> and returning your DTO:
final json = await client.get<Map<String, dynamic>>('/paginated_collection');
final paginatedRes = Paginated.fromJson(json, YourDto.fromJson);
If you would like to contribute to this package, you can:
- file an issue or propose an improvement
- view the source code on GitHub
- create a pull request in the repository
All contributions are most welcome.