Fold pagination +1 trick into API methods, return typed records

Remove dead _pageSize from BackendAPI and centralise the
"fetch perPage+1, detect hasNextPage, trim" logic into
listDevices and listNotifications. Both methods now accept
page/perPage and return ({items, hasNextPage}) records,
eliminating the duplicated boilerplate at each call site.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
rzuasti
2026-05-31 17:40:18 -04:00
co-authored by Claude Sonnet 4.6
parent f75d1ce313
commit 8be65f3ffa
3 changed files with 34 additions and 29 deletions
+5 -5
View File
@@ -89,21 +89,21 @@ class _DeviceListState extends State<DeviceList> with RouteAware {
if (_filter == DeviceFilter.newDevices) isRegistered = false;
if (_filter == DeviceFilter.registered) isRegistered = true;
final results = await BackendAPI.instance.listDevices(
final result = await BackendAPI.instance.listDevices(
isRegistered: isRegistered,
owner: _ownerController.text.isEmpty ? null : _ownerController.text,
deviceType: _typeFilter,
sortBy: _sortColumn.apiName,
sortAscending: _sortAscending,
offset: page * _pageSize,
limit: _pageSize + 1,
page: page,
perPage: _pageSize,
cancelToken: token,
);
if (!mounted || token != _fetchToken) return;
setState(() {
_currentPage = page;
_hasNextPage = results.length > _pageSize;
_devices = _hasNextPage ? results.take(_pageSize).toList() : results;
_hasNextPage = result.hasNextPage;
_devices = result.items;
_isLoading = false;
});
} catch (e) {
+5 -5
View File
@@ -115,17 +115,17 @@ class _NotificationsListState extends State<NotificationsList>
_error = null;
});
try {
final results = await BackendAPI.instance.listNotifications(
final result = await BackendAPI.instance.listNotifications(
_filter.isNew,
page * _pageSize,
limit: _pageSize + 1,
page: page,
perPage: _pageSize,
cancelToken: token,
);
if (!mounted || token != _fetchToken) return;
setState(() {
_currentPage = page;
_hasNextPage = results.length > _pageSize;
_items = _hasNextPage ? results.take(_pageSize).toList() : results;
_hasNextPage = result.hasNextPage;
_items = result.items;
_isLoading = false;
});
} catch (e) {
+24 -19
View File
@@ -82,8 +82,6 @@ String dioErrorToUserMessage(Object error) {
class BackendAPI {
static final BackendAPI _instance = BackendAPI._internal();
static const _pageSize = 5;
static BackendAPI get instance => _instance;
BackendAPI._internal() {
@@ -162,14 +160,14 @@ class BackendAPI {
return Device.fromJson(response.data as Map<String, dynamic>);
}
Future<List<Device>> listDevices({
Future<({List<Device> items, bool hasNextPage})> listDevices({
bool? isRegistered,
String? owner,
DeviceType? deviceType,
String? sortBy,
bool? sortAscending,
int? offset,
int? limit,
int page = 0,
int perPage = 10,
CancelToken? cancelToken,
}) async {
final params = <String, dynamic>{};
@@ -184,10 +182,8 @@ class BackendAPI {
if (sortAscending != null) {
params['sort_order'] = sortAscending ? 'asc' : 'desc';
}
if (offset != null) {
params['page_offset'] = offset;
params['page_limit'] = limit ?? _pageSize;
}
params['page_offset'] = page * perPage;
params['page_limit'] = perPage + 1;
final response = await _dio.get(
'/devices',
@@ -195,9 +191,14 @@ class BackendAPI {
cancelToken: cancelToken,
);
return (response.data as List)
final results = (response.data as List)
.map((item) => Device.fromJson(item as Map<String, dynamic>))
.toList();
final hasNextPage = results.length > perPage;
return (
items: hasNextPage ? results.take(perPage).toList() : results,
hasNextPage: hasNextPage,
);
}
Future<void> registerDevice(
@@ -284,25 +285,29 @@ class BackendAPI {
return MdnsScannerStatus.fromJson(response.data as Map<String, dynamic>);
}
Future<List<Notification>> listNotifications(
bool? isNew,
int offset, {
int? limit,
Future<({List<Notification> items, bool hasNextPage})> listNotifications(
bool? isNew, {
int page = 0,
int perPage = 5,
CancelToken? cancelToken,
}) async {
final response = await _dio.get(
'/notifications',
queryParameters: {
'is_new': isNew ?? '',
'page_offset': offset,
'page_limit': limit ?? _pageSize,
'page_offset': page * perPage,
'page_limit': perPage + 1,
},
cancelToken: cancelToken,
);
final list = response.data as List<dynamic>;
return List<Notification>.from(
list.map((item) => Notification.fromJson(item)),
final results = (response.data as List<dynamic>)
.map((item) => Notification.fromJson(item))
.toList();
final hasNextPage = results.length > perPage;
return (
items: hasNextPage ? results.take(perPage).toList() : results,
hasNextPage: hasNextPage,
);
}
}