fix: user liked tracks memory leak due to isStale & updateQueryFn

This commit is contained in:
Kingkor Roy Tirtho 2023-09-08 10:56:03 +06:00
parent 1b52de0906
commit 57281faa42
4 changed files with 103 additions and 58 deletions

View File

@ -29,7 +29,7 @@ class HeartButton extends HookConsumerWidget {
Widget build(BuildContext context, ref) {
final auth = ref.watch(AuthenticationNotifier.provider);
if (auth == null) return Container();
if (auth == null) return const SizedBox.shrink();
return IconButton(
tooltip: tooltip,
@ -57,18 +57,21 @@ class HeartButton extends HookConsumerWidget {
}
}
({
typedef UseTrackToggleLike = ({
bool isLiked,
Mutation<bool, dynamic, bool> toggleTrackLike,
Query<User?, dynamic> me,
}) useTrackToggleLike(Track track, WidgetRef ref) {
});
UseTrackToggleLike useTrackToggleLike(Track track, WidgetRef ref) {
final me = useQueries.user.me(ref);
final savedTracks =
useQueries.playlist.tracksOfQuery(ref, "user-liked-tracks");
final savedTracks = useQueries.playlist.likedTracksQuery(ref);
final isLiked =
savedTracks.data?.any((element) => element.id == track.id) ?? false;
final isLiked = useMemoized(
() => savedTracks.data?.any((element) => element.id == track.id) ?? false,
[savedTracks.data, track.id],
);
final mounted = useIsMounted();
@ -76,28 +79,48 @@ class HeartButton extends HookConsumerWidget {
ref,
track.id!,
onMutate: (isLiked) {
savedTracks.setData(
[
if (isLiked == true)
...?savedTracks.data?.where((element) => element.id != track.id)
else
...?savedTracks.data?..add(track)
],
);
print("Toggle Like onMutate: $isLiked");
if (isLiked) {
savedTracks.setData(
savedTracks.data
?.where((element) => element.id != track.id)
.toList() ??
[],
);
} else {
savedTracks.setData(
[
...?savedTracks.data,
track,
],
);
}
return isLiked;
},
onData: (data, recoveryData) async {
print("Toggle Like onData: $data");
await savedTracks.refresh();
},
onError: (payload, isLiked) {
print("Toggle Like onError: $payload");
if (!mounted()) return;
savedTracks.setData([
if (isLiked != true)
...?savedTracks.data?.where((element) => element.id != track.id)
else
...?savedTracks.data?..add(track),
]);
if (isLiked != true) {
savedTracks.setData(
savedTracks.data
?.where((element) => element.id != track.id)
.toList() ??
[],
);
} else {
savedTracks.setData(
[
...?savedTracks.data,
track,
],
);
}
},
);
@ -113,21 +136,21 @@ class TrackHeartButton extends HookConsumerWidget {
@override
Widget build(BuildContext context, ref) {
final savedTracks =
useQueries.playlist.tracksOfQuery(ref, "user-liked-tracks");
final toggler = useTrackToggleLike(track, ref);
if (toggler.me.isLoading || !toggler.me.hasData) {
final savedTracks = useQueries.playlist.likedTracksQuery(ref);
final (:me, :isLiked, :toggleTrackLike) = useTrackToggleLike(track, ref);
if (me.isLoading || !me.hasData) {
return const CircularProgressIndicator();
}
return HeartButton(
tooltip: toggler.isLiked
tooltip: isLiked
? context.l10n.remove_from_favorites
: context.l10n.save_as_favorite,
isLiked: toggler.isLiked,
isLiked: isLiked,
onPressed: savedTracks.hasData
? () {
toggler.toggleTrackLike.mutate(toggler.isLiked);
toggleTrackLike.mutate(isLiked);
}
: null,
);

View File

@ -55,7 +55,12 @@ class PlaylistView extends HookConsumerWidget {
final mediaQuery = MediaQuery.of(context);
final meSnapshot = useQueries.user.me(ref);
final tracksSnapshot = useQueries.playlist.tracksOfQuery(ref, playlist.id!);
final playlistTrackSnapshot =
useQueries.playlist.tracksOfQuery(ref, playlist.id!);
final likedTracksSnapshot = useQueries.playlist.likedTracksQuery(ref);
final tracksSnapshot = playlist.id! == "user-liked-tracks"
? likedTracksSnapshot
: playlistTrackSnapshot;
final isPlaylistPlaying = useMemoized(
() => proxyPlaylist.collections.contains(playlist.id!),

View File

@ -1,4 +1,5 @@
import 'dart:io';
import 'dart:math';
import 'package:catcher/catcher.dart';
import 'package:fl_query/fl_query.dart';
@ -145,23 +146,47 @@ class PlaylistQueries {
);
}
Future<List<Track>> likedTracks(
SpotifyApi spotify,
WidgetRef ref,
) async {
final tracks = await spotify.tracks.me.saved.all();
return tracks.map((e) => e.track!).toList();
}
Query<List<Track>, dynamic> likedTracksQuery(WidgetRef ref) {
final query = useCallback((spotify) => likedTracks(spotify, ref), []);
final context = useContext();
return useSpotifyQuery<List<Track>, dynamic>(
"user-liked-tracks",
query,
jsonConfig: JsonConfig(
toJson: (tracks) => <String, dynamic>{
'tracks': tracks.map((e) => e.toJson()).toList(),
},
fromJson: (json) => (json['tracks'] as List)
.map(
(e) => Track.fromJson((e as Map).castKeyDeep<String>()),
)
.toList(),
),
refreshConfig: RefreshConfig.withDefaults(
context,
// will never make it stale
staleDuration: const Duration(days: 60),
),
ref: ref,
);
}
Future<List<Track>> tracksOf(
String playlistId,
SpotifyApi spotify,
WidgetRef ref,
) {
if (playlistId == "user-liked-tracks") {
return spotify.tracks.me.saved
.all()
.then(
(tracks) => tracks.map((e) => e.track!).toList(),
)
.catchError((e) {
final isLoggedIn = ref.read(AuthenticationNotifier.provider) != null;
if (e is SocketException && isLoggedIn) return <Track>[];
throw e;
});
}
) async {
if (playlistId == "user-liked-tracks") return <Track>[];
return spotify.playlists.getTracksByPlaylistId(playlistId).all().then(
(value) => value.toList(),
);
@ -174,22 +199,6 @@ class PlaylistQueries {
return useSpotifyQuery<List<Track>, dynamic>(
"playlist-tracks/$playlistId",
(spotify) => tracksOf(playlistId, spotify, ref),
jsonConfig: playlistId == "user-liked-tracks"
? JsonConfig(
toJson: (tracks) => <String, dynamic>{
'tracks': tracks.map((e) => e.toJson()).toList()
},
fromJson: (json) => (json['tracks'] as List)
.map((e) => Track.fromJson(
(e as Map).castKeyDeep<String>(),
))
.toList(),
)
: null,
retryConfig: RetryConfig.withConstantDefaults(
maxRetries: 1,
retryDelay: const Duration(seconds: 5),
),
ref: ref,
);
}

View File

@ -1,4 +1,5 @@
import 'package:fl_query/fl_query.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:spotify/spotify.dart';
import 'package:spotube/hooks/use_spotify_query.dart';
@ -8,6 +9,8 @@ import 'package:spotube/utils/type_conversion_utils.dart';
class UserQueries {
const UserQueries();
Query<User?, dynamic> me(WidgetRef ref) {
final context = useContext();
return useSpotifyQuery<User, dynamic>(
"current-user",
(spotify) async {
@ -26,6 +29,11 @@ class UserQueries {
}
return me;
},
refreshConfig: RefreshConfig.withDefaults(
context,
// will never make it stale
staleDuration: const Duration(days: 60),
),
ref: ref,
);
}