November 13, 2020
Durante estas últimas semanas he estado trabajando con un proyecto utilizando React Native, y si bien React Native es, de alguna manera, 90% simplemente React, es tambien cierto que crea nuevos desafios.
Recomendado: Comenzando con React Native
Algunos de estos:
Relacionado: Agregar módulos nativos a una aplicación React Native
Es en este último desafío es en donde pase la última semana, implementando un wrapper en Java para exponer una API de un SDK.
Cuando desarrollas una aplicación web utilizando Javascript el uso de funciones asíncronas es parte del día a día y para lograr esa asincronicidad hay dos métodos posibles: Callbacks y Promesas.
Estas son parte esencial de Javascript y han estado presentes desde el inicio de los tiempos. ¿Que son?, de forma muy simple, una callback es una función que es pasado como argumento a otra función y es después llamada desde el interior de la función cuando termina de ejecutar alguna tarea.
Un ejemplo es el uso de setTimeout
const customSetTimeout = (callback) => {
setTimeout(() => { //Esta tambien es un callback
console.log('Set timeout terminado')
callback(); //Ejecuta el callback cuando setTimeout termino
}, 1000); //1 segundo
}
Una Promesa es una implementación un poco más robusta para resolver el problema de asincronicidad. En palabras generales, una promesa es “algo que pasará en el futuro”, basicamente es una función que recibe una instrucción de hacer alguna tarea y te responde “Aún no tengo los dato, pero dame tu contacto y cuano tenga los dato de aviso”. ¿Cómo se ve en código?
fetch('https://someapi.com').then(data => {
doSomethingWithData(data)
}).catch(e => {
console.error(e)
})
Para “capturar” esta promesa se utiliza then
para definir que hacer cuando la promesa retorna “se resuelve” y catch
para definir que hacer cuando la promesa falla.
Actualmente Javascript ofrece algo de syntaxis sugar
utilizando async/await
const getData = async () => {
try {
const data = await fetch('https://someapi.com')
doSomethingWithData(data)
}catch(e){
console.error(e)
}
}
React Native ofrece algunas estructuras de datos que permiten ofrecen esta experiencia en los métodos expuestos. Existen dos estructuras utilizadas como argumentos Callback
y Promise
.
Callback
es utilizado para proveer el resultado del llamado de la función hacia Javascript.
En el caso de iOS utilizando Objective-C
RTC_EXPORT_METHOD(nativeFetchApi:(RCTResponseSenderBlock)callback)
{
NSArray *array = AwesomeSDK.fetch();
callback(@[NSNull null], array);
}
O en su version Java
@ReactMethod
public void nativeFetchApi(Callback callback) {
WritableArray array = AwesomeSDK.fetch()
callback.invoke(array)
}
Y después simplemente se utiliza en nuestro código Javacript
const fetchCallback = (data) => {
doSomethingCool(data)
}
NativeModules.MyAwesomeModule.nativeFetchApi(fetchCallback);
El módulo nativo debe invocar el callback sólo una vez. También es posible almacenar el callback y llamarlo en otro punto del código. De hecho esto es lo que tuve que realizar ya que el SDK utilizado tiene sus propios observers
.
Java
private Callback privateCallback = null;
@ReactMethod
public void nativeSdkSingin(Callback callback) {
this.privateCallback = callback;
AwesomeSDK.signin()
}
@Override
public void signinObserver() {
if(this.privateCallback != null) {
this.privateCallback.invoke();
}38k
this.privateCallback = null;
}
React Native también provee de una estructura de datos que puede definir una Promesa, que permite simplificar un poco el código asíncrono, sobre todo si se utiliza async/await
. Para definir el uso de una promesa, es decir, determinar que una función retornará una promesa debes utilizar como último parámetro de la función este argumento.
En el caso de iOS utilizando Objective-C
RTC_EXPORT_METHOD(nativeFetchApi,
nativeFetchApiWithResolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
{
NSArray *array = AwesomeSDK.fetch();
if(array){
resolve(array)
}else{
NSError *error =....
reject(@"no_data", @"No hay datos", error)
}
}
O en su version Java
@ReactMethod
public void nativeFetchApi(Promise promise) {
try {
WritableArray array = AwesomeSDK.fetch()
promise.resolve(array)
}catch(...){
promise.reject('...')
}
}
Y despues simplemente se utiliza en nuestro código Javacript
const fetchNativeData = async () => {
try {
const data = await NativeModules.MyAwesomeModule.nativeFetchApi();
doSomethingCool(data)
}catch(e){
console.error(e)
}
}
Y de igual forma que en el ejemplo de uso de callbacks, también es posible almacenar la promesa y llamarla en otro punto del código.
Java
private Promise privatePromise = null;
@ReactMethod
public void nativeSdkSingin(Promise promise) {
this.privatePromise = promise;
AwesomeSDK.signin()
}
@Override
public void signinObserver() {
if(this.privatePromise != null) {
this.privatePromise.resolve();
}38k
this.privatePromise = null;
}
Al desarrollar aplicaciones móviles utilizando React Native, ciertos casos de usos requieren el desarrollo de módulos nativos, y uno de los objetivos de estos módulos es ofrecer una experiencia de uso (al desarrollador) lo más cercana posible a una librería escrita en javascript, para esto React Native ofrece estructuras de datos que permiten desarrollar métodos nativos que soportan Callbacks y Promesas.
Mantente al día con más Javascript, React, Typescript y otros temas de interés
🎉 📩 🎉
🧡 Hecho full JamStack con Gatsby, Sanity.io. Hosteado en Netlify. Hermoseado gracias a TailwindCSS. Gracias a @benrogerson por la genial twin.macro 🤘