[React Native] AppState로 앱이 종료되어도, 데이터들을 살리자.

mainsain·2023년 9월 20일

React Native

목록 보기
7/9
post-thumbnail

앱을 종료했을때, 토큰 및 데이터가 사라진다.

AppState · React Native

앱의 현재 상태가 Background인지, Foreground인지 체크를 한 뒤, 상황에 맞게 SecureStore에 담아 저장하고 꺼내사용하자.

공식문서 사용예시

import React, {useRef, useState, useEffect} from 'react';
import {AppState, StyleSheet, Text, View} from 'react-native';

const AppStateExample = () => {
  const appState = useRef(AppState.currentState);
  const [appStateVisible, setAppStateVisible] = useState(appState.current);

  useEffect(() => {
    const subscription = AppState.addEventListener('change', nextAppState => {
      if (
        appState.current.match(/inactive|background/) &&
        nextAppState === 'active'
      ) {
        console.log('App has come to the foreground!');
      }

      appState.current = nextAppState;
      setAppStateVisible(appState.current);
      console.log('AppState', appState.current);
    });

    return () => {
      subscription.remove();
    };
  }, []);

  return (
    <View style={styles.container}>
      <Text>Current state is: {appStateVisible}</Text>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
  },
});

export default AppStateExample;
  • AppState의 addEventListner의 상태를 체크해서, 지금이 background인지를 체크하고있다.
  • 상태를 계속 업데이트해주며 사용한 데이터는 제거해준다.

우리 프로젝트에 적용해보자.

  1. 현재 AccessToken, RefreshToken은 SecureStore에 저장되어 앱이 강제종료해도 데이터는 남아있다.
  2. 그러나 전역 상태관리 Store들은, mobx에서 저장되어있지 SecureStore에는 저장되어 있지 않은 상태이다.
  3. 이 데이터들을 앱을 종료해도 기억해둬야 한다. 틀린 예시지만, 이해하기 쉬운 예시로 위젯이 있다.(실제론 android에서 따로 저장을 한다) 그 위젯을 mobx에서 데이터를 관리한다고 했을 때, 앱을 종료한다면 그 데이터들은 사라지게된다.
  4. 이를 방지하고자, 앱을 종료하면 mobx → SecureStore, 앱을 다시 키면 SecureStore → mobx 를 하는 방식으로 진행한다.

코드 작성

import React, { useRef, useEffect } from "react";
import { AppState } from "react-native";
import IndexStore from "../stores/IndexStore";
import * as SecureStore from "expo-secure-store";

const CurrentAppState = () => {
	const appState = useRef(AppState.currentState);
	const stores = IndexStore();

	useEffect(() => {
		secureStorageToStore();

		const subscription = AppState.addEventListener("change", (nextAppState) => {
			if (appState.current.match(/background/) && nextAppState === "active") {
				secureStorageToStore();
			} else if (appState.current.match(/active/) && nextAppState === "background") {
				storeToSecureStorage();
			}
			appState.current = nextAppState;
		});

		return () => {
			if (subscription) {
				subscription.remove();
			}
		};
	}, []);

	const secureStorageToStore = async () => {
		for (let key in stores) {
			const storeData = await SecureStore.getItemAsync(key);
			if (storeData) {
				const parsedData = JSON.parse(storeData);
				Object.assign(stores[key], parsedData);
			}
		}
	};

	const storeToSecureStorage = async () => {
		for (let key in stores) {
			await SecureStore.setItemAsync(key, JSON.stringify(stores[key]));
		}
	};

	return null;
};

export default CurrentAppState;

초반부분은 똑같다. 현 상태가 background인지, foreground인지 체크해주고, 데이터들을 옮기는 함수를 호출한다. (inactive는 iOS다.)

stores의 key를 가져오고, 그 key를 기준으로 데이터를 Secure에 stringify해서 담는다.

반대의 경우도 똑같다. 저장된 데이터들을 파싱해서 stores[key]에 파싱된 데이터들을 덮어쓴다.

그러면 store의 수가 많아져도, 코드의 로직은 변함이 없다. 그렇게 작성했다.

적용

CurrentAppState 컴포넌트를, 모든 페이지에서 적용되게 해야한다.

현재 우리 프로젝트의 최상위 파일은, App.tsx이다.

...
import CurrentAppState from "./src/components/CurrentAppState";

const App = () => {
	const Stack = createNativeStackNavigator();

	Sentry.init({
		dsn: sentry_dsn,
		tracesSampleRate: 1.0,
	});

	return (
		<>
			<CurrentAppState />
			<NavigationContainer>
				<Stack.Navigator initialRouteName="Main">
					<Stack.Screen name="Main" component={Main} />
					<Stack.Screen name="Album" component={Album} />
					<Stack.Screen name="SideMenu" component={SideMenu} />
...

CurrentAppState 컴포넌트가 다 적용되게 위치시켜준다.

AppState코드는, 계속해서 바뀔 것 같다. 활용처가 많아보인다.

profile
새로운 자극을 주세요.

0개의 댓글