Improve performance of app - 1.182 🚀

The App.tsx had a bunch of separate useState calls which would
cause unneccesary re-renders of the entire app. This became
apparent after adding the global progress bar, since it caused
even more re-renders to the point of being unusable.
This commit is contained in:
Brandon Presley 2023-11-13 17:33:32 +13:00
parent 49646c3107
commit 6950cd04f4
7 changed files with 115 additions and 87 deletions

114
App.tsx
View File

@ -1,27 +1,22 @@
import { import {
NavigationContainer,
DarkTheme as NavigationDarkTheme, DarkTheme as NavigationDarkTheme,
DefaultTheme as NavigationDefaultTheme, DefaultTheme as NavigationDefaultTheme,
NavigationContainer,
} from "@react-navigation/native"; } from "@react-navigation/native";
import React, { useEffect, useMemo, useState } from "react"; import React, { useEffect, useMemo, useState } from "react";
import { useColorScheme } from "react-native"; import { useColorScheme } from "react-native";
import { import {
MD3DarkTheme as PaperDarkTheme, MD3DarkTheme as PaperDarkTheme,
MD3LightTheme as PaperDefaultTheme, MD3LightTheme as PaperDefaultTheme,
ProgressBar,
Provider as PaperProvider, Provider as PaperProvider,
Snackbar,
} from "react-native-paper"; } from "react-native-paper";
import MaterialIcon from "react-native-vector-icons/MaterialCommunityIcons"; import MaterialIcon from "react-native-vector-icons/MaterialCommunityIcons";
import AppSnack from "./AppSnack";
import AppStack from "./AppStack"; import AppStack from "./AppStack";
import TimerProgress from "./TimerProgress";
import { AppDataSource } from "./data-source"; import { AppDataSource } from "./data-source";
import { settingsRepo } from "./db"; import { settingsRepo } from "./db";
import { emitter } from "./emitter";
import { TickEvent } from "./TimerPage";
import { TOAST } from "./toast";
import { ThemeContext } from "./use-theme"; import { ThemeContext } from "./use-theme";
import Settings from "./settings";
import { MARGIN } from "./constants";
export const CombinedDefaultTheme = { export const CombinedDefaultTheme = {
...NavigationDefaultTheme, ...NavigationDefaultTheme,
@ -43,61 +38,54 @@ export const CombinedDarkTheme = {
const App = () => { const App = () => {
const phoneTheme = useColorScheme(); const phoneTheme = useColorScheme();
const [initialized, setInitialized] = useState(false);
const [snackbar, setSnackbar] = useState("");
const [appTheme, setAppTheme] = useState("system");
const [progress, setProgress] = useState(0);
const [settings, setSettings] = useState<Settings>();
const [lightColor, setLightColor] = useState<string>( const [appSettings, setAppSettings] = useState({
CombinedDefaultTheme.colors.primary startup: undefined,
); theme: "system",
lightColor: CombinedDefaultTheme.colors.primary,
darkColor: CombinedDarkTheme.colors.primary,
});
const [darkColor, setDarkColor] = useState<string>( console.log("Rerendered App");
CombinedDarkTheme.colors.primary
);
useEffect(() => { useEffect(() => {
(async () => { (async () => {
if (!AppDataSource.isInitialized) await AppDataSource.initialize(); if (!AppDataSource.isInitialized) await AppDataSource.initialize();
const gotSettings = await settingsRepo.findOne({ where: {} }); const gotSettings = await settingsRepo.findOne({ where: {} });
console.log({ gotSettings }); console.log({ gotSettings });
setSettings(gotSettings); setAppSettings({
setAppTheme(gotSettings.theme); startup: gotSettings.startup,
if (gotSettings.lightColor) setLightColor(gotSettings.lightColor); theme: gotSettings.theme,
if (gotSettings.darkColor) setDarkColor(gotSettings.darkColor); lightColor: gotSettings.lightColor,
setInitialized(true); darkColor: gotSettings.darkColor,
});
})(); })();
const descriptions = [
emitter.addListener(TOAST, ({ value }: { value: string }) => {
setSnackbar(value);
}),
emitter.addListener("tick", (event: TickEvent) => {
setProgress((Number(event.minutes) * 60 + Number(event.seconds)) / 210);
}),
];
return () => descriptions.forEach((description) => description.remove());
}, []); }, []);
const paperTheme = useMemo(() => { const paperTheme = useMemo(() => {
const darkTheme = lightColor const darkTheme = appSettings.lightColor
? { ? {
...CombinedDarkTheme, ...CombinedDarkTheme,
colors: { ...CombinedDarkTheme.colors, primary: darkColor }, colors: {
...CombinedDarkTheme.colors,
primary: appSettings.darkColor,
},
} }
: CombinedDarkTheme; : CombinedDarkTheme;
const lightTheme = lightColor const lightTheme = appSettings.lightColor
? { ? {
...CombinedDefaultTheme, ...CombinedDefaultTheme,
colors: { ...CombinedDefaultTheme.colors, primary: lightColor }, colors: {
...CombinedDefaultTheme.colors,
primary: appSettings.lightColor,
},
} }
: CombinedDefaultTheme; : CombinedDefaultTheme;
let value = phoneTheme === "dark" ? darkTheme : lightTheme; let value = phoneTheme === "dark" ? darkTheme : lightTheme;
if (appTheme === "dark") value = darkTheme; if (appSettings.theme === "dark") value = darkTheme;
else if (appTheme === "light") value = lightTheme; else if (appSettings.theme === "light") value = lightTheme;
return value; return value;
}, [phoneTheme, appTheme, lightColor, darkColor]); }, [phoneTheme, appSettings]);
return ( return (
<PaperProvider <PaperProvider
@ -105,46 +93,26 @@ const App = () => {
settings={{ icon: (props) => <MaterialIcon {...props} /> }} settings={{ icon: (props) => <MaterialIcon {...props} /> }}
> >
<NavigationContainer theme={paperTheme}> <NavigationContainer theme={paperTheme}>
{initialized && ( {appSettings.startup !== undefined && (
<ThemeContext.Provider <ThemeContext.Provider
value={{ value={{
theme: appTheme, theme: appSettings.theme,
setTheme: setAppTheme, setTheme: (theme) => setAppSettings({ ...appSettings, theme }),
lightColor, lightColor: appSettings.lightColor,
setLightColor, setLightColor: (color) =>
darkColor, setAppSettings({ ...appSettings, lightColor: color }),
setDarkColor, darkColor: appSettings.darkColor,
setDarkColor: (color) =>
setAppSettings({ ...appSettings, darkColor: color }),
}} }}
> >
<AppStack settings={settings} /> <AppStack startup={appSettings.startup} />
</ThemeContext.Provider> </ThemeContext.Provider>
)} )}
</NavigationContainer> </NavigationContainer>
<Snackbar <AppSnack textColor={paperTheme.colors.background} />
duration={3000} <TimerProgress />
onDismiss={() => setSnackbar("")}
visible={!!snackbar}
action={{
label: "Close",
onPress: () => setSnackbar(""),
textColor: paperTheme.colors.background,
}}
>
{snackbar}
</Snackbar>
{progress > 0 && (
<ProgressBar
style={{
position: "absolute",
bottom: MARGIN / 2,
left: MARGIN,
right: MARGIN,
}}
progress={progress}
/>
)}
</PaperProvider> </PaperProvider>
); );
}; };

View File

@ -1,6 +1,6 @@
import { createDrawerNavigator } from "@react-navigation/drawer"; import { createDrawerNavigator } from "@react-navigation/drawer";
import { StackScreenProps } from "@react-navigation/stack";
import { IconButton } from "react-native-paper"; import { IconButton } from "react-native-paper";
import { DrawerParams } from "./drawer-param-list";
import ExerciseList from "./ExerciseList"; import ExerciseList from "./ExerciseList";
import GraphsList from "./GraphsList"; import GraphsList from "./GraphsList";
import InsightsPage from "./InsightsPage"; import InsightsPage from "./InsightsPage";
@ -8,20 +8,19 @@ import PlanList from "./PlanList";
import SetList from "./SetList"; import SetList from "./SetList";
import SettingsPage from "./SettingsPage"; import SettingsPage from "./SettingsPage";
import TimerPage from "./TimerPage"; import TimerPage from "./TimerPage";
import useDark from "./use-dark";
import WeightList from "./WeightList"; import WeightList from "./WeightList";
import Settings from "./settings"; import { DrawerParams } from "./drawer-param-list";
import { StackScreenProps } from "@react-navigation/stack"; import useDark from "./use-dark";
const Drawer = createDrawerNavigator<DrawerParams>(); const Drawer = createDrawerNavigator<DrawerParams>();
interface AppDrawerParams { interface AppDrawerParams {
settings: Settings; startup: string;
} }
export default function AppDrawer({ export default function AppDrawer({
route, route,
}: StackScreenProps<{ settings: AppDrawerParams }>) { }: StackScreenProps<{ startup: AppDrawerParams }>) {
const dark = useDark(); const dark = useDark();
return ( return (
@ -31,9 +30,7 @@ export default function AppDrawer({
swipeEdgeWidth: 1000, swipeEdgeWidth: 1000,
headerShown: false, headerShown: false,
}} }}
initialRouteName={ initialRouteName={(route.params.startup || "Home") as keyof DrawerParams}
(route.params.settings.startup || "Home") as keyof DrawerParams
}
> >
<Drawer.Screen <Drawer.Screen
name="Home" name="Home"

33
AppSnack.tsx Normal file
View File

@ -0,0 +1,33 @@
import React, { useEffect, useState } from "react";
import { Snackbar } from "react-native-paper";
import { emitter } from "./emitter";
import { TOAST } from "./toast";
export default function AppSnack({ textColor }: { textColor: string }) {
const [snackbar, setSnackbar] = useState("");
useEffect(() => {
const description = emitter.addListener(
TOAST,
({ value }: { value: string }) => {
setSnackbar(value);
}
);
return description.remove;
}, []);
return (
<Snackbar
duration={3000}
onDismiss={() => setSnackbar("")}
visible={!!snackbar}
action={{
label: "Close",
onPress: () => setSnackbar(""),
textColor,
}}
>
{snackbar}
</Snackbar>
);
}

View File

@ -50,7 +50,7 @@ export type StackParams = {
const Stack = createStackNavigator<StackParams>(); const Stack = createStackNavigator<StackParams>();
export default function AppStack({ settings }: { settings: Settings }) { export default function AppStack({ startup }: { startup: string }) {
return ( return (
<Stack.Navigator <Stack.Navigator
screenOptions={{ headerShown: false, animationEnabled: false }} screenOptions={{ headerShown: false, animationEnabled: false }}
@ -58,7 +58,7 @@ export default function AppStack({ settings }: { settings: Settings }) {
<Stack.Screen <Stack.Screen
name="Drawer" name="Drawer"
component={AppDrawer} component={AppDrawer}
initialParams={{ settings }} initialParams={{ startup }}
/> />
<Stack.Screen name="EditSet" component={EditSet} /> <Stack.Screen name="EditSet" component={EditSet} />
<Stack.Screen name="EditSets" component={EditSets} /> <Stack.Screen name="EditSets" component={EditSets} />

30
TimerProgress.tsx Normal file
View File

@ -0,0 +1,30 @@
import { useEffect, useState } from "react";
import { emitter } from "./emitter";
import { TickEvent } from "./TimerPage";
import { ProgressBar } from "react-native-paper";
export default function TimerProgress() {
const [progress, setProgress] = useState(0);
useEffect(() => {
const description = emitter.addListener(
"tick",
({ minutes, seconds }: TickEvent) => {
setProgress((Number(minutes) * 60 + Number(seconds)) / 210);
}
);
return description.remove;
}, []);
if (progress === 0) return null;
return (
<ProgressBar
style={{
position: "absolute",
bottom: 0,
}}
progress={progress}
/>
);
}

View File

@ -85,8 +85,8 @@ android {
applicationId "com.massive" applicationId "com.massive"
minSdkVersion rootProject.ext.minSdkVersion minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 36207 versionCode 36208
versionName "1.181" versionName "1.182"
} }
signingConfigs { signingConfigs {
release { release {

View File

@ -1,6 +1,6 @@
{ {
"name": "massive", "name": "massive",
"version": "1.181", "version": "1.182",
"private": true, "private": true,
"license": "GPL-3.0-only", "license": "GPL-3.0-only",
"scripts": { "scripts": {