Nous allons créer une application React Native qui permet de lire ou écrire un fichier dans un espace de stockage interne ou externe. Avoir accès au fichier du système peut être intéressant pour sauvegarder les données d’une session de l’application à une autre ou pour utiliser ou modifier les données d’un fichier.
Matériel
- Appareil Android
- Ordinateur pour la programmation
- Un câble USB pour connecter l’appareil Android au PC
Configuration du projet React Native
Pour venir lire et écrire dans un fichier nous allons utiliser la librairie File System, react-native-fs
npm install --save react-native-fs
Utilisation de librairie
A partir de la librairie, nous importons les objets et fonctions qui nous intéresse
import { DocumentDirectoryPath, writeFile, readDir, readFile, unlink } from 'react-native-fs'; //send files for testing
- DocumentDirectoryPath permet de récupérer le chemin d’accès des fichiers de l’application
- writeFile permet d’écrire dans un fichier
- readDir permet de lire un répertoire
- readFile permet de lire un fichier
- unkink permet de supprimer un fichier
Nous créons ensuite un composant fonctionnel avec les états désirés
const FileSysComp = () => { const [dirPath, setDirPath] = useState(DocumentDirectoryPath); const [filename, setFilename] = useState("myfile.txt"); const [textToWrite, setTextToWrite] = useState(""); const [textFromFile, setTextFromFile] = useState(""); const [fileInDir, setFileInDir] = useState([]);
Nous ajoutons ensuite les fonctions utiles pour le système de fichier lire, écrire, supprimer
const makeFile = async (filePath : string, content : string) => { try { //create a file at filePath. Write the content data to it await writeFile(filePath, content, "utf8"); console.log("written to file"); } catch (error) { //if the function throws an error, log it out. console.log(error); } }; const getFile = async (filePath : string) => { try{ const response = await readFile(filePath); setTextFromFile(response) console.log("File read : ",response) } catch (error) { console.log(error); } }; const deleteFile = async (filePath : string) => { try { await unlink(filePath); //delete the item present at 'path' console.log("deleted : ",filePath); } catch (error) { console.log(error); } };
Lors de la création du composant, nous venons lire le répertoire défini par défaut
useEffect(() => { /* file system*/ console.log("FS directory path : ",dirPath); readDir(dirPath).then((files) => { console.log("files on FS: ",files); setFileInDir(files); }); }, [filename,dirPath]);
Enfin, nous créons le rendu de l’application avec les éléments suivant
- barre d’entrée pour spécifier le répertoire
- barre d’entrée pour spécifier le nom du fichier
- zone de texte pour le texte à écrire
- bouton Write pour écrire le texte dans le fichier
- zone de texte pour afficher les fichiers et sous-répertoires contenu dans le répertoire
- bouton Read pour lire le texte contenu dans le fichier
- zone de texte pour afficher le texte lu
- bouton Delete pour effacer le fichier spécifié
return ( <View style={styles.mainBody}> <Text style={styles.mainTitle}> AC File System </Text> <View style={styles.inputBar}> <TextInput style={styles.textInput} placeholder="Directory" value={dirPath} onChangeText={setDirPath} /> </View> <View style={styles.inputBar}> <TextInput style={styles.textInput} placeholder="Filename" value={filename} onChangeText={setFilename} /> </View> <View style={styles.inputBar}> <TextInput style={styles.textInput} placeholder="Text to write" value={textToWrite} onChangeText={setTextToWrite} /> <TouchableOpacity onPress={() => makeFile(dirPath + "/" + filename, textToWrite)} style={[styles.sendButton]}> <Text style={styles.buttonText}> WRITE </Text> </TouchableOpacity> </View> <Text>Directory:</Text> <ScrollView style={styles.textOutput}> {fileInDir.length>0 ? fileInDir.map((filedir, idx) => ( <Text key={idx}>{filedir.name}</Text> )) : <Text>Folder empty or unknown</Text>} </ScrollView> <View style={{ flexDirection: 'row', justifyContent:'space-between'}}> <Text>Text from file:</Text> <TouchableOpacity onPress={() => getFile(dirPath + "/" + filename)} style={[styles.sendButton]}> <Text style={styles.buttonText}> READ </Text> </TouchableOpacity> </View> <ScrollView style={styles.textOutput}> {textFromFile ? <Text>{textFromFile}</Text> : null} </ScrollView> <TouchableOpacity onPress={() => deleteFile(dirPath + "/" + filename)} style={[styles.sendButton]}> <Text style={styles.buttonText}> DELETE </Text> </TouchableOpacity> </View> );
Résultat
Grâce à cette application, nous pouvons afficher le contenu d’un répertoire, lire, écrire et supprimer un fichier
Lire et écrire un fichier dans un espace de stockage externe
Par défaut, une application n’a d’autorité que sur son propre répertoire
Il est possible de donner les droits d’accès à un espace de stockage externe comme une clé USB.
Pour se faire, vous pouvez accorder les droits depuis l’interface Android. Aller dans les paramètres > App et notifications > Gestionnaire des autorisations > Fichiers et contenus multimédia
Brancher la clé USB à votre appareil, à l’aide d’adb repérer le chemin d’accès de l’espace de stockage dans ls /storage/ (ici: /storage/70A8-C229)
Une fois l’autorisation activée, vous pourrez afficher et créer des fichiers sur la clés USB
Code complet de gestion de fichier avec React Native
/** * https://www.npmjs.com/package/react-native-fs?activeTab=readme * https://github.com/itinance/react-native-fs */ import React, {useState, useEffect, useCallback} from 'react'; import { View, ScrollView, Text, TextInput, TouchableOpacity, StyleSheet, Alert} from 'react-native'; import { DocumentDirectoryPath, writeFile, readDir, readFile, unlink } from 'react-native-fs'; //send files for testing let storagePath = "/storage/70A8-C229" /** * Maincomp */ const FileSysComp = () => { const [dirPath, setDirPath] = useState(storagePath) //DocumentDirectoryPath); const [filename, setFilename] = useState("myfile.txt"); const [textToWrite, setTextToWrite] = useState(""); const [textFromFile, setTextFromFile] = useState(""); const [fileInDir, setFileInDir] = useState([]); const makeFile = async (filePath : string, content : string) => { try { //create a file at filePath. Write the content data to it await writeFile(filePath, content, "utf8"); console.log("written to file"); } catch (error) { //if the function throws an error, log it out. console.log(error); Alert.alert("Cannot write file. Permission denied") } }; const getFile = async (filePath : string) => { try{ const response = await readFile(filePath); setTextFromFile(response) console.log("File read : ",response) } catch (error) { console.log(error); } }; const deleteFile = async (filePath : string) => { try { await unlink(filePath); //delete the item present at 'path' console.log("deleted : ",filePath); } catch (error) { console.log(error); } }; useEffect(() => { /* file system*/ console.log("FS directory path : ",dirPath); readDir(dirPath).then((files) => { console.log("files on FS: ",files); setFileInDir(files); }).catch((e) =>{ console.log("foler does not exist"); setFileInDir([]) }); }, [filename,dirPath]); return ( <View style={styles.mainBody}> <Text style={styles.mainTitle}> AC File System </Text> <View style={styles.inputBar}> <TextInput style={styles.textInput} placeholder="Directory" value={dirPath} onChangeText={setDirPath} /> </View> <View style={styles.inputBar}> <TextInput style={styles.textInput} placeholder="Filename" value={filename} onChangeText={setFilename} /> </View> <View style={styles.inputBar}> <TextInput style={styles.textInput} placeholder="Text to write" value={textToWrite} onChangeText={setTextToWrite} /> <TouchableOpacity onPress={() => makeFile(dirPath + "/" + filename, textToWrite)} style={[styles.sendButton]}> <Text style={styles.buttonText}> WRITE </Text> </TouchableOpacity> </View> <Text>Directory:</Text> <ScrollView style={styles.textOutput}> {fileInDir.length>0 ? fileInDir.map((filedir, idx) => ( <Text key={idx}>{filedir.name}</Text> )) : <Text>Folder empty or unknown</Text>} </ScrollView> <View style={{ flexDirection: 'row', justifyContent:'space-between'}}> <Text>Text from file:</Text> <TouchableOpacity onPress={() => getFile(dirPath + "/" + filename)} style={[styles.sendButton]}> <Text style={styles.buttonText}> READ </Text> </TouchableOpacity> </View> <ScrollView style={styles.textOutput}> {textFromFile ? <Text>{textFromFile}</Text> : null} </ScrollView> <TouchableOpacity onPress={() => deleteFile(dirPath + "/" + filename)} style={[styles.sendButton]}> <Text style={styles.buttonText}> DELETE </Text> </TouchableOpacity> </View> ); } export default FileSysComp; let BACKGROUND_COLOR = "#161616"; //191A19 let BUTTON_COLOR = "#346751"; //1E5128 let ERROR_COLOR = "#C84B31"; //4E9F3D let TEXT_COLOR = "#ECDBBA"; //D8E9A8 var styles = StyleSheet.create({ mainBody: { flex: 1, justifyContent: 'center', backgroundColor: BACKGROUND_COLOR}, mainTitle:{ color: TEXT_COLOR, fontSize: 30, textAlign: 'center', borderBottomWidth: 2, borderBottomColor: ERROR_COLOR, }, backgroundVideo: { borderWidth: 2, borderColor: 'red', position: 'absolute', top: 0, left: 0, bottom: 0, right: 0, }, webVideo: { borderWidth: 2, borderColor: 'green', position: 'absolute', top: 0, left: 0, bottom: 0, right: 0, }, buttonText: { color: TEXT_COLOR, fontWeight: 'bold', fontSize: 12, textAlign: 'center', textAlignVertical: 'center', }, sendButton: { backgroundColor: BUTTON_COLOR, padding: 15, borderRadius: 15, margin: 2, paddingHorizontal: 20, right:0, }, deviceItem: { flexDirection: 'row', flex: 3, marginBottom: 2, }, deviceName: { fontSize: 14, fontWeight: 'bold', }, deviceInfo: { fontSize: 8, }, deviceButton: { backgroundColor: '#2196F3', padding: 10, borderRadius: 10, margin: 2, paddingHorizontal: 20, }, inputBar:{ flexDirection: 'row', justifyContent: 'space-between', margin: 5, }, textInput:{ backgroundColor: '#888888', margin: 2, borderRadius: 15, flex:3, }, textOutput:{ backgroundColor: '#333333', margin: 10, borderRadius: 2, borderWidth: 1, borderColor: '#EEEEEE', textAlignVertical: 'top', minHeight : 50, } });