From 75b05f3dc88d0423590a3014a75f0995113d3524 Mon Sep 17 00:00:00 2001 From: Kingkor Roy Tirtho Date: Wed, 20 Mar 2024 23:22:48 +0600 Subject: [PATCH] feat: make many provider autoDispose after 5 minutes of no usage --- lib/pages/search/search.dart | 5 +- lib/provider/spotify/album/tracks.dart | 10 +- lib/provider/spotify/artist/albums.dart | 10 +- lib/provider/spotify/artist/artist.dart | 5 +- lib/provider/spotify/artist/related.dart | 6 +- lib/provider/spotify/artist/top_tracks.dart | 5 +- lib/provider/spotify/category/playlists.dart | 6 +- lib/provider/spotify/playlist/playlist.dart | 2 +- lib/provider/spotify/playlist/tracks.dart | 13 +- lib/provider/spotify/search/search.dart | 17 +- lib/provider/spotify/spotify.dart | 5 + lib/provider/spotify/tracks/track.dart | 5 +- lib/provider/spotify/utils/mixin.dart | 20 +- lib/provider/spotify/utils/provider.dart | 211 ------------------ .../spotify/utils/provider/cursor.dart | 56 +++++ .../spotify/utils/provider/cursor_family.dart | 113 ++++++++++ .../spotify/utils/provider/paginated.dart | 63 ++++++ .../utils/provider/paginated_family.dart | 113 ++++++++++ 18 files changed, 425 insertions(+), 240 deletions(-) create mode 100644 lib/provider/spotify/utils/provider/cursor.dart create mode 100644 lib/provider/spotify/utils/provider/cursor_family.dart create mode 100644 lib/provider/spotify/utils/provider/paginated.dart create mode 100644 lib/provider/spotify/utils/provider/paginated_family.dart diff --git a/lib/pages/search/search.dart b/lib/pages/search/search.dart index c14e8ff2..e666c9aa 100644 --- a/lib/pages/search/search.dart +++ b/lib/pages/search/search.dart @@ -28,15 +28,14 @@ class SearchPage extends HookConsumerWidget { @override Widget build(BuildContext context, ref) { final theme = Theme.of(context); - final controller = useTextEditingController(); + final searchTerm = ref.watch(searchTermStateProvider); + final controller = useTextEditingController(text: searchTerm); ref.watch(AuthenticationNotifier.provider); final authenticationNotifier = ref.watch(AuthenticationNotifier.provider.notifier); final mediaQuery = MediaQuery.of(context); - final searchTerm = ref.watch(searchTermStateProvider); - final searchTrack = ref.watch(searchProvider(SearchType.track)); final searchAlbum = ref.watch(searchProvider(SearchType.album)); final searchPlaylist = ref.watch(searchProvider(SearchType.playlist)); diff --git a/lib/provider/spotify/album/tracks.dart b/lib/provider/spotify/album/tracks.dart index b9bc9173..9556cc52 100644 --- a/lib/provider/spotify/album/tracks.dart +++ b/lib/provider/spotify/album/tracks.dart @@ -24,8 +24,8 @@ class AlbumTracksState extends PaginatedState { } } -class AlbumTracksNotifier - extends FamilyPaginatedAsyncNotifier { +class AlbumTracksNotifier extends AutoDisposeFamilyPaginatedAsyncNotifier { AlbumTracksNotifier() : super(); @override @@ -39,6 +39,8 @@ class AlbumTracksNotifier @override build(arg) async { + ref.cacheFor(); + ref.watch(spotifyProvider); final tracks = await fetch(arg, 0, 20); return AlbumTracksState( @@ -50,7 +52,7 @@ class AlbumTracksNotifier } } -final albumTracksProvider = AsyncNotifierProviderFamily( +final albumTracksProvider = AutoDisposeAsyncNotifierProviderFamily< + AlbumTracksNotifier, AlbumTracksState, AlbumSimple>( () => AlbumTracksNotifier(), ); diff --git a/lib/provider/spotify/artist/albums.dart b/lib/provider/spotify/artist/albums.dart index 0e7ea84b..16bd8768 100644 --- a/lib/provider/spotify/artist/albums.dart +++ b/lib/provider/spotify/artist/albums.dart @@ -24,8 +24,8 @@ class ArtistAlbumsState extends PaginatedState { } } -class ArtistAlbumsNotifier - extends FamilyPaginatedAsyncNotifier { +class ArtistAlbumsNotifier extends AutoDisposeFamilyPaginatedAsyncNotifier< + Album, ArtistAlbumsState, String> { ArtistAlbumsNotifier() : super(); @override @@ -40,6 +40,8 @@ class ArtistAlbumsNotifier @override build(arg) async { + ref.cacheFor(); + ref.watch(spotifyProvider); ref.watch( userPreferencesProvider.select((s) => s.recommendationMarket), @@ -54,7 +56,7 @@ class ArtistAlbumsNotifier } } -final artistAlbumsProvider = AsyncNotifierProviderFamily( +final artistAlbumsProvider = AutoDisposeAsyncNotifierProviderFamily< + ArtistAlbumsNotifier, ArtistAlbumsState, String>( () => ArtistAlbumsNotifier(), ); diff --git a/lib/provider/spotify/artist/artist.dart b/lib/provider/spotify/artist/artist.dart index 5f795658..c69badd2 100644 --- a/lib/provider/spotify/artist/artist.dart +++ b/lib/provider/spotify/artist/artist.dart @@ -1,6 +1,9 @@ part of '../spotify.dart'; -final artistProvider = FutureProvider.family((ref, String artistId) { +final artistProvider = + FutureProvider.autoDispose.family((ref, String artistId) { + ref.cacheFor(); + final spotify = ref.watch(spotifyProvider); return spotify.artists.get(artistId); diff --git a/lib/provider/spotify/artist/related.dart b/lib/provider/spotify/artist/related.dart index 6782f56e..317feba3 100644 --- a/lib/provider/spotify/artist/related.dart +++ b/lib/provider/spotify/artist/related.dart @@ -1,7 +1,9 @@ part of '../spotify.dart'; -final relatedArtistsProvider = - FutureProvider.family, String>((ref, artistId) async { +final relatedArtistsProvider = FutureProvider.autoDispose + .family, String>((ref, artistId) async { + ref.cacheFor(); + final spotify = ref.watch(spotifyProvider); final artists = await spotify.artists.relatedArtists(artistId); diff --git a/lib/provider/spotify/artist/top_tracks.dart b/lib/provider/spotify/artist/top_tracks.dart index 77186689..fa40d646 100644 --- a/lib/provider/spotify/artist/top_tracks.dart +++ b/lib/provider/spotify/artist/top_tracks.dart @@ -1,7 +1,10 @@ part of '../spotify.dart'; -final artistTopTracksProvider = FutureProviderFamily, String>( +final artistTopTracksProvider = + FutureProvider.autoDispose.family, String>( (ref, artistId) async { + ref.cacheFor(); + final spotify = ref.watch(spotifyProvider); final market = ref .watch(userPreferencesProvider.select((s) => s.recommendationMarket)); diff --git a/lib/provider/spotify/category/playlists.dart b/lib/provider/spotify/category/playlists.dart index aaa676f9..979b7f31 100644 --- a/lib/provider/spotify/category/playlists.dart +++ b/lib/provider/spotify/category/playlists.dart @@ -24,7 +24,7 @@ class CategoryPlaylistsState extends PaginatedState { } } -class CategoryPlaylistsNotifier extends FamilyPaginatedAsyncNotifier< +class CategoryPlaylistsNotifier extends AutoDisposeFamilyPaginatedAsyncNotifier< PlaylistSimple, CategoryPlaylistsState, String> { CategoryPlaylistsNotifier() : super(); @@ -44,6 +44,8 @@ class CategoryPlaylistsNotifier extends FamilyPaginatedAsyncNotifier< @override build(arg) async { + ref.cacheFor(); + ref.watch(spotifyProvider); ref.watch(userPreferencesProvider.select((s) => s.locale)); ref.watch(userPreferencesProvider.select((s) => s.recommendationMarket)); @@ -59,7 +61,7 @@ class CategoryPlaylistsNotifier extends FamilyPaginatedAsyncNotifier< } } -final categoryPlaylistsProvider = AsyncNotifierProviderFamily< +final categoryPlaylistsProvider = AutoDisposeAsyncNotifierProviderFamily< CategoryPlaylistsNotifier, CategoryPlaylistsState, String>( () => CategoryPlaylistsNotifier(), ); diff --git a/lib/provider/spotify/playlist/playlist.dart b/lib/provider/spotify/playlist/playlist.dart index 6f62000f..fd420cd9 100644 --- a/lib/provider/spotify/playlist/playlist.dart +++ b/lib/provider/spotify/playlist/playlist.dart @@ -85,6 +85,6 @@ class PlaylistNotifier extends FamilyAsyncNotifier { } final playlistProvider = - AsyncNotifierProviderFamily( + AsyncNotifierProvider.family( () => PlaylistNotifier(), ); diff --git a/lib/provider/spotify/playlist/tracks.dart b/lib/provider/spotify/playlist/tracks.dart index ed0041cd..476721a2 100644 --- a/lib/provider/spotify/playlist/tracks.dart +++ b/lib/provider/spotify/playlist/tracks.dart @@ -24,8 +24,8 @@ class PlaylistTracksState extends PaginatedState { } } -class PlaylistTracksNotifier - extends FamilyPaginatedAsyncNotifier { +class PlaylistTracksNotifier extends AutoDisposeFamilyPaginatedAsyncNotifier< + Track, PlaylistTracksState, String> { PlaylistTracksNotifier() : super(); @override @@ -34,11 +34,16 @@ class PlaylistTracksNotifier .getTracksByPlaylistId(arg) .getPage(limit, offset); - return tracks.items?.toList() ?? []; + /// Filter out tracks with null id because some personal playlists + /// may contain local tracks that are not available in the Spotify catalog + return tracks.items?.where((track) => track.id != null).toList() ?? + []; } @override build(arg) async { + ref.cacheFor(); + ref.watch(spotifyProvider); final tracks = await fetch(arg, 0, 20); @@ -51,7 +56,7 @@ class PlaylistTracksNotifier } } -final playlistTracksProvider = AsyncNotifierProviderFamily< +final playlistTracksProvider = AutoDisposeAsyncNotifierProviderFamily< PlaylistTracksNotifier, PlaylistTracksState, String>( () => PlaylistTracksNotifier(), ); diff --git a/lib/provider/spotify/search/search.dart b/lib/provider/spotify/search/search.dart index 4b936df9..bd97f08b 100644 --- a/lib/provider/spotify/search/search.dart +++ b/lib/provider/spotify/search/search.dart @@ -1,6 +1,11 @@ part of '../spotify.dart'; -final searchTermStateProvider = StateProvider((ref) => ""); +final searchTermStateProvider = StateProvider.autoDispose( + (ref) { + ref.cacheFor(const Duration(minutes: 2)); + return ""; + }, +); class SearchState extends PaginatedState { SearchState({ @@ -26,8 +31,8 @@ class SearchState extends PaginatedState { } } -class SearchNotifier - extends FamilyPaginatedAsyncNotifier, SearchType> { +class SearchNotifier extends AutoDisposeFamilyPaginatedAsyncNotifier, SearchType> { SearchNotifier() : super(); @override @@ -46,6 +51,8 @@ class SearchNotifier @override build(arg) async { + ref.cacheFor(const Duration(minutes: 2)); + ref.watch(searchTermStateProvider); ref.watch(spotifyProvider); ref.watch( @@ -63,7 +70,7 @@ class SearchNotifier } } -final searchProvider = - AsyncNotifierProvider.family( +final searchProvider = AsyncNotifierProvider.autoDispose + .family( () => SearchNotifier(), ); diff --git a/lib/provider/spotify/spotify.dart b/lib/provider/spotify/spotify.dart index 024f60c4..ea28b6d8 100644 --- a/lib/provider/spotify/spotify.dart +++ b/lib/provider/spotify/spotify.dart @@ -66,3 +66,8 @@ part 'utils/state.dart'; part 'utils/provider.dart'; part 'utils/persistence.dart'; part 'utils/async.dart'; + +part 'utils/provider/paginated.dart'; +part 'utils/provider/cursor.dart'; +part 'utils/provider/paginated_family.dart'; +part 'utils/provider/cursor_family.dart'; diff --git a/lib/provider/spotify/tracks/track.dart b/lib/provider/spotify/tracks/track.dart index dd0e1184..e3913b1f 100644 --- a/lib/provider/spotify/tracks/track.dart +++ b/lib/provider/spotify/tracks/track.dart @@ -1,6 +1,9 @@ part of '../spotify.dart'; -final trackProvider = FutureProvider.family((ref, id) async { +final trackProvider = + FutureProvider.autoDispose.family((ref, id) async { + ref.cacheFor(); + final spotify = ref.watch(spotifyProvider); return spotify.tracks.get(id); diff --git a/lib/provider/spotify/utils/mixin.dart b/lib/provider/spotify/utils/mixin.dart index 0728c207..0da14c6f 100644 --- a/lib/provider/spotify/utils/mixin.dart +++ b/lib/provider/spotify/utils/mixin.dart @@ -1,6 +1,24 @@ part of '../spotify.dart'; // ignore: invalid_use_of_internal_member -mixin SpotifyMixin on BuildlessAsyncNotifier { +mixin SpotifyMixin on AsyncNotifierBase { SpotifyApi get spotify => ref.read(spotifyProvider); } + +extension on AutoDisposeAsyncNotifierProviderRef { + // When invoked keeps your provider alive for [duration] + void cacheFor([Duration duration = const Duration(minutes: 5)]) { + final link = keepAlive(); + final timer = Timer(duration, () => link.close()); + onDispose(() => timer.cancel()); + } +} + +extension on AutoDisposeRef { + // When invoked keeps your provider alive for [duration] + void cacheFor([Duration duration = const Duration(minutes: 5)]) { + final link = keepAlive(); + final timer = Timer(duration, () => link.close()); + onDispose(() => timer.cancel()); + } +} diff --git a/lib/provider/spotify/utils/provider.dart b/lib/provider/spotify/utils/provider.dart index a0e44030..50458c3a 100644 --- a/lib/provider/spotify/utils/provider.dart +++ b/lib/provider/spotify/utils/provider.dart @@ -4,214 +4,3 @@ part of '../spotify.dart'; class AsyncLoadingNext extends AsyncData { const AsyncLoadingNext(super.value); } - -abstract class PaginatedAsyncNotifier> - extends AsyncNotifier with SpotifyMixin { - Future> fetch(int offset, int limit); - - Future fetchMore() async { - if (state.value == null || !state.value!.hasMore) return; - - state = AsyncLoadingNext(state.asData!.value); - - state = await AsyncValue.guard( - () async { - final items = await fetch( - state.value!.offset + state.value!.limit, - state.value!.limit, - ); - return state.value!.copyWith( - hasMore: items.length == state.value!.limit, - items: [ - ...state.value!.items, - ...items, - ], - offset: state.value!.offset + state.value!.limit, - ) as T; - }, - ); - } - - Future> fetchAll() async { - if (state.value == null) return []; - if (!state.value!.hasMore) return state.value!.items; - - bool hasMore = true; - while (hasMore) { - await update((state) async { - final items = await fetch( - state.offset + state.limit, - state.limit, - ); - - hasMore = items.length == state.limit; - return state.copyWith( - items: [...state.items, ...items], - offset: state.offset + state.limit, - hasMore: hasMore, - ) as T; - }); - } - - return state.value!.items; - } -} - -abstract class CursorPaginatedAsyncNotifier> extends AsyncNotifier - with SpotifyMixin { - Future<(List items, String nextCursor)> fetch(String? offset, int limit); - - Future fetchMore() async { - if (state.value == null || !state.value!.hasMore) return; - - state = AsyncLoadingNext(state.asData!.value); - - state = await AsyncValue.guard( - () async { - final items = await fetch(state.value!.offset, state.value!.limit); - return state.value!.copyWith( - hasMore: items.$1.length == state.value!.limit, - items: [ - ...state.value!.items, - ...items.$1, - ], - offset: items.$2, - ) as T; - }, - ); - } - - Future> fetchAll() async { - if (state.value == null) return []; - if (!state.value!.hasMore) return state.value!.items; - - bool hasMore = true; - while (hasMore) { - await update((state) async { - final items = await fetch(state.offset, state.limit); - - hasMore = items.$1.length == state.limit; - return state.copyWith( - items: [...state.items, ...items.$1], - offset: items.$2, - hasMore: hasMore, - ) as T; - }); - } - - return state.value!.items; - } -} - -abstract class FamilyPaginatedAsyncNotifier< - K, - T extends BasePaginatedState, - A> extends FamilyAsyncNotifier with SpotifyMixin { - Future> fetch(A arg, int offset, int limit); - - Future fetchMore() async { - if (state.value == null || !state.value!.hasMore) return; - - state = AsyncLoadingNext(state.asData!.value); - - state = await AsyncValue.guard( - () async { - final items = await fetch( - arg, - state.value!.offset + state.value!.limit, - state.value!.limit, - ); - return state.value!.copyWith( - hasMore: items.length == state.value!.limit, - items: [ - ...state.value!.items, - ...items, - ], - offset: state.value!.offset + state.value!.limit, - ) as T; - }, - ); - } - - Future> fetchAll() async { - if (state.value == null) return []; - if (!state.value!.hasMore) return state.value!.items; - - bool hasMore = true; - while (hasMore) { - await update((state) async { - final items = await fetch( - arg, - state.offset + state.limit, - state.limit, - ); - - hasMore = items.length == state.limit; - return state.copyWith( - items: [...state.items, ...items], - offset: state.offset + state.limit, - hasMore: hasMore, - ) as T; - }); - } - - return state.value!.items; - } -} - -abstract class FamilyCursorPaginatedAsyncNotifier< - K, - T extends CursorPaginatedState, - A> extends FamilyAsyncNotifier with SpotifyMixin { - Future<(List items, String nextCursor)> fetch( - A arg, - String? offset, - int limit, - ); - - Future fetchMore() async { - if (state.value == null || !state.value!.hasMore) return; - - state = AsyncLoadingNext(state.asData!.value); - - state = await AsyncValue.guard( - () async { - final items = await fetch(arg, state.value!.offset, state.value!.limit); - return state.value!.copyWith( - hasMore: items.$1.length == state.value!.limit, - items: [ - ...state.value!.items, - ...items.$1, - ], - offset: items.$2, - ) as T; - }, - ); - } - - Future> fetchAll() async { - if (state.value == null) return []; - if (!state.value!.hasMore) return state.value!.items; - - bool hasMore = true; - while (hasMore) { - await update((state) async { - final items = await fetch( - arg, - state.offset, - state.limit, - ); - - hasMore = items.$1.length == state.limit; - return state.copyWith( - items: [...state.items, ...items.$1], - offset: items.$2, - hasMore: hasMore, - ) as T; - }); - } - - return state.value!.items; - } -} diff --git a/lib/provider/spotify/utils/provider/cursor.dart b/lib/provider/spotify/utils/provider/cursor.dart new file mode 100644 index 00000000..c241827e --- /dev/null +++ b/lib/provider/spotify/utils/provider/cursor.dart @@ -0,0 +1,56 @@ +part of '../../spotify.dart'; + +mixin CursorPaginatedAsyncNotifierMixin> + // ignore: invalid_use_of_internal_member + on AsyncNotifierBase { + Future<(List items, String nextCursor)> fetch(String? offset, int limit); + + Future fetchMore() async { + if (state.value == null || !state.value!.hasMore) return; + + state = AsyncLoadingNext(state.asData!.value); + + state = await AsyncValue.guard( + () async { + final items = await fetch(state.value!.offset, state.value!.limit); + return state.value!.copyWith( + hasMore: items.$1.length == state.value!.limit, + items: [ + ...state.value!.items, + ...items.$1, + ], + offset: items.$2, + ) as T; + }, + ); + } + + Future> fetchAll() async { + if (state.value == null) return []; + if (!state.value!.hasMore) return state.value!.items; + + bool hasMore = true; + while (hasMore) { + await update((state) async { + final items = await fetch(state.offset, state.limit); + + hasMore = items.$1.length == state.limit; + return state.copyWith( + items: [...state.items, ...items.$1], + offset: items.$2, + hasMore: hasMore, + ) as T; + }); + } + + return state.value!.items; + } +} + +abstract class CursorPaginatedAsyncNotifier> extends AsyncNotifier + with CursorPaginatedAsyncNotifierMixin, SpotifyMixin {} + +abstract class AutoDisposeCursorPaginatedAsyncNotifier> extends AutoDisposeAsyncNotifier + with CursorPaginatedAsyncNotifierMixin, SpotifyMixin {} diff --git a/lib/provider/spotify/utils/provider/cursor_family.dart b/lib/provider/spotify/utils/provider/cursor_family.dart new file mode 100644 index 00000000..ea8577de --- /dev/null +++ b/lib/provider/spotify/utils/provider/cursor_family.dart @@ -0,0 +1,113 @@ +part of '../../spotify.dart'; + +abstract class FamilyCursorPaginatedAsyncNotifier< + K, + T extends CursorPaginatedState, + A> extends FamilyAsyncNotifier with SpotifyMixin { + Future<(List items, String nextCursor)> fetch( + A arg, + String? offset, + int limit, + ); + + Future fetchMore() async { + if (state.value == null || !state.value!.hasMore) return; + + state = AsyncLoadingNext(state.asData!.value); + + state = await AsyncValue.guard( + () async { + final items = await fetch(arg, state.value!.offset, state.value!.limit); + return state.value!.copyWith( + hasMore: items.$1.length == state.value!.limit, + items: [ + ...state.value!.items, + ...items.$1, + ], + offset: items.$2, + ) as T; + }, + ); + } + + Future> fetchAll() async { + if (state.value == null) return []; + if (!state.value!.hasMore) return state.value!.items; + + bool hasMore = true; + while (hasMore) { + await update((state) async { + final items = await fetch( + arg, + state.offset, + state.limit, + ); + + hasMore = items.$1.length == state.limit; + return state.copyWith( + items: [...state.items, ...items.$1], + offset: items.$2, + hasMore: hasMore, + ) as T; + }); + } + + return state.value!.items; + } +} + +abstract class AutoDisposeFamilyCursorPaginatedAsyncNotifier< + K, + T extends CursorPaginatedState, + A> extends AutoDisposeFamilyAsyncNotifier with SpotifyMixin { + Future<(List items, String nextCursor)> fetch( + A arg, + String? offset, + int limit, + ); + + Future fetchMore() async { + if (state.value == null || !state.value!.hasMore) return; + + state = AsyncLoadingNext(state.asData!.value); + + state = await AsyncValue.guard( + () async { + final items = await fetch(arg, state.value!.offset, state.value!.limit); + return state.value!.copyWith( + hasMore: items.$1.length == state.value!.limit, + items: [ + ...state.value!.items, + ...items.$1, + ], + offset: items.$2, + ) as T; + }, + ); + } + + Future> fetchAll() async { + if (state.value == null) return []; + if (!state.value!.hasMore) return state.value!.items; + + bool hasMore = true; + while (hasMore) { + await update((state) async { + final items = await fetch( + arg, + state.offset, + state.limit, + ); + + hasMore = items.$1.length == state.limit; + return state.copyWith( + items: [...state.items, ...items.$1], + offset: items.$2, + hasMore: hasMore, + ) as T; + }); + } + + return state.value!.items; + } +} diff --git a/lib/provider/spotify/utils/provider/paginated.dart b/lib/provider/spotify/utils/provider/paginated.dart new file mode 100644 index 00000000..30b66e67 --- /dev/null +++ b/lib/provider/spotify/utils/provider/paginated.dart @@ -0,0 +1,63 @@ +part of '../../spotify.dart'; + +mixin PaginatedAsyncNotifierMixin> + // ignore: invalid_use_of_internal_member + on AsyncNotifierBase { + Future> fetch(int offset, int limit); + + Future fetchMore() async { + if (state.value == null || !state.value!.hasMore) return; + + state = AsyncLoadingNext(state.asData!.value); + + state = await AsyncValue.guard( + () async { + final items = await fetch( + state.value!.offset + state.value!.limit, + state.value!.limit, + ); + return state.value!.copyWith( + hasMore: items.length == state.value!.limit, + items: [ + ...state.value!.items, + ...items, + ], + offset: state.value!.offset + state.value!.limit, + ) as T; + }, + ); + } + + Future> fetchAll() async { + if (state.value == null) return []; + if (!state.value!.hasMore) return state.value!.items; + + bool hasMore = true; + while (hasMore) { + await update((state) async { + final items = await fetch( + state.offset + state.limit, + state.limit, + ); + + hasMore = items.length == state.limit; + return state.copyWith( + items: [...state.items, ...items], + offset: state.offset + state.limit, + hasMore: hasMore, + ) as T; + }); + } + + return state.value!.items; + } +} + +abstract class PaginatedAsyncNotifier> + extends AsyncNotifier + with PaginatedAsyncNotifierMixin, SpotifyMixin {} + +abstract class AutoDisposePaginatedAsyncNotifier> + extends AutoDisposeAsyncNotifier + with PaginatedAsyncNotifierMixin, SpotifyMixin {} diff --git a/lib/provider/spotify/utils/provider/paginated_family.dart b/lib/provider/spotify/utils/provider/paginated_family.dart new file mode 100644 index 00000000..84c6ba20 --- /dev/null +++ b/lib/provider/spotify/utils/provider/paginated_family.dart @@ -0,0 +1,113 @@ +part of '../../spotify.dart'; + +abstract class FamilyPaginatedAsyncNotifier< + K, + T extends BasePaginatedState, + A> extends FamilyAsyncNotifier with SpotifyMixin { + Future> fetch(A arg, int offset, int limit); + + Future fetchMore() async { + if (state.value == null || !state.value!.hasMore) return; + + state = AsyncLoadingNext(state.asData!.value); + + state = await AsyncValue.guard( + () async { + final items = await fetch( + arg, + state.value!.offset + state.value!.limit, + state.value!.limit, + ); + return state.value!.copyWith( + hasMore: items.length == state.value!.limit, + items: [ + ...state.value!.items, + ...items, + ], + offset: state.value!.offset + state.value!.limit, + ) as T; + }, + ); + } + + Future> fetchAll() async { + if (state.value == null) return []; + if (!state.value!.hasMore) return state.value!.items; + + bool hasMore = true; + while (hasMore) { + await update((state) async { + final items = await fetch( + arg, + state.offset + state.limit, + state.limit, + ); + + hasMore = items.length == state.limit; + return state.copyWith( + items: [...state.items, ...items], + offset: state.offset + state.limit, + hasMore: hasMore, + ) as T; + }); + } + + return state.value!.items; + } +} + +abstract class AutoDisposeFamilyPaginatedAsyncNotifier< + K, + T extends BasePaginatedState, + A> extends AutoDisposeFamilyAsyncNotifier with SpotifyMixin { + Future> fetch(A arg, int offset, int limit); + + Future fetchMore() async { + if (state.value == null || !state.value!.hasMore) return; + + state = AsyncLoadingNext(state.asData!.value); + + state = await AsyncValue.guard( + () async { + final items = await fetch( + arg, + state.value!.offset + state.value!.limit, + state.value!.limit, + ); + return state.value!.copyWith( + hasMore: items.length == state.value!.limit, + items: [ + ...state.value!.items, + ...items, + ], + offset: state.value!.offset + state.value!.limit, + ) as T; + }, + ); + } + + Future> fetchAll() async { + if (state.value == null) return []; + if (!state.value!.hasMore) return state.value!.items; + + bool hasMore = true; + while (hasMore) { + await update((state) async { + final items = await fetch( + arg, + state.offset + state.limit, + state.limit, + ); + + hasMore = items.length == state.limit; + return state.copyWith( + items: [...state.items, ...items], + offset: state.offset + state.limit, + hasMore: hasMore, + ) as T; + }); + } + + return state.value!.items; + } +}