feat: make many provider autoDispose after 5 minutes of no usage

This commit is contained in:
Kingkor Roy Tirtho 2024-03-20 23:22:48 +06:00
parent 36316f5e94
commit 75b05f3dc8
18 changed files with 425 additions and 240 deletions

View File

@ -28,15 +28,14 @@ class SearchPage extends HookConsumerWidget {
@override @override
Widget build(BuildContext context, ref) { Widget build(BuildContext context, ref) {
final theme = Theme.of(context); final theme = Theme.of(context);
final controller = useTextEditingController(); final searchTerm = ref.watch(searchTermStateProvider);
final controller = useTextEditingController(text: searchTerm);
ref.watch(AuthenticationNotifier.provider); ref.watch(AuthenticationNotifier.provider);
final authenticationNotifier = final authenticationNotifier =
ref.watch(AuthenticationNotifier.provider.notifier); ref.watch(AuthenticationNotifier.provider.notifier);
final mediaQuery = MediaQuery.of(context); final mediaQuery = MediaQuery.of(context);
final searchTerm = ref.watch(searchTermStateProvider);
final searchTrack = ref.watch(searchProvider(SearchType.track)); final searchTrack = ref.watch(searchProvider(SearchType.track));
final searchAlbum = ref.watch(searchProvider(SearchType.album)); final searchAlbum = ref.watch(searchProvider(SearchType.album));
final searchPlaylist = ref.watch(searchProvider(SearchType.playlist)); final searchPlaylist = ref.watch(searchProvider(SearchType.playlist));

View File

@ -24,8 +24,8 @@ class AlbumTracksState extends PaginatedState<Track> {
} }
} }
class AlbumTracksNotifier class AlbumTracksNotifier extends AutoDisposeFamilyPaginatedAsyncNotifier<Track,
extends FamilyPaginatedAsyncNotifier<Track, AlbumTracksState, AlbumSimple> { AlbumTracksState, AlbumSimple> {
AlbumTracksNotifier() : super(); AlbumTracksNotifier() : super();
@override @override
@ -39,6 +39,8 @@ class AlbumTracksNotifier
@override @override
build(arg) async { build(arg) async {
ref.cacheFor();
ref.watch(spotifyProvider); ref.watch(spotifyProvider);
final tracks = await fetch(arg, 0, 20); final tracks = await fetch(arg, 0, 20);
return AlbumTracksState( return AlbumTracksState(
@ -50,7 +52,7 @@ class AlbumTracksNotifier
} }
} }
final albumTracksProvider = AsyncNotifierProviderFamily<AlbumTracksNotifier, final albumTracksProvider = AutoDisposeAsyncNotifierProviderFamily<
AlbumTracksState, AlbumSimple>( AlbumTracksNotifier, AlbumTracksState, AlbumSimple>(
() => AlbumTracksNotifier(), () => AlbumTracksNotifier(),
); );

View File

@ -24,8 +24,8 @@ class ArtistAlbumsState extends PaginatedState<Album> {
} }
} }
class ArtistAlbumsNotifier class ArtistAlbumsNotifier extends AutoDisposeFamilyPaginatedAsyncNotifier<
extends FamilyPaginatedAsyncNotifier<Album, ArtistAlbumsState, String> { Album, ArtistAlbumsState, String> {
ArtistAlbumsNotifier() : super(); ArtistAlbumsNotifier() : super();
@override @override
@ -40,6 +40,8 @@ class ArtistAlbumsNotifier
@override @override
build(arg) async { build(arg) async {
ref.cacheFor();
ref.watch(spotifyProvider); ref.watch(spotifyProvider);
ref.watch( ref.watch(
userPreferencesProvider.select((s) => s.recommendationMarket), userPreferencesProvider.select((s) => s.recommendationMarket),
@ -54,7 +56,7 @@ class ArtistAlbumsNotifier
} }
} }
final artistAlbumsProvider = AsyncNotifierProviderFamily<ArtistAlbumsNotifier, final artistAlbumsProvider = AutoDisposeAsyncNotifierProviderFamily<
ArtistAlbumsState, String>( ArtistAlbumsNotifier, ArtistAlbumsState, String>(
() => ArtistAlbumsNotifier(), () => ArtistAlbumsNotifier(),
); );

View File

@ -1,6 +1,9 @@
part of '../spotify.dart'; 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); final spotify = ref.watch(spotifyProvider);
return spotify.artists.get(artistId); return spotify.artists.get(artistId);

View File

@ -1,7 +1,9 @@
part of '../spotify.dart'; part of '../spotify.dart';
final relatedArtistsProvider = final relatedArtistsProvider = FutureProvider.autoDispose
FutureProvider.family<List<Artist>, String>((ref, artistId) async { .family<List<Artist>, String>((ref, artistId) async {
ref.cacheFor();
final spotify = ref.watch(spotifyProvider); final spotify = ref.watch(spotifyProvider);
final artists = await spotify.artists.relatedArtists(artistId); final artists = await spotify.artists.relatedArtists(artistId);

View File

@ -1,7 +1,10 @@
part of '../spotify.dart'; part of '../spotify.dart';
final artistTopTracksProvider = FutureProviderFamily<List<Track>, String>( final artistTopTracksProvider =
FutureProvider.autoDispose.family<List<Track>, String>(
(ref, artistId) async { (ref, artistId) async {
ref.cacheFor();
final spotify = ref.watch(spotifyProvider); final spotify = ref.watch(spotifyProvider);
final market = ref final market = ref
.watch(userPreferencesProvider.select((s) => s.recommendationMarket)); .watch(userPreferencesProvider.select((s) => s.recommendationMarket));

View File

@ -24,7 +24,7 @@ class CategoryPlaylistsState extends PaginatedState<PlaylistSimple> {
} }
} }
class CategoryPlaylistsNotifier extends FamilyPaginatedAsyncNotifier< class CategoryPlaylistsNotifier extends AutoDisposeFamilyPaginatedAsyncNotifier<
PlaylistSimple, CategoryPlaylistsState, String> { PlaylistSimple, CategoryPlaylistsState, String> {
CategoryPlaylistsNotifier() : super(); CategoryPlaylistsNotifier() : super();
@ -44,6 +44,8 @@ class CategoryPlaylistsNotifier extends FamilyPaginatedAsyncNotifier<
@override @override
build(arg) async { build(arg) async {
ref.cacheFor();
ref.watch(spotifyProvider); ref.watch(spotifyProvider);
ref.watch(userPreferencesProvider.select((s) => s.locale)); ref.watch(userPreferencesProvider.select((s) => s.locale));
ref.watch(userPreferencesProvider.select((s) => s.recommendationMarket)); 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, CategoryPlaylistsState, String>(
() => CategoryPlaylistsNotifier(), () => CategoryPlaylistsNotifier(),
); );

View File

@ -85,6 +85,6 @@ class PlaylistNotifier extends FamilyAsyncNotifier<Playlist, String> {
} }
final playlistProvider = final playlistProvider =
AsyncNotifierProviderFamily<PlaylistNotifier, Playlist, String>( AsyncNotifierProvider.family<PlaylistNotifier, Playlist, String>(
() => PlaylistNotifier(), () => PlaylistNotifier(),
); );

View File

@ -24,8 +24,8 @@ class PlaylistTracksState extends PaginatedState<Track> {
} }
} }
class PlaylistTracksNotifier class PlaylistTracksNotifier extends AutoDisposeFamilyPaginatedAsyncNotifier<
extends FamilyPaginatedAsyncNotifier<Track, PlaylistTracksState, String> { Track, PlaylistTracksState, String> {
PlaylistTracksNotifier() : super(); PlaylistTracksNotifier() : super();
@override @override
@ -34,11 +34,16 @@ class PlaylistTracksNotifier
.getTracksByPlaylistId(arg) .getTracksByPlaylistId(arg)
.getPage(limit, offset); .getPage(limit, offset);
return tracks.items?.toList() ?? <Track>[]; /// 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() ??
<Track>[];
} }
@override @override
build(arg) async { build(arg) async {
ref.cacheFor();
ref.watch(spotifyProvider); ref.watch(spotifyProvider);
final tracks = await fetch(arg, 0, 20); final tracks = await fetch(arg, 0, 20);
@ -51,7 +56,7 @@ class PlaylistTracksNotifier
} }
} }
final playlistTracksProvider = AsyncNotifierProviderFamily< final playlistTracksProvider = AutoDisposeAsyncNotifierProviderFamily<
PlaylistTracksNotifier, PlaylistTracksState, String>( PlaylistTracksNotifier, PlaylistTracksState, String>(
() => PlaylistTracksNotifier(), () => PlaylistTracksNotifier(),
); );

View File

@ -1,6 +1,11 @@
part of '../spotify.dart'; part of '../spotify.dart';
final searchTermStateProvider = StateProvider<String>((ref) => ""); final searchTermStateProvider = StateProvider.autoDispose<String>(
(ref) {
ref.cacheFor(const Duration(minutes: 2));
return "";
},
);
class SearchState<Y> extends PaginatedState<Y> { class SearchState<Y> extends PaginatedState<Y> {
SearchState({ SearchState({
@ -26,8 +31,8 @@ class SearchState<Y> extends PaginatedState<Y> {
} }
} }
class SearchNotifier<Y> class SearchNotifier<Y> extends AutoDisposeFamilyPaginatedAsyncNotifier<Y,
extends FamilyPaginatedAsyncNotifier<Y, SearchState<Y>, SearchType> { SearchState<Y>, SearchType> {
SearchNotifier() : super(); SearchNotifier() : super();
@override @override
@ -46,6 +51,8 @@ class SearchNotifier<Y>
@override @override
build(arg) async { build(arg) async {
ref.cacheFor(const Duration(minutes: 2));
ref.watch(searchTermStateProvider); ref.watch(searchTermStateProvider);
ref.watch(spotifyProvider); ref.watch(spotifyProvider);
ref.watch( ref.watch(
@ -63,7 +70,7 @@ class SearchNotifier<Y>
} }
} }
final searchProvider = final searchProvider = AsyncNotifierProvider.autoDispose
AsyncNotifierProvider.family<SearchNotifier, SearchState, SearchType>( .family<SearchNotifier, SearchState, SearchType>(
() => SearchNotifier(), () => SearchNotifier(),
); );

View File

@ -66,3 +66,8 @@ part 'utils/state.dart';
part 'utils/provider.dart'; part 'utils/provider.dart';
part 'utils/persistence.dart'; part 'utils/persistence.dart';
part 'utils/async.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';

View File

@ -1,6 +1,9 @@
part of '../spotify.dart'; part of '../spotify.dart';
final trackProvider = FutureProvider.family<Track, String>((ref, id) async { final trackProvider =
FutureProvider.autoDispose.family<Track, String>((ref, id) async {
ref.cacheFor();
final spotify = ref.watch(spotifyProvider); final spotify = ref.watch(spotifyProvider);
return spotify.tracks.get(id); return spotify.tracks.get(id);

View File

@ -1,6 +1,24 @@
part of '../spotify.dart'; part of '../spotify.dart';
// ignore: invalid_use_of_internal_member // ignore: invalid_use_of_internal_member
mixin SpotifyMixin<T> on BuildlessAsyncNotifier<T> { mixin SpotifyMixin<T> on AsyncNotifierBase<T> {
SpotifyApi get spotify => ref.read(spotifyProvider); 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());
}
}

View File

@ -4,214 +4,3 @@ part of '../spotify.dart';
class AsyncLoadingNext<T> extends AsyncData<T> { class AsyncLoadingNext<T> extends AsyncData<T> {
const AsyncLoadingNext(super.value); const AsyncLoadingNext(super.value);
} }
abstract class PaginatedAsyncNotifier<K, T extends BasePaginatedState<K, int>>
extends AsyncNotifier<T> with SpotifyMixin<T> {
Future<List<K>> fetch(int offset, int limit);
Future<void> 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<List<K>> 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<K,
T extends CursorPaginatedState<K>> extends AsyncNotifier<T>
with SpotifyMixin<T> {
Future<(List<K> items, String nextCursor)> fetch(String? offset, int limit);
Future<void> 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<List<K>> 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<K, dynamic>,
A> extends FamilyAsyncNotifier<T, A> with SpotifyMixin<T> {
Future<List<K>> fetch(A arg, int offset, int limit);
Future<void> 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<List<K>> 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<K>,
A> extends FamilyAsyncNotifier<T, A> with SpotifyMixin<T> {
Future<(List<K> items, String nextCursor)> fetch(
A arg,
String? offset,
int limit,
);
Future<void> 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<List<K>> 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;
}
}

View File

@ -0,0 +1,56 @@
part of '../../spotify.dart';
mixin CursorPaginatedAsyncNotifierMixin<K, T extends CursorPaginatedState<K>>
// ignore: invalid_use_of_internal_member
on AsyncNotifierBase<T> {
Future<(List<K> items, String nextCursor)> fetch(String? offset, int limit);
Future<void> 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<List<K>> 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<K,
T extends CursorPaginatedState<K>> extends AsyncNotifier<T>
with CursorPaginatedAsyncNotifierMixin<K, T>, SpotifyMixin<T> {}
abstract class AutoDisposeCursorPaginatedAsyncNotifier<K,
T extends CursorPaginatedState<K>> extends AutoDisposeAsyncNotifier<T>
with CursorPaginatedAsyncNotifierMixin<K, T>, SpotifyMixin<T> {}

View File

@ -0,0 +1,113 @@
part of '../../spotify.dart';
abstract class FamilyCursorPaginatedAsyncNotifier<
K,
T extends CursorPaginatedState<K>,
A> extends FamilyAsyncNotifier<T, A> with SpotifyMixin<T> {
Future<(List<K> items, String nextCursor)> fetch(
A arg,
String? offset,
int limit,
);
Future<void> 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<List<K>> 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<K>,
A> extends AutoDisposeFamilyAsyncNotifier<T, A> with SpotifyMixin<T> {
Future<(List<K> items, String nextCursor)> fetch(
A arg,
String? offset,
int limit,
);
Future<void> 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<List<K>> 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;
}
}

View File

@ -0,0 +1,63 @@
part of '../../spotify.dart';
mixin PaginatedAsyncNotifierMixin<K, T extends BasePaginatedState<K, int>>
// ignore: invalid_use_of_internal_member
on AsyncNotifierBase<T> {
Future<List<K>> fetch(int offset, int limit);
Future<void> 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<List<K>> 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<K, T extends BasePaginatedState<K, int>>
extends AsyncNotifier<T>
with PaginatedAsyncNotifierMixin<K, T>, SpotifyMixin<T> {}
abstract class AutoDisposePaginatedAsyncNotifier<K,
T extends BasePaginatedState<K, int>>
extends AutoDisposeAsyncNotifier<T>
with PaginatedAsyncNotifierMixin<K, T>, SpotifyMixin<T> {}

View File

@ -0,0 +1,113 @@
part of '../../spotify.dart';
abstract class FamilyPaginatedAsyncNotifier<
K,
T extends BasePaginatedState<K, dynamic>,
A> extends FamilyAsyncNotifier<T, A> with SpotifyMixin<T> {
Future<List<K>> fetch(A arg, int offset, int limit);
Future<void> 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<List<K>> 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<K, dynamic>,
A> extends AutoDisposeFamilyAsyncNotifier<T, A> with SpotifyMixin<T> {
Future<List<K>> fetch(A arg, int offset, int limit);
Future<void> 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<List<K>> 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;
}
}