Cómo crear en React Native una Share Extension

React Native y Share Extensions desde cero.

Image for post
Image for post

Hoy quiero hablaros de cómo he creado una Share Extension para una aplicación que estoy desarrollando bajo React Native y Redux. He visto interesante escribir un artículo sobre este tema ya que la documentación que hay (o por lo menos, la que yo he sido capaz de encontrar) es bastante escasa y creo que puede seros de utilidad si os toca desarrollar algo parecido.

Para quien no sepa a qué me refiero cuando hablo de una Share Extension os dejo una captura de lo que he desarrollado para que os hagáis una idea:

Share Extension con React Native

¡Así que vamos a ello!

0. Situación inicial

Como os comentaba al principio, partiremos de una aplicación que funciona bajo React Native junto a Redux y Redux Persist (empleando como storage AsyncStorage ) y que obtiene toda la información mediante llamadas autenticadas (esto es importante, ya que el JWT se encuentra almacenado en la store y será necesario recuperarlo para poder hacer llamadas desde la extensión).

El proyecto funciona tanto para iOS como para Android por lo que el objetivo es crear una Share Extension escrita en React Native que funcione en ambos sistemas, si bien en este artículo voy a ceñirme tan solo al sistema operativo de Apple (cuando tenga creada la extensión en Android escribiré una segunda parte contando los pasos que he seguido).

Nota. Antes de comenzar es importante destacar que en iOS las extensiones (tanto las denominadas Share como las Action) son tratadas como contenedores separados, por lo que no tienen acceso a la carpeta principal de la aplicación, es decir, los recursos como imágenes y fuentes deberán ser añadidos a la extensión (ahora veremos cómo) para que los podamos emplear dentro de ella.

React Native Share Extension

1. React Native Share Extension

Sí, como sospechabais existe un paquete que permite agilizar el proceso de integrar la extensión dentro de nuestra aplicación, lo cual se agradece bastante si os queréis ahorrar el proceso de crear el código nativo y generar el Bridge correspondiente para conectar la extensión con React Native. Este paquete es:

Y la verdad es que tanto la documentación como el mantenimiento que le da el propio desarrollador son bastante buenos, por lo que mi recomendación es que comencéis a desarrollar tomando como punto de partida este paquete.

Ojo. En el momento de escribir este artículo React Native Share Extension solo soporta imágenes y URL’s, por lo que si estáis tratando de compartir otro tipo de objetos (vídeos, archivos…) hacia vuestra aplicación tendréis que forkear este repositorio y añadirle esa funcionalidad extra.

El proceso de configuración de este paquete es bastante sencillo y basta con seguir los pasos que describe su autor en el Readme.md, teniendo en cuenta los siguientes puntos.

No os olvidéis de linkar las librerías extra

En el momento en que se linkan en la extensión las librerías estáticas, él evidentemente solo nombra las que está usando en su proyecto de prueba, por lo que si habéis añadido alguna más que vayáis a necesitarla en la extensión, no os olvidéis de ella ya que debuggear este tipo de errores es bastante doloroso cuando estamos desarrollando una extensión.

Añadir soporte para compartir imágenes

En el tutorial solo describe la forma de configurar la extensión para que trabaje con URL’s compartidas desde el navegador. Si queréis añadir también soporte para imágenes deberéis añadir en vuestro info.plist de la extensión lo siguiente:

NSExtensionActivationSupportsImageWithMaxCount

Con esto, cuando vayamos a compartir la imagen desde el carrete, obtendremos su path interno en el value que devuelve el bridge ShareExtension.

// path a la imagen
const { value } = await ShareExtension.data();

Fuentes personalizadas

Si estáis empleando fuentes personalizadas y las queréis emplear también dentro de la extensión deberéis realizar los siguientes pasos:

  • En XCode, ir al archivo donde está fuente (probablemente dentro de la carpeta Resources si las habéis linkado mediante react-native link ) y seleccionarla.
  • Aseguraros de que está añadida al target no solo de la aplicación, sino también de la extensión:
Image for post
Image for post
  • Añadir la fuente al archivo info.plist de la extensión de la misma forma que habéis hecho para la aplicación:

MainQueue warning

Es probable que al lanzar la extensión os aparezca el siguiente warning:

Fix iOS Yellow box warning “requires main queue setup”

Esto se soluciona yendo al código de vuestra extensión en XCode (el archivo extensionName.m ) y añadiendo la siguiente línea en la implementación de la misma:

@implementation AppExtension...+ (BOOL)requiresMainQueueSetup
{
return YES;
}
@end

Paso a producción

Finalmente, y aunque en la documentación no lo pone, es probable que si compiláis la aplicación en modo release y la instaláis en vuestro dispositivo os topéis con el siguiente fallo en los logs cuando intentéis lanzar la extensión:

Make sure you're running a packager server or have included a .jsbundle file in your application bundle.

(Nota. Tendréis que abrir los logs del dispositivo desde XCode para encontraros con este fallo ya que en la consola no aparece nada. El único signo de este fallo es que al pulsar sobre la extensión ésta no se abre).

Para resolverlo, iremos a las Build Phases de nuestra Share Extension y añadiremos un nuevo Run Script con el siguiente contenido:

export NODE_BINARY=node../node_modules/react-native/scripts/react-native-xcode.sh

