From 464c36c06f720c17105ba069424705df1cf7bf97 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Fri, 19 Jan 2024 04:59:00 +0530 Subject: [PATCH] chore: update doc comments, minor changes Signed-off-by: Sahil Kumar --- example/pubspec.lock | 26 ++++---- lib/src/load_state.dart | 17 +++--- lib/src/load_type.dart | 13 ++-- lib/src/page_fetcher.dart | 2 +- lib/src/paging_config.dart | 59 ++++++++++--------- lib/src/paging_source.dart | 25 ++++---- lib/src/super_pager.dart | 37 ++++++++---- pubspec.yaml | 2 +- ...ng_source.dart => fake_paging_source.dart} | 4 +- test/page_fetcher_test.dart | 18 +++--- test/paging_source_test.dart | 4 +- test/super_pager_test.dart | 18 +++--- 12 files changed, 119 insertions(+), 106 deletions(-) rename test/{mock_paging_source.dart => fake_paging_source.dart} (87%) diff --git a/example/pubspec.lock b/example/pubspec.lock index 38890b1..d1f4696 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -149,10 +149,10 @@ packages: dependency: transitive description: name: collection - sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687 + sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a url: "https://pub.dev" source: hosted - version: "1.17.2" + version: "1.18.0" convert: dependency: transitive description: @@ -351,10 +351,10 @@ packages: dependency: transitive description: name: meta - sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3" + sha256: a6e590c838b18133bb482a2745ad77c5bb7715fb0451209e1a7567d416678b8e url: "https://pub.dev" source: hosted - version: "1.9.1" + version: "1.10.0" mime: dependency: transitive description: @@ -452,18 +452,18 @@ packages: dependency: transitive description: name: stack_trace - sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5 + sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" url: "https://pub.dev" source: hosted - version: "1.11.0" + version: "1.11.1" stream_channel: dependency: transitive description: name: stream_channel - sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8" + sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.2" stream_transform: dependency: transitive description: @@ -499,10 +499,10 @@ packages: dependency: transitive description: name: test_api - sha256: "75760ffd7786fffdfb9597c35c5b27eaeec82be8edfb6d71d32651128ed7aab8" + sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b" url: "https://pub.dev" source: hosted - version: "0.6.0" + version: "0.6.1" timing: dependency: transitive description: @@ -539,10 +539,10 @@ packages: dependency: transitive description: name: web - sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10 + sha256: afe077240a270dcfd2aafe77602b4113645af95d0ad31128cc02bce5ac5d5152 url: "https://pub.dev" source: hosted - version: "0.1.4-beta" + version: "0.3.0" web_socket_channel: dependency: transitive description: @@ -560,5 +560,5 @@ packages: source: hosted version: "3.1.2" sdks: - dart: ">=3.1.0 <4.0.0" + dart: ">=3.2.0-194.0.dev <4.0.0" flutter: ">=3.13.0" diff --git a/lib/src/load_state.dart b/lib/src/load_state.dart index c151ff9..8be5d8d 100644 --- a/lib/src/load_state.dart +++ b/lib/src/load_state.dart @@ -4,16 +4,17 @@ part 'load_state.freezed.dart'; /// LoadState of a PagedList load - associated with a [LoadType] /// -/// [LoadState] of any [LoadType] may be observed for UI purposes by registering a listener via -/// [androidx.paging.PagingDataAdapter.addLoadStateListener] or -/// [androidx.paging.AsyncPagingDataDiffer.addLoadStateListener] +/// [LoadState] of any [LoadType] may be observed for UI purposes by registering +/// a listener via [SuperPager.addListener]. /// -/// [endOfPaginationReached] `false` if there is more data to load in the [LoadType] this -/// [LoadState] is associated with, `true` otherwise. This parameter informs [Pager] if it -/// should continue to make requests for additional data in this direction or if it should -/// halt as the end of the dataset has been reached. +/// [endOfPaginationReached] `false` if there is more data to load in the +/// [LoadType] this [LoadState] is associated with, `true` otherwise. +/// This parameter informs [Pager] if it should continue to make requests for +/// additional data in this direction or if it should halt as the end of the +/// dataset has been reached. /// -/// @see LoadType +/// also see: +/// * [LoadType] @freezed sealed class LoadState with _$LoadState { /// Indicates the [PagingData] is not currently loading, and no error currently observed. diff --git a/lib/src/load_type.dart b/lib/src/load_type.dart index 78e5eda..5e505c1 100644 --- a/lib/src/load_type.dart +++ b/lib/src/load_type.dart @@ -1,16 +1,15 @@ -/// Type of load a [PagingData] can trigger a [PagingSource] to perform. +/// Type of load a [SuperPager] can trigger a [PageFetcher] to perform. /// -/// [LoadState] of any [LoadType] may be observed for UI purposes by registering a listener via -/// [androidx.paging.PagingDataAdapter.addLoadStateListener] or -/// [androidx.paging.AsyncPagingDataDiffer.addLoadStateListener]. +/// [LoadState] of any [LoadType] may be observed for UI purposes by registering +/// a listener via [SuperPager.addListener]. enum LoadType { - /// [PagingData] content being refreshed, which can be a result of refresh + /// [SuperPager] content being refreshed, which can be a result of refresh /// that may contain content updates, or the initial load. refresh, - /// Load at the start of a [PagingData]. + /// Load at the start of a [SuperPager]. prepend, - /// Load at the end of a [PagingData]. + /// Load at the end of a [SuperPager]. append, } diff --git a/lib/src/page_fetcher.dart b/lib/src/page_fetcher.dart index edd3dcd..e4341da 100644 --- a/lib/src/page_fetcher.dart +++ b/lib/src/page_fetcher.dart @@ -206,7 +206,7 @@ class PageFetcher extends ValueNotifier> { // Never drop below 2 pages as this can cause UI flickering with certain configs and it's // much more important to protect against this behaviour over respecting a config where // maxSize is set unusually (probably incorrectly) strict. - if (value.pages.length <= 2) return 0; + if (value.pages.length <= 3) return 0; if (value.pages.itemCount <= maxSize) return 0; diff --git a/lib/src/paging_config.dart b/lib/src/paging_config.dart index a2375e4..a0a276b 100644 --- a/lib/src/paging_config.dart +++ b/lib/src/paging_config.dart @@ -23,19 +23,18 @@ class PagingConfig { /// /// Should be several times the number of visible items onscreen. /// - /// Configuring your page size depends on how your data is being loaded and used. Smaller - /// page sizes improve memory usage, latency, and avoid GC churn. Larger pages generally - /// improve loading throughput, to a point (avoid loading more than 2MB from SQLite at - /// once, since it incurs extra cost). + /// Configuring your page size depends on how your data is being loaded and + /// used. Smaller page sizes improve memory usage, latency, and avoid + /// GC churn. Larger pages generally improve loading throughput. /// - /// If you're loading data for very large, social-media style cards that take up most of - /// a screen, and your database isn't a bottleneck, 10-20 may make sense. If you're - /// displaying dozens of items in a tiled grid, which can present items during a scroll - /// much more quickly, consider closer to 100. + /// If you're loading data for very large, social-media style cards that take + /// up most of a screen, and your database isn't a bottleneck, 10-20 may make + /// sense. If you're displaying dozens of items in a tiled grid, which can + /// present items during a scroll much more quickly, consider closer to 100. /// - /// Note: [pageSize] is used to inform [PagingSource.LoadParams.loadSize], but is not enforced. - /// A [PagingSource] may completely ignore this value and still return a valid - /// [Page][PagingSource.LoadResult.Page]. + /// Note: [pageSize] is used to inform [LoadParams.loadSize], but is not + /// enforced. A [PagingSource] may completely ignore this value and still + /// return a valid [Page]. final int pageSize; /// Prefetch index defines how far from the edge of loaded content an access must be to @@ -50,33 +49,35 @@ class PagingConfig { /// end of list while scrolling. final int prefetchIndex; - /// Defines requested load size for initial load from [PagingSource], typically larger than - /// [pageSize], so on first load data there's a large enough range of content loaded to cover - /// small scrolls. + /// Defines requested load size for initial load from [PagingSource], + /// typically larger than [pageSize], so on first load data there's a large + /// enough range of content loaded to cover small scrolls. /// - /// Note: [initialLoadSize] is used to inform [PagingSource.LoadParams.loadSize], but is not - /// enforced. A [PagingSource] may completely ignore this value and still return a valid initial - /// [Page][PagingSource.LoadResult.Page]. + /// Note: [initialLoadSize] is used to inform [LoadParams.loadSize], but is + /// not enforced. A [PagingSource] may completely ignore this value and still + /// return a valid initial [Page]. final int initialLoadSize; - /// Defines the maximum number of items that may be loaded into [PagingData] before pages should - /// be dropped. + /// Defines the maximum number of items that may be loaded into [PagingData] + /// before pages should be dropped. /// /// If set to null (Default), pages will never be dropped. /// - /// This can be used to cap the number of items kept in memory by dropping pages. This value is - /// typically many pages so old pages are cached in case the user scrolls back. + /// This can be used to cap the number of items kept in memory by dropping + /// pages. This value is typically many pages so old pages are cached in case + /// the user scrolls back. /// - /// This value must be at least two times the [prefetchIndex] plus the [pageSize]). This - /// constraint prevent loads from being continuously fetched and discarded due to prefetching. + /// This value must be at least three times the [pageSize]. This constraint + /// prevent loads from being continuously fetched and discarded due to + /// prefetching. + /// + /// [maxSize] is best effort, not a guarantee. In practice, if [maxSize] is + /// many times [pageSize], the number of items held by [PagingData] will not + /// grow above this number. /// - /// [maxSize] is best effort, not a guarantee. In practice, if [maxSize] is many times - /// [pageSize], the number of items held by [PagingData] will not grow above this number. /// Exceptions are made as necessary to guarantee: - /// * Pages are never dropped until there are more than two pages loaded. Note that - /// a [PagingSource] may not be held strictly to [requested pageSize][PagingConfig.pageSize], so + /// * Pages are never dropped until there are more than two pages loaded. + /// Note that a [PagingSource] may not be held strictly to [pageSize], so /// two pages may be larger than expected. - /// * Pages are never dropped if they are within a prefetch window (defined to be - /// `pageSize + (2 * prefetchDistance)`) of the most recent load. final int? maxSize; } diff --git a/lib/src/paging_source.dart b/lib/src/paging_source.dart index 0383795..275b6f7 100644 --- a/lib/src/paging_source.dart +++ b/lib/src/paging_source.dart @@ -6,15 +6,16 @@ import 'preconditions.dart'; part 'paging_source.freezed.dart'; -/// Base class for an abstraction of pageable static data from some source, where loading pages -/// of data is typically an expensive operation. Some examples of common [PagingSource]s might be -/// from network or from a database. +/// Base class for an abstraction of pageable static data from some source, +/// where loading pages of data is typically an expensive operation. Some +/// examples of common [PagingSource]s might be from network or from a database. /// -/// An instance of a [PagingSource] is used to load pages of data for an instance of [PagingData]. +/// An instance of a [PagingSource] is used to load pages of data for an +/// instance of [PagingState]. /// -/// A [PagingData] can grow as it loads more data, but the data loaded cannot be updated. If the -/// underlying data set is modified, a new [PagingSource] / [PagingData] pair must be created to -/// represent an updated snapshot of the data. +/// A [PagingState] can grow as it loads more data, but the data loaded cannot +/// be updated. If the underlying data set is modified, a new [PagingSource] +/// must be created to represent an updated snapshot of the data. /// /// ### Loading Pages /// @@ -55,18 +56,16 @@ abstract mixin class PagingSource { /// Loading API for [PagingSource]. /// - /// Implement this method to trigger your async load (e.g. from database or network). + /// Implement this method to trigger your async load (e.g. from database or + /// network). Future> load(LoadParams params); - - @override - String toString() => describeIdentity(this); } /// Params for a load request on a [PagingSource] from [PagingSource.load]. @freezed sealed class LoadParams with _$LoadParams { - /// Params for an initial load request on a [PagingSource] from [PagingSource.load] or a - /// refresh triggered by [invalidate]. + /// Params for an initial load request on a [PagingSource] from + /// [PagingSource.load] or a refresh triggered by [invalidate]. const factory LoadParams.refresh({ /// Key for the page to be loaded. /// diff --git a/lib/src/super_pager.dart b/lib/src/super_pager.dart index 28370ff..3b40cac 100644 --- a/lib/src/super_pager.dart +++ b/lib/src/super_pager.dart @@ -45,6 +45,17 @@ class SuperPager _pageFetcher.addListener(_onPageFetcherStateChange); } + @visibleForTesting + SuperPager.custom({ + required PageFetcherFactory pageFetcherFactory, + PagingState initialState = const PagingState(), + }) : _notifier = ValueNotifier(initialState), + _pageFetcherFactory = pageFetcherFactory { + // Create a new page fetcher and listen to its state changes. + _pageFetcher = _pageFetcherFactory.call(initialState); + _pageFetcher.addListener(_onPageFetcherStateChange); + } + // Called whenever the page fetcher's state changes. void _onPageFetcherStateChange() { _notifier.value = _pageFetcher.value; @@ -72,25 +83,27 @@ class SuperPager } /// Load data from the [PagingSource] represented by this [SuperPager]. - Future load(LoadType loadType) => _pageFetcher.load(loadType); + Future load(LoadType loadType) { + return _pageFetcher.load(loadType); + } - /// Retry any failed load requests that would result in a [LoadState.Error] update to this - /// [PagingDataDiffer]. + /// Retry any failed load requests that would result in a [LoadState.error] + /// update to this [PagingState]. /// - /// Unlike [refresh], this does not invalidate [PagingSource], it only retries failed loads - /// within the same generation of [PagingData]. + /// Unlike [refresh], this does not invalidate [PageFetcher], it only retries + /// failed loads within the same generation of [PageFetcher]. /// - /// [LoadState.Error] can be generated from two types of load requests: - /// * [PagingSource.load] returning [PagingSource.LoadResult.Error] - /// * [RemoteMediator.load] returning [RemoteMediator.MediatorResult.Error] + /// [LoadState.error] can be generated from types of load requests: + /// * [PagingSource.load] returning [LoadResult.error] Future retry() => _pageFetcher.retry(); - /// Refresh the data presented by this [PagingDataDiffer]. + /// Refresh the data presented by this [SuperPager]. /// - /// [refresh] triggers the creation of a new [PagingData] with a new instance of [PagingSource] - /// to represent an updated snapshot of the backing dataset. + /// [refresh] triggers the creation of a new [PagingState] with a new instance + /// of [PageFetcher] to represent an updated snapshot of the backing dataset. /// - /// Note: This API is intended for UI-driven refresh signals, such as swipe-to-refresh. + /// Note: This API is intended for UI-driven refresh signals, such as + /// swipe-to-refresh. Future refresh({bool resetPages = true}) { final current = _pageFetcher; diff --git a/pubspec.yaml b/pubspec.yaml index 7f3c970..16bd1cd 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -20,7 +20,7 @@ dev_dependencies: sdk: flutter flutter_lints: ^2.0.3 build_runner: ^2.4.6 - freezed: ^2.4.3 + freezed: ^2.4.5 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec diff --git a/test/mock_paging_source.dart b/test/fake_paging_source.dart similarity index 87% rename from test/mock_paging_source.dart rename to test/fake_paging_source.dart index 84d9f28..eafdcbe 100644 --- a/test/mock_paging_source.dart +++ b/test/fake_paging_source.dart @@ -1,7 +1,7 @@ import 'package:super_pager/super_pager.dart'; -class MockPagingSource extends PagingSource { - const MockPagingSource({this.totalPageCount = 100}); +class FakePagingSource extends PagingSource { + const FakePagingSource({this.totalPageCount = 100}); final int totalPageCount; diff --git a/test/page_fetcher_test.dart b/test/page_fetcher_test.dart index 0778a3e..834badf 100644 --- a/test/page_fetcher_test.dart +++ b/test/page_fetcher_test.dart @@ -3,7 +3,7 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:super_pager/src/page_fetcher.dart'; import 'package:super_pager/super_pager.dart'; -import 'mock_paging_source.dart'; +import 'fake_paging_source.dart'; const initialState = PagingState( pages: PagingList(bottom: [ @@ -18,7 +18,7 @@ void main() { test('PageFetcher should be initialized with the provided initialState', () { final pageFetcher = PageFetcher( config: const PagingConfig(pageSize: 20), - pagingSource: const MockPagingSource(), + pagingSource: const FakePagingSource(), initialState: initialState, ); @@ -28,7 +28,7 @@ void main() { test('PageFetcher should load data with load method', () async { final pageFetcher = PageFetcher( config: const PagingConfig(pageSize: 20), - pagingSource: const MockPagingSource(), + pagingSource: const FakePagingSource(), initialState: const PagingState(), ); @@ -72,7 +72,7 @@ void main() { test('PageFetcher should drop pages when necessary', () async { final pageFetcher = PageFetcher( config: const PagingConfig(pageSize: 20, maxSize: 80), - pagingSource: const MockPagingSource(), + pagingSource: const FakePagingSource(), initialState: const PagingState(), ); @@ -90,7 +90,7 @@ void main() { test('PageFetcher should be disposed correctly', () { final pageFetcher = PageFetcher( config: const PagingConfig(pageSize: 20), - pagingSource: const MockPagingSource(), + pagingSource: const FakePagingSource(), initialState: const PagingState(), ); @@ -104,7 +104,7 @@ void main() { () async { final pageFetcher = PageFetcher( config: const PagingConfig(pageSize: 20), - pagingSource: const MockPagingSource(), + pagingSource: const FakePagingSource(), initialState: const PagingState(), ); @@ -123,7 +123,7 @@ void main() { final pageFetcher = PageFetcher( initialKey: 3, config: const PagingConfig(pageSize: 20), - pagingSource: const MockPagingSource(), + pagingSource: const FakePagingSource(), initialState: const PagingState(), ); @@ -142,7 +142,7 @@ void main() { const initialKey = 2; final pageFetcher = PageFetcher( config: const PagingConfig(pageSize: 20), - pagingSource: const MockPagingSource(), + pagingSource: const FakePagingSource(), initialKey: initialKey, initialState: const PagingState(), ); @@ -157,7 +157,7 @@ void main() { test('PageFetcher should handle a refresh', () async { final pageFetcher = PageFetcher( config: const PagingConfig(pageSize: 20), - pagingSource: const MockPagingSource(), + pagingSource: const FakePagingSource(), initialState: const PagingState(), ); diff --git a/test/paging_source_test.dart b/test/paging_source_test.dart index e2d0058..a8a8f34 100644 --- a/test/paging_source_test.dart +++ b/test/paging_source_test.dart @@ -1,12 +1,12 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:super_pager/src/paging_source.dart'; -import 'mock_paging_source.dart'; +import 'fake_paging_source.dart'; void main() { group('PagingSource', () { test('load method returns a LoadResult', () async { - const pagingSource = MockPagingSource(); + const pagingSource = FakePagingSource(); const loadParams = LoadParams.refresh(key: null, loadSize: 10); diff --git a/test/super_pager_test.dart b/test/super_pager_test.dart index 5806e1b..0e1b49c 100644 --- a/test/super_pager_test.dart +++ b/test/super_pager_test.dart @@ -3,7 +3,7 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:super_pager/super_pager.dart'; -import 'mock_paging_source.dart'; +import 'fake_paging_source.dart'; const initialState = PagingState( pages: PagingList(bottom: [ @@ -19,7 +19,7 @@ void main() { // Test initialization with a custom initial state. final superPager = SuperPager( config: const PagingConfig(pageSize: 20), - pagingSource: const MockPagingSource(), + pagingSource: const FakePagingSource(), initialState: initialState, ); @@ -29,7 +29,7 @@ void main() { test('SuperPager should load data with load method', () async { final superPager = SuperPager( config: const PagingConfig(pageSize: 20), - pagingSource: const MockPagingSource(), + pagingSource: const FakePagingSource(), ); await superPager.load(LoadType.refresh); @@ -41,7 +41,7 @@ void main() { test('SuperPager should refresh data with refresh method', () async { final superPager = SuperPager( config: const PagingConfig(pageSize: 20), - pagingSource: const MockPagingSource(), + pagingSource: const FakePagingSource(), ); await superPager.load(LoadType.refresh); @@ -86,7 +86,7 @@ void main() { test('SuperPager should add and remove listeners correctly', () async { final superPager = SuperPager( config: const PagingConfig(pageSize: 20), - pagingSource: const MockPagingSource(), + pagingSource: const FakePagingSource(), ); int listenerCount = 0; @@ -112,7 +112,7 @@ void main() { test('SuperPager should dispose correctly', () { final superPager = SuperPager( config: const PagingConfig(pageSize: 20), - pagingSource: const MockPagingSource(), + pagingSource: const FakePagingSource(), ); superPager.dispose(); @@ -125,7 +125,7 @@ void main() { final superPager = SuperPager( config: const PagingConfig(pageSize: 20), // Ensure multiple pages - pagingSource: const MockPagingSource(), + pagingSource: const FakePagingSource(), ); await superPager.load(LoadType.refresh); @@ -145,7 +145,7 @@ void main() { final superPager = SuperPager( config: const PagingConfig(pageSize: 20), // Ensure multiple pages - pagingSource: const MockPagingSource(), + pagingSource: const FakePagingSource(), ); await superPager.load(LoadType.refresh); @@ -164,7 +164,7 @@ void main() { test('SuperPager should correctly handle concurrency', () async { final superPager = SuperPager( config: const PagingConfig(pageSize: 20), - pagingSource: const MockPagingSource(), + pagingSource: const FakePagingSource(), ); // Initial load.