unstatedで作ったグローバルStateは、アプリを終了したら失われますが、これを永続化することでアプリを次に起動したときも前のデータをキープします。
この記事は連載です。親記事はこちら
前提
前回までに、請求書情報をサーバーから取得して表示できるようになったので、今回はサーバーから取得した後と、データを修正した時に、その結果得られるデータ全体を永続化します。
- Macを使います。でもWindows/Linuxでも同じで大丈夫なはず。
- React Nativeの前回までの内容の続きです。
- 前回までに、React Navigation V5 と unstated と Native Base を導入済みです。
永続化には、react-native-community/async-storageを使います。これを使うことで、アプリからスマホ内にデータを保存できます。Expo版詳細はこちら。
async-storageのインストール
react-native-community/async-storageをインストール。
$ expo install @react-native-community/async-storage
unstatedのコンテナに保存と読み込みメソッドを追加
ローカルストレージにデータを保存するsetAndSaveState()と、読み込むload()を作ります。コードは本家サンプルからコピーして、自前Stateに合致するように少し修正。
import AsyncStorage from '@react-native-community/async-storage';
...
export default class InvoiceContainer extends Container {
...
// Save data to the local storage, then setState.
setStateAndSave = async updateStates => {
try {
for (var k in updateStates) {
await AsyncStorage.setItem(k, JSON.stringify(updateStates[k]));
}
this.setState(updateStates);
} catch (error) {
// Error saving data
console.log("storage error");
}
};
// Load data from the local storage
load = async () => {
try {
const value = await AsyncStorage.getItem("data");
if (value !== null) {
// Data found
this.setState({ data: JSON.parse(value) });
} else {
this.setState({ data: this.getEmptyData() });
}
} catch (error) {
// Error retrieving data
console.log("storage error");
}
};
... AsyncStorageで保管できるのは文字列だけなので、DataオブジェクトをJSON文字列に変換して保存し、読み出し字はその逆を実施します。ポイントは以下の部分です。
保存:単にsave()とすると、保存のタイミング制御が難しくなるので、setState()と動作を組み合わせたsetStateAndSave()としています。こうしないと、呼び出し側でsetState()した直後にsave()したくなりますが、setState()した直後は実際にはStateが更新されていないため、直前の状態を保存してしまう問題が発生します。この問題を意識させないため、setStateと保存を同時に行うメソッドを準備します。中のコードは単純で、指定されたStateについてローカルに保存してからsetStateしています。awaitが入っているので、保存に失敗するとsetStateに到達せず、State自体の書き換えも行われない、つまりユーザー側から見ても保存失敗が結果として見える、というのがポイントです。
for (var k in updateStates) {
await AsyncStorage.setItem(k, JSON.stringify(updateStates[k]));
}
this.setState(updateStates);読み込み:setStateを使っていることに注意。
const value = await AsyncStorage.getItem('data');
...
this.setState({data:JSON.parse(value)});保存したいタイミングのコーディング
サーバーからデータを取得した直後や、アプリ内でデータを変更したときに、上で作ったsetAndSaveState()を呼びます。
サーバーからデータを取得した時:setStateをsetStateAndSaveに置き換えるだけです。isDataLoadingは永続化したくないのでsetStateのままであることに注意してください。
getDataFromServer() {
this.setState({ isDataLoading: true });
axios
.get(INVOICE_API_ENDPOINT, { params: {} })
.then((results) => {
console.log("HTTP Request succeeded.");
console.log(results);
this.setStateAndSave({ data: results.data });
this.setState({ isDataLoading: false });
})
.catch(() => {
console.log("HTTP Request failed.");
this.setState({ isDataLoading: false });
});
}データを修正した時:setStateをsetStateAndSaveに置き換えるだけです。
class SummaryScreenContent extends React.Component {
render() {
return (
<View style={styles.container}>
<Text>Summary Screen</Text>
<Button
title="Modify Inv#2"
onPress={() => {
let data = this.props.globalState.state.data;
data.invoices[1].date = "2/2/2020";
this.props.globalState.setStateAndSave({ data: data });
}}
/>
</View>
);
}
}読み込みたいタイミングのコーディング
アプリを起動したときに、以前のデータを読み込むべきなので、グローバルStateを作った時=unstatedコンテナのインスタンスを作った時に、load()を呼びます。
render() {
...
let globalState = new InvoiceContainer({ initialSeeding: true });
globalState.load();
return (
<Provider inject={[globalState]}>
<NavigationContainer>
<RootStack />
</NavigationContainer>
</Provider>
);
}実行すると、最初はInvoiceがない状態になります。

一度データをサーバーからインポート。

一度デバイスを振って(Device -> Shake)Expoメニューを出し、Reloadすると、

起動直後からさきほどのデータが見えます。
