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
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));

View File

@ -24,8 +24,8 @@ class AlbumTracksState extends PaginatedState<Track> {
}
}
class AlbumTracksNotifier
extends FamilyPaginatedAsyncNotifier<Track, AlbumTracksState, AlbumSimple> {
class AlbumTracksNotifier extends AutoDisposeFamilyPaginatedAsyncNotifier<Track,
AlbumTracksState, AlbumSimple> {
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<AlbumTracksNotifier,
AlbumTracksState, AlbumSimple>(
final albumTracksProvider = AutoDisposeAsyncNotifierProviderFamily<
AlbumTracksNotifier, AlbumTracksState, AlbumSimple>(
() => AlbumTracksNotifier(),
);

View File

@ -24,8 +24,8 @@ class ArtistAlbumsState extends PaginatedState<Album> {
}
}
class ArtistAlbumsNotifier
extends FamilyPaginatedAsyncNotifier<Album, ArtistAlbumsState, String> {
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<ArtistAlbumsNotifier,
ArtistAlbumsState, String>(
final artistAlbumsProvider = AutoDisposeAsyncNotifierProviderFamily<
ArtistAlbumsNotifier, ArtistAlbumsState, String>(
() => ArtistAlbumsNotifier(),
);

View File

@ -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);

View File

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

View File

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

View File

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

View File

@ -24,8 +24,8 @@ class PlaylistTracksState extends PaginatedState<Track> {
}
}
class PlaylistTracksNotifier
extends FamilyPaginatedAsyncNotifier<Track, PlaylistTracksState, String> {
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() ?? <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
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(),
);

View File

@ -1,6 +1,11 @@
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> {
SearchState({
@ -26,8 +31,8 @@ class SearchState<Y> extends PaginatedState<Y> {
}
}
class SearchNotifier<Y>
extends FamilyPaginatedAsyncNotifier<Y, SearchState<Y>, SearchType> {
class SearchNotifier<Y> extends AutoDisposeFamilyPaginatedAsyncNotifier<Y,
SearchState<Y>, SearchType> {
SearchNotifier() : super();
@override
@ -46,6 +51,8 @@ class SearchNotifier<Y>
@override
build(arg) async {
ref.cacheFor(const Duration(minutes: 2));
ref.watch(searchTermStateProvider);
ref.watch(spotifyProvider);
ref.watch(
@ -63,7 +70,7 @@ class SearchNotifier<Y>
}
}
final searchProvider =
AsyncNotifierProvider.family<SearchNotifier, SearchState, SearchType>(
final searchProvider = AsyncNotifierProvider.autoDispose
.family<SearchNotifier, SearchState, SearchType>(
() => SearchNotifier(),
);

View File

@ -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';

View File

@ -1,6 +1,9 @@
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);
return spotify.tracks.get(id);

View File

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