From 8f976a2aaa2bfc151d30a566e6ea38eddf6dedab Mon Sep 17 00:00:00 2001 From: riteshshukla04 Date: Thu, 14 Aug 2025 22:20:04 +0530 Subject: [PATCH] Perf-1 --- src/components/Albums/component.tsx | 19 ++++++++------- src/components/Library/component.tsx | 1 + src/components/Tracks/component.tsx | 35 ++++++++++++++++++---------- src/constants/query-client.ts | 16 ++++++------- src/providers/Library/index.tsx | 8 +++++++ 5 files changed, 50 insertions(+), 29 deletions(-) diff --git a/src/components/Albums/component.tsx b/src/components/Albums/component.tsx index 09dd5498..e7fc4591 100644 --- a/src/components/Albums/component.tsx +++ b/src/components/Albums/component.tsx @@ -37,6 +37,15 @@ export default function Albums({ return 'album' }, []) + // Memoize expensive stickyHeaderIndices calculation to prevent unnecessary re-computations + const stickyHeaderIndices = React.useMemo(() => { + if (!showAlphabeticalSelector || !albums) return [] + + return albums + .map((album, index) => (typeof album === 'string' ? index : 0)) + .filter((value, index, indices) => indices.indexOf(value) === index) + }, [showAlphabeticalSelector, albums]) + return ( : null} ItemSeparatorComponent={() => } refreshControl={} - stickyHeaderIndices={ - showAlphabeticalSelector - ? albums - ?.map((album, index, albums) => - typeof album === 'string' ? index : 0, - ) - .filter((value, index, indices) => indices.indexOf(value) === index) - : [] - } + stickyHeaderIndices={stickyHeaderIndices} removeClippedSubviews /> diff --git a/src/components/Library/component.tsx b/src/components/Library/component.tsx index ba6d9dfe..568aad83 100644 --- a/src/components/Library/component.tsx +++ b/src/components/Library/component.tsx @@ -31,6 +31,7 @@ export default function Library({ tabBarLabelStyle: { fontFamily: 'Figtree-Bold', }, + lazy: true, // Enable lazy loading to prevent all tabs from mounting simultaneously }} > BaseItemDto[] = useCallback(() => { + // Memoize the expensive tracks processing to prevent memory leaks + const tracksToDisplay = React.useMemo(() => { if (filterDownloaded) { return ( downloadedTracks @@ -54,6 +55,24 @@ export default function Tracks({ return tracks?.pages.flatMap((page) => page) ?? [] }, [filterDownloaded, downloadedTracks, tracks, filterFavorites]) + // Memoize key extraction for FlashList performance + const keyExtractor = React.useCallback((item: BaseItemDto) => item.Id!, []) + + // Memoize render item to prevent recreation + const renderItem = React.useCallback( + ({ index, item: track }: { index: number; item: BaseItemDto }) => ( + + ), + [navigation, tracksToDisplay, queue], + ) + return ( } numColumns={1} - data={tracksToDisplay()} - renderItem={({ index, item: track }) => ( - - )} + data={tracksToDisplay} + keyExtractor={keyExtractor} + renderItem={renderItem} onEndReached={() => { if (hasNextPage) fetchNextPage() }} diff --git a/src/constants/query-client.ts b/src/constants/query-client.ts index f9306050..83d9d25e 100644 --- a/src/constants/query-client.ts +++ b/src/constants/query-client.ts @@ -3,9 +3,8 @@ import { QueryClient } from '@tanstack/react-query' /** * A global instance of the Tanstack React Query client * - * Garbage collection is disabled by default, as we are using MMKV - * as a client persister. This may need to be re-evaluated - * at some point if storage usage becomes a problem + * Memory management optimized for mobile devices to prevent memory buildup + * while still maintaining good performance with MMKV persistence * * Default stale time is set to 1 hour. Users have the option * to refresh relevant datasets by design (i.e. refreshing @@ -15,15 +14,16 @@ export const queryClient = new QueryClient({ defaultOptions: { queries: { /** - * Infinity, this needs to be greater than - * or higher than the `maxAge` set in App.tsx + * 30 minutes GC time for better memory management + * This prevents excessive memory usage while still keeping + * recent data in memory for performance */ - gcTime: Infinity, + gcTime: 1000 * 60 * 30, // 30 minutes /** - * 2 hours as a default. + * 1 hour as a default - reduced from 2 hours for better battery usage */ - staleTime: 1000 * 60 * 60 * 2, // 2 hours + staleTime: 1000 * 60 * 60, // 1 hour retry(failureCount, error) { if (failureCount > 2) return false diff --git a/src/providers/Library/index.tsx b/src/providers/Library/index.tsx index c9317fc2..837e492f 100644 --- a/src/providers/Library/index.tsx +++ b/src/providers/Library/index.tsx @@ -123,6 +123,8 @@ const LibraryContextInitializer = () => { ), select: selectArtists, initialPageParam: 0, + staleTime: QueryConfig.staleTime.oneDay, // Cache for 1 day to reduce network requests + gcTime: QueryConfig.staleTime.oneWeek, // Keep in memory for 1 week getNextPageParam: (lastPage, allPages, lastPageParam, allPageParams) => { return lastPage.length === QueryConfig.limits.library ? lastPageParam + 1 : undefined }, @@ -152,6 +154,8 @@ const LibraryContextInitializer = () => { sortDescending ? SortOrder.Descending : SortOrder.Ascending, ), initialPageParam: 0, + staleTime: QueryConfig.staleTime.oneDay, // Cache for 1 day to reduce network requests + gcTime: QueryConfig.staleTime.oneWeek, // Keep in memory for 1 week getNextPageParam: (lastPage, allPages, lastPageParam, allPageParams) => { console.debug(`Tracks last page length: ${lastPage.length}`) return lastPage.length === QueryConfig.limits.library * 2 @@ -182,6 +186,8 @@ const LibraryContextInitializer = () => { initialPageParam: alphabet[0], select: (data) => data.pages.flatMap((page) => [page.title, ...page.data]), maxPages: alphabet.length, + staleTime: QueryConfig.staleTime.oneDay, // Cache for 1 day to reduce network requests + gcTime: QueryConfig.staleTime.oneWeek, // Keep in memory for 1 week getNextPageParam: (lastPage, allPages, lastPageParam, allPageParams) => { console.debug(`Albums last page length: ${lastPage.data.length}`) if (lastPageParam !== alphabet[alphabet.length - 1]) { @@ -219,6 +225,8 @@ const LibraryContextInitializer = () => { queryFn: () => fetchUserPlaylists(api, user, library), select: (data) => data.pages.flatMap((page) => page), initialPageParam: 0, + staleTime: QueryConfig.staleTime.oneDay, // Cache for 1 day to reduce network requests + gcTime: QueryConfig.staleTime.oneWeek, // Keep in memory for 1 week getNextPageParam: (lastPage, allPages, lastPageParam, allPageParams) => { return lastPage.length === QueryConfig.limits.library ? lastPageParam + 1 : undefined },