Aitor Sánchez - Blog - Oct. 27, 2023, 10:39 a.m.
¿Necesitas que tu usuario pueda enviar, o recibir, archivos por internet desde tu app? O, quizás, ya sabes cómo hacerlo y solo te falta algún dato sobre alguna función o campo de FileTransfer en Ionic ¿verdad?
Mira,
Mi nombre es Aitor Sánchez, soy desarrollador de apps desde el 2014, y en el artículo de hoy te enseñaré, paso a paso, cómo puede descargar y enviar archivos desde tu aplicación mediante una conexión http y la librería File Transfer de Ionic. Así que pilla sitio que empezamos...
Pero antes de continuar, esta es la Flutter Mafia. Es mi newsletter donde aprenderás desarrollo de apps móviles, aso y monetización junto con otros genietes que ya están dentro. Y si te suscribes te regalo mi ebook "Duplica los ingreso de tus apps en 5 minutos" No es broma, quizás te interese.
Como con todos los componentes externos al core que vas a utilizar, tienes que instalar tanto el plugin para la comunicación nativa, como el código de abstracción para que puedas enviar y recibir información desde tu código TypeScrypt.
$ ionic cordova plugin add cordova-plugin-file-transfer
$ npm install --save @awesome-cordova-plugins/file-transfer
En la primera línea, instalas el plugin de cordova para la comunicación nativa con el dispositivo de tu usuario.
La segunda línea es para instalar las dependencias y que puedas, desde tu código TS, acceder a la entrada y salida del módulo desde tu código.
Y para terminar con la instalación necesitarás, en tu archivo appmodule, incluir dentro de los provider el componente File Transfer de la siguiente manera:
Nota: Recuerda que, si usas una versión superior a Ionic 3, y el módulo NGX, esto no es necesario que lo hagas.
import { FileTransfer } from '@awesome-cordova-plugins/file-transfer/ngx';
...
@NgModule({
...
//Aquí es donde tenemos que meter la clase para que la podamos usar.
providers: [
...
FileTransfer
...
]
...
})
export class AppModule { }
Vale, menos trabajo a realizar. Este módulo, como muchos otros, no necesita una configuración previa. Así que te ahorras este paso. ¿Qué guay no? ;)
Vale, aquí empieza lo bueno. Te enseño un ejemplo, que en la mayoría de las ocasiones es bastante más ilustrativo.
Usaré un código de ejemplo que te servirá perfectamente para subir (upload), o bajar (download), contenido a una api mediante File Transfer.
Aquí vamos:
import { FileTransfer, FileUploadOptions, FileTransferObject } from '@awesome-cordova-plugins/file-transfer/ngx'; //Si usas NGX
import { FileTransfer, FileUploadOptions, FileTransferObject } from '@ionic-native/file-transfer'; //Si no usas NGX
import { File } from '@ionic-native/file';
//Iyectamos la clase en el componente y se lo asignamos al campo de clase transfer
//El file es necesario para poder enviar el archivo solamente, en futuros tutoriales hablaremos del esta clase.
constructor(private transfer: FileTransfer, private file: File) { }
...
//Creamos la instancia del fileTransfer a transfer con un voley.
const fileTransfer: FileTransferObject = this.transfer.create();
// Subida del archivo.
fileTransfer.upload(..).then(..).catch(..);
// Descarga del archivo.
fileTransfer.download(..).then(..).catch(..);
// Esta función aborta la transferencia en curso.
fileTransfer.abort();
// Ejemplo completo
upload() {
let options: FileUploadOptions = {
fileKey: 'file',
fileName: 'name.jpg',
headers: {}
.....
}
fileTransfer.upload('<file path>', '<api endpoint>', options)
.then((data) => {
// success
}, (err) => {
// error
})
}
download() {
const url = 'http://www.example.com/file.pdf';
fileTransfer.download(url, this.file.dataDirectory + 'file.pdf').then((entry) => {
console.log('download complete: ' + entry.toURL());
}, (error) => {
// Controlamos el error aquí.
});
}
UoUo, cuanto código, madre mía. No pensaba que fuera a salir tanto… Una cosa más, si en algún momento te hace falta subir contenido desde la web, este código también te sirve para AngularJS en gran medida, quizás no todo, pero gran parte sí.
En primer lugar tienes que importar los componentes "FileTransfer", "FileUploadOptions", "FileTransferObject" y "File" de las librerías "@Ionic-native/file-transfer" y "@Ionic-native/file".
Ahora, como era de esperar, tienes que hacer las inyecciones de dependencias en tu clase.
Las que vas a inyectar son: FileTransfer y File. Las otras que he nombrado arriba son interfaces necesarias para el uso y no es necesario que las cargues en el constructor.
No voy a explicar más código que el que hemos explicado ya. He intentado ser lo más ilustrativo posible y creo que es suficiente.
Nota: Este tuto necesita una dependencia que aún no está descrita. Para poder almacenar un archivo descargado dentro de nuestro teléfono necesitamos saber usar la gestión de almacenamiento en Ionic. Pero tranquilo, hare un tutorial dentro de muy poco.
Es más, pondré aquí un enlace para que lo veas directamente (Si el enlace aún no está, es que el tuto no está realizado aún. Así que sí necesitas esta información ya, en el blog, al menos de momento, no la vas a encontrar, lo siento :P).
Te dejo un enlace a la página oficial mientras cocino el tutorial: https://ionicframework.com/docs/native/file/
Ahora vamos a ver más información sobre los componentes que hemos usado para llevar a cabo este ejemplo y que te quede todo más o menos claro.
Es posible que mientras vayas programando la app, te des cuenta de que el sistema de permisos (permissions) de lectura/escritura de archivos no funcione cómo esperas. Para darle solución lo que tienes que hacer es pedir los permisos de manera explícita al usuario en el momento que los necesitemos.
Se hace así:
this.androidPermissions.checkPermission(this.androidPermissions.PERMISSION.READ_EXTERNAL_STORAGE).then( (result) => {
if (result.hasPermission) {
// Nuestro código
} else {
this.androidPermissions.requestPermission(this.androidPermissions.PERMISSION.READ_EXTERNAL_STORAGE).then(result => {
if (result.hasPermission) {
// code
}
});
}
},
err => this.androidPermissions.requestPermission(this.androidPermissions.PERMISSION.READ_EXTERNAL_STORAGE)
);
Sigamos...
Esta clase cuenta con dos miembros imprescindibles para poder hacer funcionar todo el tinglado al pie de la letra.
Ahora vamos a ver las clases adicionales que hemos usado
Bueno, quizás esto daría para algo más que un capítulo por muchos motivos. Pero el principal es, que no se puede. He estado mirando a ver si estaba equivocado por todo internet, pero no, no se puede usar con Base64 por lo menos hasta el momento.
Me explico mejor. FileTransfer no utiliza contenido almacenado en ram, hace uso de contenidos almacenados en de manera persistente en dispositivo. Esto es lo que manda y recibe.
Básicamente, pilla el archivo, le saca los Bytes y después los manda mediante un multipart/form-data para enviarlos. Y para descargar, igual que lo hace todo el mundo, lee el buffer de entrada y lo va escribiendo en un archivo local.
No vamos a parar a explicar aquí cómo funciona la tecnología Blob, daría para un tuto entero, pero vamos, que no se puede realizar.
Vale, esta parte me habéis pedido que la aclarase más. Pues bien, aquí vamos.
En las aplicaciones móviles todo lo que tenga que ver con la cámara es demandado ya de por si. Entonces, una de las cosas que no se puede quedar fuera es enseñaros cómo se puede enviar el contenido a server directamente después de tomar una foto. Pues lo tenemos que hacer de la siguiente manera:
En primer lugar, vamos a programar una función para tomar la imagen desde la cámara. Si aún no sabes cómo se usa la cámara, aquí tienes nuestro tutorial para aprender a utilizarla cómo un pro :)
pickPicture(){
Camera.getPicture({
destinationType: Camera.DestinationType.DATA_URL,
sourceType : Camera.PictureSourceType.PHOTOLIBRARY,
mediaType: Camera.MediaType.PICTURE
}).then((imageData) => {
// imageData es una cadena codificada en base64
this.base64Image = "data:image/jpeg;base64," + imageData;
}, (err) => {
console.log(err);
});
}
Ahora podemos hacer dos cosas.
this.http.post("http://nuestroserver.com", this.base64Image)
.map((res:Response) => res.json())
.catch((error:any) => Observable.throw(error.json().error || 'Server error'));
Bien, tendríamos que codificar en la parte de arriba el parse para crear el archivo. Pero bueno, ahí está. Se puede de las dos maneras.
Pero si tuviese que elegir una, me quedo con la primera. Básicamente por que enviar el archivo entero, tienes un retorno con datos, en PHP se controlar desde los $_FILE, y vamos que se hace cómo toda la vida. Por lo menos desde que yo llevo programando :)
Este es un error bastante común en las compilaciones en Android. El porqué, es muy sencillo. Es por que nos falta que instalar el plugin WhiteList. Se hace de la siguiente manera:
$ cordova plugin add cordova-plugin-whitelist
Y posteriormente tienes que agregar esto en archivo xml de configuración:
<access origin="*" subdomains="true" />
<allow-navigation href="http://*/*" />
<allow-navigation href="https://*/*" />
<allow-intent href="http://*/*" />
<allow-intent href="https://*/*" />
Te explico. En Android, en las últimas versione de SO, han agregado un sistema de seguridad para que el dispositivo no permita enviar peticiones http fuera de unos dominios que tenemos que definir en un archivo. Vamos que, si no se ha especificado el dominio ahí, no te va a dejar tirar una query a ese dominio, o sub-dominio.
Está bien, esto es un problema que llevo arrastrando bastante tiempo. Yo lo sufro más porque al programar en nativo es un poco más complejo, pero el hecho de enviar un archivo mediante FileTransfer con más parámetros (with data) se debe hacer de la siguiente manera:
var uploadOptions = {
fileKey: "file", // Es la KEY desde la que se va a recuperar el archivo sobre el server.
chunkedMode: false, // Agregar chunkedMode, sirve para decir si la petición va dividida o en un bloque.
mimeType: "multipart/form-data", // Agregamos el mimeType para enviar archivos.
fileName: "img.png",
params : {'bid':businessId,"imgurl":theFile,"content":content},
headers: {'Authorization':'Bearer ' + val, 'Content-Type': 'application/x-www-form-urlencoded'}
};
Si te fijas, tenemos que configurar las opciones cómo si se tratasen de un formulario en HTML. Con este ejemplo ya te va a funcionar sin problemas :P
Una cosa más antes de seguir. Se me ha consultado cómo podemos definir un TimeOut sobre la petición. Pues bien, imagino que están definidos de manera interna. La verdad que nunca había hecho por preguntarme esta cuestión, pero ahora que lo preguntan, pues tiene sentido... El sistema no cuenta con una manera de definir el TimeOut de la petición de manera manual. Así que tendremos que confiar en lo que haga por debajo...
En primer lugar, quiero aclarar que el sistema no permite realizar en envío de varios archivos en la misma petición. Entonces, cómo estarás imaginando, tenemos meterlo todo dentro de un bucle e ir uno por uno. La respuesta, al tratarse de una promesa, será enviado todo a la vez en lugar de secuencialmente.
import { FileTransfer, FileUploadOptions, FileTransferObject } from '@awesome-cordova-plugins/file-transfer/ngx';
uploadAllImage()
{
const fileTransfer:FileTransferObject = this.transfer.create();
var i;
var filetype;
var itemtype;
for(i=0; i<this.photos.length; i++)
{
//Obtener el tipo de archivo.
filetype = this.itemtypes[i];
//Obtenemos el mime type dependiendo del tipo recuperado antes.
switch(filetype)
{
case 'audio':
{
itemtype = 'audio/amr';
break;
}
case 'video':
{
itemtype = 'video/quicktime';
break;
}
case 'image':
{
itemtype = 'image/jpeg';
break;
}
default:
{
return;
}
}
//Fijamos el nombre del archivo.
var name ='pinglun_' + filetype;
name = name + '#';
name = name + this.photosName[i];
//Seteamos el mime type de la petición.
let option: FileUploadOptions = {
fileKey:'file',
mimeType:itemtype,
httpMethod:'POST',
fileName:name
};
if(filetype == 'image')
{
fileTransfer.upload(this.photos[i], encodeURI(localStorage.getItem('mi_dominio') + "/upload"),option).then((result)=>{
},(error) => {
});
} else {
fileTransfer.upload(this.fileurls[i], encodeURI(localStorage.getItem('mi_dominio') + "/upload"),option).then((result)=>{
}, (error) => {
});
}
}
}
Pues aquí tienes un buen ejemplo que podemos usar para llevar a cabo la subida múltiple.
Bien, después de mucho rebuscar. Y ver los errores reportados por los usuarios por la red, y las posibles soluciones, me decanto por una. Y es esta:
Primer vamos a actualizar todo lo interno de Ionic. Tranqui que la versión en sí, no se actualiza con esto.
npm i @ionic/app-scripts@latest --save
npm i ionic-native@latest --save
Y posteriormente tenemos que obtener la instancia de FileTransfer de la siguiente manera:
this.platform.ready().then(() => {
// Okay, so the platform is ready and our plugins are available.
// Here you can do any higher level native things you might need.
this.fileTransfer = this.transfer.create();
});
La diferencia está en crearla aquí en lugar de, por ejemplo, el constructor. Y con esto ya te debería de funciona.
La optimización del logo debe ser una de la patas principales en tu estrategia de ASO. Mejor logo, más descargas, más pasta. Hasta ahí bien. ¿Cómo lo puedes hacer? Pues mira, hemos creado para ti una herramienta que evalua y te da consejos sobre cómo mejorar tu logo. Aquí tienes más detalles.
Y ahora si, nos vemos en el siguiente artículo. Hasta entonces ¡que vaya bien!