筋トレ大学生によるアウトプット雑記

筋トレや読書のことをはじめとして、プログラミングその他日常生活における様々なことについて書いていきます。

Output Blog

主に筋トレ・プログラミングについて書きます

React Native入門⑧ AsyncStorageを使って「後で読む」機能を実装する

スポンサーリンク

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>
  )
}

鬼長いんですが、意外とやってることは単純です。
次の節で解説します。

動作結果と解説

React Native入門⑧ AsyncStorageを使って「後で読む」機能を実装する
React Native入門⑧ AsyncStorageを使って「後で読む」機能を実装する

たぶん大体上記のようになるはず、、
画面全体は僕が作ったプログラムのスクショなので、上記ソースとは完全一致しませんので、ご注意ください。

AsyncStorageで、stockボタンを押したらsetItemするようにしています。
また、stockListページでは、すべてのキーを持ってきてすべてのアイテムに対してgetItemをかけ、表示するようにします。

deleteボタンを押した後などには、画面更新をかけるようにしています。

すべて削除ボタン押下時には、確認ポップアップの表示を行い、その下のレイヤーに1枚TouchableOpacityを入れることによって、そこをタップした場合ポップアップが消える設定となっています。

ていうくらいですかね。
特に難しいことはないと思います。

まとめ

AsyncStorageの使い方と、それを応用したアプリ開発例を紹介しました。
ぜひ作ってみてくださいね。

スポンサーリンク

//タブメニュー