React Native入門⑧ AsyncStorageを使って「後で読む」機能を実装する
スポンサーリンク
React Nativeについて知りたいですか?
本記事では、AsyncStorageを使った「後で読む」機能の実装を解説します。
アプリ開発に興味のある方必見です。
是非ご覧ください。
AsyncStorageとは?
AsyncStorageとは、データを端末に永続的に保存するライブラリの1つです。React Nativeでは、データの保存方法として主要なものは
・FirebaseなどのDBを使うパターン
・AsyncStorageで端末にそのまま保存するパターン
の2つがあります。
🚧 AsyncStorage · React Native
主な使い方などはこちらに記載されています。
基本的に、setItemしてgetItemするだけの簡単な仕組みです。
実際に使ってみる
import * as React from 'react'; import { FlatList, View, ScrollView, Text, ActivityIndicator, RefreshControl, TouchableOpacity, AsyncStorage } from 'react-native'; import { WebView } from 'react-native-webview'; import { ListItem, Avatar, Icon } from 'react-native-elements'; import { NavigationContainer} from '@react-navigation/native'; import { createStackNavigator } from '@react-navigation/stack'; import { createMaterialTopTabNavigator } from '@react-navigation/material-top-tabs'; function NewsList({route,navigation}) { const [data, setData] = React.useState([]); const [load, isLoaded] = React.useState(false); function getNews() { var url = 'https://newsapi.org/v2/top-headlines?' + 'country=jp&' + 'apiKey=ccc238cd0003440592c82d3926899a61&'; fetch(url) .then(response => response.json()) .then(result => { setData(result.articles); isLoaded(true); }); } React.useEffect(() => { getNews(); }, []); const keyExtractor = (item, index) => index.toString(); const renderItem = ({ item }) => ( <ListItem onPress={() => { navigation.navigate('Web', { url: item.url, title: item.title }); }} title={item.title} subtitle={() => <Text>{item.publishedAt.substr(0, 10)}</Text>} leftAvatar={() => { if (item.urlToImage !== null) { return <Avatar size="large" source={{ uri: item.urlToImage }} />; } else { return ( <Avatar size="large" overlayContainerStyle={{ backgroundColor: '#555' }} rounded icon={{ name: 'file-o', color: '#ffe', type: 'font-awesome' }} /> ); } }} rightElement={() => { <TouchableOpacity style={{borderRadius:12,backgroundColor:"#5bc0de",padding:5}} onPress={async () => { try{ await AsyncStorage.setItem(item.title, JSON.stringify(item) ) }catch(error) { console.error(error); } }} ><Text style={{color:"white"}}>Stock</Text></TouchableOpacity> } } bottomDivider chevron /> ); if (!load) { return ( <View style={{ flex: 1, justifyContent: 'center' }}> <ActivityIndicator size="large" color="#0000ff" /> </View> ); } return ( <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}> <View style={{ width: '100%' }}> <FlatList keyExtractor={keyExtractor} data={data} renderItem={renderItem} /> </View> </View> ); } function showNews({navigation,route}) { return ( <WebView source={{ uri: route.params.url }} /> ) } function StockList({navigation}) { const [stockNews,setStock] = React.useState([]) const [needRefresh,setNeedRefresh] = React.useState(false) const [load,isLoad] = React.useState(true) const [modalVisible,setModalVisible] = React.useState(false) function getStocks() { AsyncStorage.getAllKeys((err,keys)=> { if(err) { console.error(err) } else { AsyncStorage.multiGet(keys,(err,datas) => { setStock([]) datas.map((result,i,data) => { try { const get = JSON.parse(result[1]) if(get.title === undefined) { setStock((stockNews) => (stockNews).concat(get.item)) } else { setStock((stockNews) => (stockNews).concat(get)) } } catch(e) { } }) //setNeedRefresh(true); }) } }) isLoad(false) } React.useEffect(() => { const unsubscribe = navigation.addListener('focus', () => { onRefresh(); }); return unsubscribe; }, [navigation]); const keyExtractor = (item,index) => index.toString(); const renderItem = ({item}) => ( <ListItem onPress={() => {navigation.navigate("Web",{url:item.url,title:item.title})}} title={item.title} subtitle={() => { let tmp = new String(item.publishedAt) return(<Text>{(tmp).substr(0,10)}</Text>) } } leftAvatar={() => { if(item.urlToImage !== null){ return(<Avatar size="large" source={{uri:item.urlToImage}}/>) } else { return(<Avatar size="large" overlayContainerStyle={{backgroundColor:"#555"}} rounded icon={{name:'file-o',color:"#ffe",type:"font-awesome"}}/>) } } } rightElement={() => ( <TouchableOpacity style={{borderRadius:12,backgroundColor:"#d9534f",padding:5}} onPress={() => { AsyncStorage.removeItem(item.title); onRefresh(); }} ><Text style={{color:"white"}}>Delete</Text></TouchableOpacity> ) } bottomDivider/> ) const onRefresh = React.useCallback(() => { setNeedRefresh(true); getStocks(); setNeedRefresh(false); },[needRefresh]); return ( <ScrollView style={{ width:"100%",height:"100%"}} refreshControl={ <RefreshControl refreshing={needRefresh} onRefresh={()=>onRefresh()} /> } > <Modal animationType="slide" transparent={true} visible={modalVisible} onRequestClose={() => { setModalVisible(!modalVisible) }} > <TouchableOpacity style={{width:"100%",height:"100%",justifyContent:"center",alignItems:"center"}} activeOpacity={1} onPressOut={() => setModalVisible(!modalVisible)} > <View style={{alignItems:"center",justifyContent:"center"}}></View> <View style={{ margin:20, backgroundColor: "#555", borderRadius: 20, padding: 35, alignItems: "center", shadowColor: "#000", shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.25, shadowRadius: 3.84, elevation: 5 }}> <Avatar size="large" icon={{name:'warning',color:"yellow",type:"font-awesome"}}/> <Text style={{color:"white"}}>すべて削除しますか?</Text> <View style={{marginTop:8,flexDirection:"row"}}> <TouchableOpacity style={{ backgroundColor:"#d9534f",height:50,width:80,margin:3,padding:5,borderRadius:10,justifyContent:"center",alignItems:"center" }} onPress={() => { AsyncStorage.clear(); onRefresh(); setModalVisible(!modalVisible) }} > <Text style={{color:"white",textAlign:"center"}}>すべて削除</Text> </TouchableOpacity> <TouchableOpacity style={{ backgroundColor:"#5cb85c",height:50,width:80,margin:3,padding:5,borderRadius:10,justifyContent:"center",alignItems:"center" }} onPress={() => setModalVisible(!modalVisible)} > <Text style={{color:"white",textAlign:"center"}}>戻る</Text> </TouchableOpacity> </View> </View> </TouchableOpacity> </Modal> <View style={{alignItems:"center",justifyContent:"center"}}> <TouchableOpacity style={{ borderRadius:12,backgroundColor:"#d9534f",padding:20,width:"50%",margin:8, alignItems:"center",justifyContent:"center" }} onPress={() => setModalVisible(!modalVisible)} > <Text style={{color:"white",textAlign:"center"}}>すべて削除</Text> </TouchableOpacity> </View> <FlatList data={stockNews} keyExtractor={keyExtractor} renderItem={renderItem} /> </ScrollView> ) const NewsStack = createStackNavigator(); const createTab = createMaterialTopTabNavigator(); function stackFirstRoot() { return ( <NewsStack.Navigator> <NewsStack.Screen name="News" component={MaterialRoot} options={{ headerStyle:{ backgroundColor:"rgb(0,175,236)" }, headerTintColor:"white", headerTitleStyle:{ fontWeight:"bold" }, headerTitleAlign:"center", }}/> <NewsStack.Screen name="NewsList" component={NewsList} options={{ headerStyle:{ backgroundColor:"rgb(0,175,236)" }, headerTintColor:"white", headerTitleStyle:{ fontWeight:"bold" }, headerTitleAlign:"center" }} /> <NewsStack.Screen name="Web" component={showNews} options={{ headerStyle:{ backgroundColor:"rgb(0,175,236)" }, headerTintColor:"white", headerTitleStyle:{ fontWeight:"bold" }, headerTitleAlign:"center" }}/> </NewsStack.Navigator> ) } export default function MaterialRoot() { return ( <NavigationContainer> <createTab.Navigator tabBarOptions={{ indicatorStyle:{ backgroundColor:"rgb(0,175,236)" } }} > <createTab.Screen name="Categories" component={stackFirstRoot} /> <createTab.Screen name="StockList" component={StockList} /> </createTab.Navigator> </NavigationContainer> ) }
鬼長いんですが、意外とやってることは単純です。
次の節で解説します。
動作結果と解説
たぶん大体上記のようになるはず、、
画面全体は僕が作ったプログラムのスクショなので、上記ソースとは完全一致しませんので、ご注意ください。
AsyncStorageで、stockボタンを押したらsetItemするようにしています。
また、stockListページでは、すべてのキーを持ってきてすべてのアイテムに対してgetItemをかけ、表示するようにします。
deleteボタンを押した後などには、画面更新をかけるようにしています。
すべて削除ボタン押下時には、確認ポップアップの表示を行い、その下のレイヤーに1枚TouchableOpacityを入れることによって、そこをタップした場合ポップアップが消える設定となっています。
ていうくらいですかね。
特に難しいことはないと思います。
まとめ
AsyncStorageの使い方と、それを応用したアプリ開発例を紹介しました。ぜひ作ってみてくださいね。