De modo que empleemos el script react-native-xcode para empaquetar el código y assets de la extensión y que no se produzca el fallo anterior al intentar abrirla.

2. Código de la extensión

Si habéis seguido el tutorial paso a paso del paquete React Native Share Extension tendréis ya la extensión configurada y funcionando en vuestros dispositivos. Pero claro, esta extensión tan solo muestra ahora mismo la URL de la foto que vamos a compartir (o una URL básica si estamos compartiendo desde el navegador) y nosotros queremos enviarla a nuestro servidor, ¿no?

Bien, vamos a darle un poco de chicha.

Rehidratando redux

Como os comentaba al principio del artículo, el JWT se encuentra en redux persistido mediante la librería redux-persist , de modo que, en teoría, bastaría con llamar a nuestro script configureStore para rehidratar nuestra store desde redux-persist y poder acceder al token, ¿no?

El problema es que es probable que hayáis configurado redux-persist mediante Async Storage:

import storage from 'redux-persist/lib/storage' 
// defaults to AsyncStorage for react-native

import rootReducer from './reducers'

const persistConfig = {
key: 'root',
storage,
}

Y, puesto que la extensión se encuentra en un container diferente al de la aplicación, los datos a los que accede AsyncStorage desde la aplicación son diferentes a los de la extensión, es decir, que cuando en la extensión rehidratemos nuestra store ésta se encontrará vacía, es decir, no tendremos JWT para realizar llamadas.

Apple propone una solución para esto, y es emplear lo que se conocen como AppGroups, los cuales permiten a múltiples aplicaciones compartir el mismo container, de modo que puedan acceder a los mismos datos. Para activarlos, iremos a la pestaña Capabilities dentro del target de nuestra aplicación y los activaremos:

App Groups

Es importante que después de activarlos creéis uno nuevo. Para su nombre yo suelo escoger un nombre de dominio inverso, ya que el nombre que le deis será el empleado posteriormente para crear la carpeta donde residirán los datos dentro del dispositivo, por lo que conviene que sean nombres compatibles con el sistema de archivos. En mi caso yo uso por convención app.group.appName

Hecho ésto, repetiremos el mismo proceso pero ahora yendo a la pestaña Capabilitiesdel target de nuestra extensión. Nuevamente activaremos App Groups y seleccionaremos el grupo que creamos para la aplicación:

Bien, ahora podríamos pensar que ya todo es maravilloso y que al lanzar nuestra extensión obtendremos desde redux-persist nuestra store rehidratada. Pues no…

AsyncStorage no soporta los App Groups que acabamos de configurar, por lo que es necesario cambiar el sistema de storage que empleamos en redux-persist a otro que sí sea compatible. Tras mucho investigar finalmente yo me decanté por la librería redux-persist-fs-storage , la cual es un wrapper para react-native-fs de cara a integrarla con redux-persist .

Gracias que la librería react-native-fs permite ser configurada con el path que le digamos y que además posee un método para obtener el directorio del App Group que le especifiquemos, podremos conseguir que la aplicación y la extensión compartan los mismos datos. Para ello haremos lo siguiente.

Crearemos un método que nos devuelva un objeto FSStorage instanciado con el path devuelto por RNFS donde queremos que redux-persist guarde la store :

import FSStorage from 'redux-persist-fs-storage';
import RNFS from 'react-native-fs';
const getFSStorage = () => Platform.OS === ‘ios’ ? RNFS.pathForGroup(‘group.app.appName’).then(folder => FSStorage(folder)) : Promise.resolve(FSStorage());

Como veis, compruebo la plataforma para usar los App Groupos en el caso de estar en iOS. Para android de momento empleo el path por defecto aunque probablemente tenga que modificarlo cuando termine de desarrollar la extensión para este sistema operativo.

Hecho esto, configuraremos nuestra store y redux-persist empleando el objeto devuelto por el método anterior:

Y ahora sí, ya hemos conseguido que aplicación y extensión compartan los mismos datos de modo que podamos acceder al JWT del usuario guardado en la store desde la aplicación.

De este modo, en el método componentDidMount de nuestra extensión, obtendremos la URL del objeto que estamos compartiendo y, a continuación, rehidrataremos redux desde redux-persist , para, a continuación, obtener los datos que queramos desde nuestra API

Nota. Mi método initApp se encarga de añadir el token JWT almacenado en redux a la librería axios , de modo que todas las llamadas vayan autenticadas. Según la lógica de vuestra aplicación, es probable que lo hagáis de otra forma pero lo importante es aprender a recuperar la store desde nuestra extension.

3. Conclusiones

Y ya con esto tendríamos nuestra Share Extension escrita en React Native y lista para ser usada con nuestra aplicación.

Si bien el proceso es algo largo, creo que los resultados merecen la pena especialmente por la flexibilidad que logramos al tenerla sobre React Native. Además, podremos diseñar nuestra extensión como nos apetezca pues dispondremos de las mismas propiedades y elementos que empleamos en una aplicación normal hecha sobre React Native.

Espero que os haya servido y, si os surge cualquier duda o pregunta durante el proceso, escribidme para intentar resolvérosla y así mejorar este artículo.

¿Quieres recibir más artículos como este?

Suscríbete a nuestra newsletter:

Written by

Entre paseo y paseo con Simba desarrollo en Symfony y React

Get the Medium app