Списки с пагинацией во Flutter

Для чего используется пагинация?

Под пагинацией понимается разделение большого объема данных на части (страницы). Если пользователь просматривает экран, с которым связано немало записей, то имеет смысл подгружать данные только по мере просмотра имеющихся. Не нужно запрашивать у сервера все данные, так как пользователь может и не дойти до конца экрана или веб-страницы. Иными словами, пагинация позволяет сократить объем запрашиваемых данных и ускорить время загрузки страницы. 

Запросы к серверу 

Для того чтобы реализовать пагинацию в приложении, очевидно, нужна поддержка со стороны сервера. В рамках проектной деятельности использовался API SpaceX, который поддерживает данную функцию. Есть возможность загружать данные по страницам, указав количество записей на каждой из них и номер нужной страницы, или загружать данные со смещением, задавая число записей для данного запроса. При интеграции данного API использовался второй вариант. 

Реализация в мобильном приложении

По заданию требовалось создать экран с карточками запусков ракет, на котором список пролистывается и работает с пагинацией. Для этого реализован репозиторий, который расширяет класс ChangeNotifier, то есть может уведомлять другие классы, подписанные на него, о том, что необходимо обновить UI. В репозитории имеется метод, который запрашивает у сервера записи, начиная с позиции, равной количеству уже загруженных элементов. Количество получаемых данных определяется константой и обычно полагается равной числу карточек на экране + 1.  Кроме того, если сервер уже не возвращает данные, то это значит, что все они загружены и помещены в кэш. После загрузки данных репозиторий уведомляет своих подписчиков с помощью notifyListeners, чтобы они обновили UI. Ниже приведен фрагмент кода репозитория.


const itemsPerPage = 5;
class LaunchRepository extends ChangeNotifier {
final List<Launch> pastItems = [];
var downloadedAll = false;
Future<void> fetchPagePastLaunch() async {
var result = await apiGateway.fetchPagePastLaunch(offset: pastItems.length, limit: itemsPerPage);
if (result?.length == 0) downloadedAll = true;
pastItems.addAll(result);
notifyListeners();
}
}

Тогда на экране, который должен отображать список, при его инициализации нужно запросить первую страницу данных. Далее следует использовать ListView.builder, в котором при построении последнего элемента необходимо запрашивать новые данные. Важно отметить, что делать это нужно только после того, как закончится построение текущих элементов, то есть в функции addPostFrameCallback. Кроме того, список должен быть обернут в Consumer, чтобы подписаться на репозиторий и перестраивать экран при поступлении новых данных.

Consumer<LaunchRepository>(
builder: (context, launchRepository, child) {
return ListView.builder(
itemCount: launchRepository.pastItems.length,
itemBuilder: (context, index) {
final isLast = launchRepository.pastItems.last == launchRepository.pastItems[index];
if (!launchRepository.downloadedAll && isLast) {
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
launchRepository.fetchPagePastLaunch();
});
}
return LaunchCard(model: launchRepository.pastItems[index]);
},
);
},
);
Таким образом, новые данные будут загружаться только по мере пролистывания экрана и при этом кэшироваться. Это позволяет избежать нагрузки как на сервер, так и на клиент.