Tatehitoの技術メモ

ソフトウェアエンジニアです。いろいろ書きます。

【Expo + React Native】画像をアップロードせずにデバイス内に保存する方法

子供と一緒に遊べる『シルエットクイズ』アプリをつくっています。単純に画像を切り替えるだけのアプリですが、画像をサーバーにアップロードすることなく登録したく、その方法について調査した結果をメモしておきます。

  • expo version "42.0.3"
  • react-native version "0.63.2"

expo-file-system を使う

docs.expo.dev

Expoで提供されているexpo-file-system を使うとデバイスのファイルシステムにアクセスできます。これを使って実装してみました。

expo-file-systemで作成できるディレクトリは2種類あり、用途に合わせて使い分けが必要です。

  • FileSystem.documentDirectory:アプリによって明示的に削除するまで残る
  • FileSystem.cacheDirectory:保存容量が少なくなるとシステムによって自動的に削除される

カメラロールから画像を選んでFileSystemに保存するコード

実際に実装したコードから抜粋したものですが、雰囲気は伝わると思います。ポイントは FileSystem.makeDirectoryAsync でディレクトリを作成する点と、FileSystem.copyAsync で画像を保存する点です。

import * as FileSystem from 'expo-file-system'
import * as ImagePicker from 'expo-image-picker'
import React, { useState, useEffect } from 'react'
import { View, Image } from 'react-native'

export default function () {
  const tempDir = FileSystem.cacheDirectory + 'silhouette-quiz/'
  const [questionImage, setQuestionImage] = useState()

  useEffect(() => {
    FileSystem.getInfoAsync(tempDir).then((dirInfo) => {
      if (!dirInfo.exists) {
        // tempDirが無ければ作成する
        FileSystem.makeDirectoryAsync(documentDir, { intermediates: true })
      }
    })

    if (Platform.OS !== 'web') {
      ImagePicker.requestMediaLibraryPermissionsAsync().then((status) => {
        if (!status.granted) {
          alert('Sorry, we need camera roll permissions to make this work!')
        }
      })
    }
  }, [])

  const handleClickQuestionImagePickButton = async () => {
    const result = await ImagePicker.launchImageLibraryAsync({
      mediaTypes: ImagePicker.MediaTypeOptions.Images,
      allowsEditing: true,
      aspect: [4, 3],
      quality: 1,
    })

    if (!result.cancelled) {
      // カメラロールから選択した画像をファイルシステムに保存する
      const to = tempDir + 'question_image'
      await FileSystem.copyAsync({
        from: result.uri,
        to,
      })
      setQuestionImage(to)
    }
  }

  return (
    <View>
      <Image source={{ uri: questionImage }} />
    </View>
  )
}