chore: infinite query comments

This commit is contained in:
Kingkor Roy Tirtho
2023-06-10 11:45:17 +06:00
parent bc52de5303
commit c6e484637a
@@ -5,6 +5,7 @@ import 'package:collection/collection.dart';
import 'package:fl_query/fl_query.dart'; import 'package:fl_query/fl_query.dart';
import 'package:fl_query/src/core/mixins/retryer.dart'; import 'package:fl_query/src/core/mixins/retryer.dart';
import 'package:fl_query/src/core/mixins/validation.dart'; import 'package:fl_query/src/core/mixins/validation.dart';
import 'package:flutter/material.dart' hide Listener;
import 'package:hive_flutter/adapters.dart'; import 'package:hive_flutter/adapters.dart';
import 'package:mutex/mutex.dart'; import 'package:mutex/mutex.dart';
import 'package:state_notifier/state_notifier.dart'; import 'package:state_notifier/state_notifier.dart';
@@ -16,6 +17,7 @@ typedef InfiniteQueryNextPage<DataType, PageType> = PageType? Function(
DataType lastPageData, DataType lastPageData,
); );
/// A page holding all the data for a given page
class InfiniteQueryPage<DataType, ErrorType, PageType> with Invalidation { class InfiniteQueryPage<DataType, ErrorType, PageType> with Invalidation {
final PageType page; final PageType page;
final DataType? data; final DataType? data;
@@ -74,6 +76,8 @@ class InfiniteQueryState<DataType, ErrorType, PageType> {
} }
} }
/// Event fired with data and error by the [InfiniteQuery] fetchPage operation
@immutable
class PageEvent<T, P> { class PageEvent<T, P> {
final P page; final P page;
final T data; final T data;
@@ -86,6 +90,12 @@ class PageEvent<T, P> {
} }
} }
/// A specialized Query that can retrieve + hold paginated/segmented data
///
/// - [nextPage] provides the next page param for fetching
/// - [initialParam] provides the initial page param for fetching
///
/// Use the [InfiniteQueryBuilder] create and use an [InfiniteQuery]
class InfiniteQuery<DataType, ErrorType, PageType> class InfiniteQuery<DataType, ErrorType, PageType>
extends StateNotifier<InfiniteQueryState<DataType, ErrorType, PageType>> extends StateNotifier<InfiniteQueryState<DataType, ErrorType, PageType>>
with Retryer<DataType, ErrorType> { with Retryer<DataType, ErrorType> {
@@ -169,16 +179,31 @@ class InfiniteQuery<DataType, ErrorType, PageType>
CancelableOperation<void>? _operation; CancelableOperation<void>? _operation;
/// All the pages that has been successfully fetched
List<DataType> get pages => List<DataType> get pages =>
state.pages.map((e) => e.data).whereType<DataType>().toList(); state.pages.map((e) => e.data).whereType<DataType>().toList();
/// All the errors of pages that has failed to fetch
List<ErrorType> get errors => List<ErrorType> get errors =>
state.pages.map((e) => e.error).whereType<ErrorType>().toList(); state.pages.map((e) => e.error).whereType<ErrorType>().toList();
/// The last page that has been fetched
PageType get lastPage => state.lastPage; PageType get lastPage => state.lastPage;
/// Stream of data events
///
/// Subscribe to it to get notified when a page data is
/// fetched/refreshed/retried
Stream<PageEvent<DataType, PageType>> get dataStream => Stream<PageEvent<DataType, PageType>> get dataStream =>
_dataController.stream; _dataController.stream;
/// Stream of error events
///
/// Subscribe to it to get notified when a page has failed
Stream<PageEvent<ErrorType, PageType>> get errorStream => Stream<PageEvent<ErrorType, PageType>> get errorStream =>
_errorController.stream; _errorController.stream;
/// The next page param that will be used to fetch the next page
PageType? get getNextPage { PageType? get getNextPage {
final lastPageData = state.pages final lastPageData = state.pages
.firstWhereOrNull((e) => e.data is DataType && e.page == lastPage) .firstWhereOrNull((e) => e.data is DataType && e.page == lastPage)
@@ -262,6 +287,9 @@ class InfiniteQuery<DataType, ErrorType, PageType>
}); });
} }
/// Fetch current non-fetched page
///
/// If page is already done fetching. It'll simply returns the old data
Future<DataType?> fetch() async { Future<DataType?> fetch() async {
final lastPage = state.lastPage; final lastPage = state.lastPage;
if (_mutex.isLocked || hasPageData || hasPageError) if (_mutex.isLocked || hasPageData || hasPageError)
@@ -269,6 +297,9 @@ class InfiniteQuery<DataType, ErrorType, PageType>
return await _operate(lastPage).then((_) => state.pages.last.data); return await _operate(lastPage).then((_) => state.pages.last.data);
} }
/// Refresh a page that has or has not been fetched
///
/// - [page] The page to refresh. If null, it'll refresh the last page
Future<DataType?> refresh([PageType? page]) async { Future<DataType?> refresh([PageType? page]) async {
page ??= lastPage; page ??= lastPage;
if (_mutex.isLocked) if (_mutex.isLocked)
@@ -278,6 +309,7 @@ class InfiniteQuery<DataType, ErrorType, PageType>
}); });
} }
/// Refresh all the pages that has been fetched
Future<List<DataType>?> refreshAll() async { Future<List<DataType>?> refreshAll() async {
if (_mutex.isLocked) return pages; if (_mutex.isLocked) return pages;
return await Future.wait( return await Future.wait(
@@ -285,6 +317,9 @@ class InfiniteQuery<DataType, ErrorType, PageType>
).then((_) => pages); ).then((_) => pages);
} }
/// Fetch the next page
///
/// If there's no next page, it'll simply return the last page data
Future<DataType?> fetchNext() async { Future<DataType?> fetchNext() async {
final nextPage = getNextPage; final nextPage = getNextPage;
if (_mutex.isLocked || nextPage == null) { if (_mutex.isLocked || nextPage == null) {
@@ -295,6 +330,10 @@ class InfiniteQuery<DataType, ErrorType, PageType>
}); });
} }
/// Replace the currently [queryFn] with new [queryFn]
///
/// This is internally used to update queryFn when external data
/// has changed. Used by [InfiniteQueryBuilder] and [QueryClient]
void updateQueryFn(InfiniteQueryFn<DataType, PageType> queryFn) { void updateQueryFn(InfiniteQueryFn<DataType, PageType> queryFn) {
if (_queryFn == queryFn) return; if (_queryFn == queryFn) return;
_queryFn = queryFn; _queryFn = queryFn;
@@ -311,11 +350,18 @@ class InfiniteQuery<DataType, ErrorType, PageType>
} }
} }
/// Replace the currently [nextPage] with new [nextPage]
void updateNextPageFn(InfiniteQueryNextPage<DataType, PageType> nextPage) { void updateNextPageFn(InfiniteQueryNextPage<DataType, PageType> nextPage) {
if (_nextPage == nextPage) return; if (_nextPage == nextPage) return;
_nextPage = nextPage; _nextPage = nextPage;
} }
/// Manually set the data of a page
///
/// If there's no page with the given [page], it'll create a new page
/// and set the data as the given [data]
///
/// The new page will be added to the end of the list so it becomes [lastPage]
void setPageData(PageType page, DataType data) { void setPageData(PageType page, DataType data) {
final newPage = state.pages final newPage = state.pages
.firstWhere( .firstWhere(
@@ -336,6 +382,10 @@ class InfiniteQuery<DataType, ErrorType, PageType>
); );
} }
/// Reset all data, pages, error, events of this query
///
/// This will also remove every data of this [InfiniteQuery] from
/// persistent cache
Future<void> reset() async { Future<void> reset() async {
await _operation?.cancel(); await _operation?.cancel();
state = state.copyWith(pages: { state = state.copyWith(pages: {
@@ -353,17 +403,19 @@ class InfiniteQuery<DataType, ErrorType, PageType>
Listener<InfiniteQueryState<DataType, ErrorType, PageType>> listener, { Listener<InfiniteQueryState<DataType, ErrorType, PageType>> listener, {
bool fireImmediately = true, bool fireImmediately = true,
}) { }) {
if (refreshConfig.refreshOnMount) { Future.microtask(() async {
refreshAll(); if (refreshConfig.refreshOnMount) {
} else { await refreshAll();
Future.wait( } else {
state.pages.map((page) async { await Future.wait(
if (page.isStale) { state.pages.map((page) async {
return await refresh(page.page); if (page.isStale) {
} return await refresh(page.page);
}), }
); }),
} );
}
});
return super.addListener(listener, fireImmediately: fireImmediately); return super.addListener(listener, fireImmediately: fireImmediately);
} }