Vamos criar um aplicativo React Native que permite ler ou gravar um arquivo em um espaço de armazenamento interno ou externo. Ter acesso ao arquivo do sistema pode ser útil para salvar dados de uma sessão do aplicativo para outra, ou para usar ou modificar os dados em um arquivo.
Hardware
- Dispositivo Android
- Computador para programação
- Um cabo USB para ligar o dispositivo Android ao PC
Configurar o projeto React Native
Para ler e escrever num ficheiro, vamos utilizar a biblioteca do Sistema de Ficheiros, react-native-fs
npm install --save react-native-fs
Utilizar uma biblioteca
A partir da biblioteca, importamos os objectos e funções que nos interessam
import { DocumentDirectoryPath, writeFile, readDir, readFile, unlink } from 'react-native-fs'; //send files for testing
- DocumentDirectoryPath é utilizado para obter o caminho para os ficheiros da aplicação
- writeFile permite-lhe escrever para um ficheiro
- readDir lê um diretório
- readFile lê um ficheiro
- unkink apaga um ficheiro
Em seguida, criamos um componente funcional com os estados desejados
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([]);
Em seguida, adicionamos as funções úteis para o sistema de ficheiros read, write, delete
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); } };
Quando o componente é criado, lemos o diretório predefinido
useEffect(() => { /* file system*/ console.log("FS directory path : ",dirPath); readDir(dirPath).then((files) => { console.log("files on FS: ",files); setFileInDir(files); }); }, [filename,dirPath]);
Por fim, criamos a renderização da aplicação com os seguintes elementos
- barra de entrada para especificar o diretório
- barra de entrada para especificar o nome do ficheiro
- zona de texto para o texto a escrever
- Botão Escrever para escrever o texto no ficheiro
- caixa de texto para mostrar os ficheiros e subdirectórios contidos no diretório
- Botão Ler para ler o texto contido no ficheiro
- zona de texto para visualizar o texto lido
- Botão Apagar para apagar o ficheiro especificado
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> );
Resultados
Com esta aplicação, podemos visualizar o conteúdo de um diretório e ler, escrever e apagar um ficheiro.
Ler e escrever um ficheiro num espaço de armazenamento externo
Por defeito, uma aplicação só tem autoridade sobre o seu próprio diretório
É possível atribuir direitos de acesso a um espaço de armazenamento externo, como uma chave USB.
Para tal, pode conceder direitos a partir da interface Android. Vá a Definições > Aplicações e notificações > Gestor de permissões > Ficheiros e conteúdos multimédia
Ligue a pen USB ao seu dispositivo, utilizando o adb para encontrar o caminho para o espaço de armazenamento em ls /storage/ (ex /storage/70A8-C229)
Uma vez activada a autorização, poderá visualizar e criar ficheiros na pen USB.
Código de gestão de ficheiros completo com 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, } });