Subir archivos puede llegar a ser un problema en especial cuando se hace desde una aplicación móvil, donde tenemos diferentes dispositivos, marcas y sistemas operativos.
En esta ocasión te voy a mostrar cómo resolví la necesidad de subir videos hacia tu Backend desde una aplicación creada en React Native con Expo.
Frontend
Primero vamos a crear una aplicación de React Native utilizando el CLI de Expo (o puedes seguir los pasos e integrarlo en tu proyecto, verás que es muy simple).
npm install --global expo-cli
expo init upload-file-expo
Utilizare la configuración Blank con TypeScript.
Una vez que tengamos nuestra aplicación vamos a instalar la siguiente dependencia. En mi caso quiero subir un video que esté en la galería del usuario, por lo que necesitare instalar ImagePicker de expo.
expo install expo-image-picker
Añadimos la librería a nuestro app.json
{
"expo": {
"plugins": [
[
"expo-image-picker",
{
"photosPermission": "The app accesses your photos to let you share them with your friends."
}
]
]
}
}
Una vez tengamos este Set-Up inicial crearemos nuestros componentes o páginas donde vamos a implementar nuestra nueva funcionalidad.
Creamos el handle para nuestro boton Select video, el propósito de este onPress sera solicitar al usuario permisos para acceder a su galería y una vez estos sean otorgados podremos seleccionar un video.
import React from "react";
import {
Alert,
TouchableOpacity,
StyleSheet,
Text,
View } from "react-native";
export default function App() {
const getPermissions = async () => {
...
};
return(
<View>
<TouchableOpacity
onPress={()=> getPermissions())}
style={[styles.primaryButton, { marginVertical: "3%" }]}
>
<Text style={styles.buttonText}>
Select video
</Text>
</TouchableOpacity>
</View>
);
};
const styles = StyleSheet.create({
container: {
padding: "5%",
flex: 1,
backgroundColor: "#fff",
alignItems: "center",
justifyContent: "center",
},
buttonText: {
fontSize: 20,
fontWeight: "500",
},
primaryButton: {
justifyContent: "center",
alignItems: "center",
width: "100%",
backgroundColor: "#00ED64",
borderWidth: 1,
borderColor: "transparent",
borderRadius: 10,
paddingVertical: 10,
paddingHorizontal: 40,
}
}
El metodo requestMediaLibraryPermissionsAsync de ExpoImagePicker, nos devuelve una Promesa y va aconsultar si el usuario tiene permisos o no. Si no los tiene lo va a solicitar.
import * as ImagePicker from "expo-image-picker";
export default function App() {
const getPermissions = async () => {
try {
const library = await ImagePicker.requestMediaLibraryPermissionsAsync();
if (!library.granted) {
Alert.alert("Ups!", "We need permision for this action");
return;
};
searchVideo();
} catch (err) {}
};
return(
...
);
};
Una vez que qué library.granted sea true llamaremos a la función searchVideo para que el usuario pueda seleccionar un video de su galería.
Aquí haremos uso del método launchImageLibraryAsync el cual nos abrirá la librería del dispositivo y podremos seleccionar nuestro vídeo. Este método recibe un objeto de configuración al cual le indicaremos que tipo de archivo queremos seleccionar.
Una vez que obtengamos una respuesta vamos a guardar la uri (esta es una url del video en el dispositivo) en un state.
import React, { useState } from "react";
export default function App() {
const [video, setVideo] = useState<string>("");
const getPermissions = async () => {
...
};
const searchVideo = async () => {
const res = await ImagePicker.launchImageLibraryAsync({
mediaTypes: ImagePicker.MediaTypeOptions.Videos,
});
if (!res.cancelled) {
setVideo(res.uri);
}
};
return(
...
);
};
En mi app yo estoy utilizando la librería VideoAV de Expo para poder visualizar el video en la aplicación.
expo install expo-av
Para esto creare un componente que recibirá la uri del video a reproducir.
import React, { useRef, FC } from "react";
import { View } from "react-native";
import { Video as VideoPlayer } from "expo-av";
const Video: FC<{ uri: string }> = ({ uri }) => {
const videoRef = useRef(null);
return (
<View>
<VideoPlayer
resizeMode="contain"
style={{ width: 300, height: 400 }}
ref={videoRef}
source={{ uri: uri }}
useNativeControls
/>
</View>
);
};
export default Video;
Esto le permite al usuario seleccionar otro video y finalmente subirlo.
export default function App() {
const [video, setVideo] = useState<string>("");
const getPermissions = async () => {
...
};
const searchVideo = async () => {
...
};
return(
<View style={styles.container}>
{video && <Video uri={video} />}
<TouchableOpacity
onPress={()=> (!video ? getPermissions() : handelUploade(video))}
style={[styles.primaryButton, { marginVertical: "3%" }]}
>
<Text style={styles.buttonText}>
{!video ? "Select video" : "Upload video"}
</Text>
</TouchableOpacity>
{video && (
<TouchableOpacity onPress={searchVideo} style={styles.secondaryButton}>
<Text style={styles.buttonText}>Select other video</Text>
</TouchableOpacity>
)}
</View>
);
};
const styles = StyleSheet.create({
...
secondaryButton: {
justifyContent: "center",
alignItems: "center",
width: "100%",
borderWidth: 2,
borderColor: "#00ED64",
borderRadius: 10,
paddingVertical: 10,
paddingHorizontal: 40,
},
});
Ahora para poder subir el video vamos a requerir utilizar la librería FileSystem de Expo esta nos permitirá, entre otras cosas, subir archivos con su método uploadAsync. Para ello crearemos una función para esta tarea.
expo install expo-file-system
Este método requiere 3 parámetros, el primero es la url de tu api, el segundo la uri del archivo que deseas subir, en nuestro caso el video, y por último un objeto de configuración. A este último le tendremos que agregar el método HTTP (POST), el tipo de subida que será MULTIPART, esto quiere decir que va a subir el archivo por partes (Buffer), y el fieldName que será el nombre que espera el backend (recomiendo tenerlo en ambas partes como file).
const handelUploade = async (uri: string) => {
try {
const headers = {
Accept: "application/json",
"Content-Type": "multipart/form-data",
};
let videoUri;
if (Platform.OS === "android") {
videoUri = uri.split("file").join("content");
} else {
videoUri = uri;
}
const res = await FileSystem.uploadAsync(
"https://your-api.com/uploadVideo",
videoUri,
{
httpMethod: "POST",
uploadType: FileSystemUploadType.MULTIPART,
fieldName: "file",
headers: headers,
}
);
if (res) {
return res;
}
} catch (error) {
Alert.alert("Somthing is wrong");
}
};
Tambien es necesario que modifiquemos la URI dependiendo el sistema operativo, si es Android el metodo no funcionara.
Y eso sería todo por el lado del FrontEnd.
Backend
Pequeño disclaimer antes de continuar, voy a crear un servicio en Nest.js donde implementare el File upload con MULTIPART según su documentación, pero también está su equivalencia en otros lenguajes y frameworks como Spring Boot.
Primero crearemos una aplicación de Nest con su CLI.
npm i -g @nestjs/cli
nest new project-name
Una vez creado el proyecto, vamos a instalar los types para typescript, en este caso Nest ya contiene por defecto el FileInterceptor
npm i -D @types/multer
Ahora importamos las dependencias en el código, para efectos prácticos de este tutorial voy a seguir con todo en el SetUp en el mismo Controller.
import {
Controller,
Get,
Post,
UploadedFile,
UseInterceptors,
} from "@nestjs/common";
import { FileInterceptor } from "@nestjs/platform-express";
import { Express } from "express";
import fs from "fs";
Implementaremos el FileInterceptor en un POST que será nuestro endpoint para subir archivos.
@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
@Public()
@Post('uploadVideo')
@UseInterceptors(FileInterceptor('file'))
async uploadFile(@UploadedFile() file: Express.Multer.File) {
if (file) {
fs.writeFileSync(
`/Users/gaspar/Documents/UploadedFiles/${file.filename}.mp4`,
file.buffer,
);
}
}
}
File, cómo recuerdas, es el objeto que nosotros estaremos esperando con nuestro Buffer por lo que debe ser el mismo nombre que configuramos en nuestra React Native App.
En mi caso yo solo guardare el archivo en mi computadora ya que es un ejemplo, pero podrias manipularlo según tu necesidad
Conclusiones
Ahora ya sabes como puedes subir videos desde tu app móvil de Expo, pero este ejemplo puede ser aplicado también en fotos o cualquier otro tipo de archivo. Te recomiendo limitar el tamaño de los videos ya que la subida de archivos depende del dispositivo del usuario y su internet, esto puede tardar un tiempo considerable e interferir en la experiencia del usuario.