This site runs best with JavaScript enabled.

React Native: Promesas y Callbacks en módulos nativos

Photo by Mark König on Unsplash

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:

  • desafíos son el nuevo ambiente de desarrollo: El ciclo de desarrollo y feedback es un poco más lento o forzoso en comparación con el rápido feedback obtenido al desarrollar una web utilizando por ejemplo create-react-app.
  • Diferentes primitivas: Las primitivas utilizadas para crear la interfaz en una aplicación React Native son diferentes y si bien en mi caso esto no fue algo nuevo ya que he hecho algunas otras app con React Native, si implica tener que mirar la documentacion basstante seguido para revisar algunas props o algunos elementos disponibles.
  • Es necesario implementar modulos nativos para resolver alguna tareas: Si bien la API de React Native y de herramientas como expo cubren gran parte de los casos de uso de una aplicación móvil genérica, muchas veces tenemos que crear nuestros propios módulos para resolver casos más complejos.

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.

Callbacks

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
    }

Promises

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)
        }
    }

Módulos Nativos

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.

Callbacks Nativas

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;
    }

Promises Nativas

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;
    }

Conclusión

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.

%%[converkit]

Edita esto en github

Comparte en

Matias Hernandez A.

Matias Hernandez A. Ingeniero de Producto/Software Chileno. Ha escrito cientos de lineas de código para diversas compañias y clientes en EE.UU y Europa construyendo diversos productos.

Matías Hernández Arellano's DEV Profile