React Native + Expo

React Native es la tecnología de Facebook liberada desde 2015 para el desarrollo de aplicaciones móviles multiplataforma (Android e iOS, incluso Web), mientras Expo es un marco de trabajo que opera sobre React Native para facilitar el desarrollo con esta tecnología. Básicamente Expo evita el uso de código específico para cada plataforma cuando accedes a ciertas características (notificaciones push, cámara, contactos, etc.) permitiendo que el desarrollador se concentre en el código Javascript.

Entre las caractarísticas distintivas de React Native es que usa los mismos principios de la librería React pero las aplicaciones móviles quedan nativas, con Expo obtienes la facultad de usar un simple editor de texto en lugar de un IDE y se posibilita la carga fluida de la aplicación (menos espera). Expo soporta Android 5+ e iOS 10+, también es posible programar para la Web, motivos suficientes para despertar interés.

OnMind usa esta tecnología a nivel visual (Front-End) para aplicaciones móviles introduciéndose gradualmente conforme a nuestras necesidades y enfoque, dado que hasta una aplicación móvil híbrida puede ser suficiente en nuestro caso variando según algún se requiera en un proyecto específico.

Antes de iniciar con éste marco de trabajo, debemos asegurarnos de tener un entorno preparado para su uso. Básicamente un editor de código como VSCode y un dispositivo móvil, además de tener instalado Node.js.

choco install -y nodejs.install

Instalando el SDK de Expo (+React Native)

En resumen, se deben ejecutar los siguientes comandos (donde project es el nombre de la carpeta del proyecto):

npm install expo-cli --global
expo init project --template expo-template-blank
cd project
expo start

Ahora simplementes abres la carpeta del proyecto (project) con un editor de código (como VSCode) y editas el archivo App.js.
En la línea del comando expo init se puede omitir --template expo-template-blank.
Con expo start es posible leer un código QR con la cámara de nuestro móvil para cargar facilmente nuestra aplicación en el dispositivo, evitando la fatiga de los emuladores.

Creando el primer proyecto

Para implementar nuestro propio código, sustituimos el contenido del archivo App.js por el siguiente:

import React from 'react';
import { View, Text, StyleSheet } from 'react-native';

export default function App() {
  return (
    <View style={styles.container}>
      <Text>Hi there!</Text>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff',
    alignItems: 'center',
    justifyContent: 'center',
  },
});

Esta vez, recuerda ejecutar nuevamente el comando expo start.
Nótese que algo distintivo de React Native (o React) es que retorna un trozo de html dentro de un paréntesis () de modo natural. Este formato se denomina JSX (como si fuese una extension de javascript).

El siguiente ejercicio

Para nuestro ejercicio, debemos ejecutar adicionalmente el siguiente comando:

expo install react-native-webview

Agreguemos algunas características reemplazando el contenido de nuestro código por el siguiente:

import React from 'react';
import { View } from 'react-native';
import { WebView } from 'react-native-webview';

export default function App() {
  return (
    <View style={{ flex: 1 }}>
      <WebView
        allowFileAccess
        originWhitelist={ ['*'] }
        source={{ uri: 'https://www.wikipedia.org/' }}
        style={{ marginTop: 20 }}
      />
    </View>
  );
}

Puedes probar cambiando la uri por una propia. De hecho, podrías usar arhivos locales estableciendo uri: (Platform.OS == "android" ? 'file:///android_asset/www/index.html' : './resources/www/index.html')
Si se ha cancelado el proceso anterior, se ejecuta nuevamente el comando expo start.

Generando primer paquete para Android

Para generar un paquete distribuible para Android abres una nueva terminal y ejecutas el comando:

expo build:android -t apk

Esta accion requiere que se registre una cuenta de usuario con Expo y tener configurado en el archivo app.json lo correspondiente a la plataforma Android. Por ejemplo:

    "owner": "user",
    "orientation": "default",
    ...
    "android": {
      "package": "com.example.project"
    }

El comando expo build:android nos pregunta si cargamos una keystore o dejamos a Expo generarla por nosotros (aplicamos a esta opción). Indicar en el archivo app.json el usuario registrado en Expo como owner también es parte de la configuración.

Resumimos entonces los comandos utilizados hasta ahora con Expo en el siguiente listado:

Un poco de React

Tanto React (liberada a mediados del año 2013) como React Native son librerías para la interfaz de usuario (UI) que han tenido gran acogida teniendo actualmente la mayor popularidad en su contexto. React Native se diferencia principalmente de React al enfocarse en el desarrollo móvil nativo y multi-plataforma a partir de la misma lógica y el uso de jsx (que combina html dentro de javascript como una modalidad extendida), posibilitando el acceso a recursos del dispositivo y usando sus propias etiquetas bien definidas (tales como Text, View, WebView, etc.).

Aunque en los ejercicios vistos se ha usado la simplicidad de la programación funcional (que se promueve actualmente con el uso de Hooks), puedes encontrar código en React o React Native que implementa clases para definir el componente web. En últimas, un componente web se traduce en etiquetas personalizadas desde el punto de vista de html.

Para programar con estas librerías se requiere Node.js (adicionalmente Webpack) y se puede iniciar un proyecto de React directamente con la siguiente sentencia (donde project es el nombre de la carpeta del proyecto):

npx create-react-app project

Esto crea una estructura para aplicación basada en la plantilla y hace uso interno de Webpack al momento de su despliegue. Lo más relevante aquí es que React usa un archivo index.html que invoca al index.js que a su vez carga el componente de App.js (en un DOM Virtual). El archivo index.html puede verse de la siguiente manera:

<body>
  <div id="root"></div>
</body>

El archivo index.js puede verse de la siguiente manera:

import React from `react`;
import ReactDOM from `react-dom`;
import App from `./App.js`;

ReactDOM.render(<App />, document.getElementByid('root'));

El archivo App.js basado en clases, puede verse de la siguiente manera:

export default class App extends React.Component {
  render() {
    return (<h1>Hi there!</h1>);
  }
}

O escrito como función, puede verse así:

export default function App() {
  return (<h1>Hi there!</h1>);
}

En el sentido esencial, React es una librería para la UI. Usa la sintaxis JSX, la cual consiste en trozos de HTML dentro de Javascript para obtener componentes web, que se definen mediante funciones. Para esto se debe considerar que:

  1. Los ciclos (for,map) y las validaciones (if) se aplican usando Javascript, mientras los trozos HTML se establecen con paréntesis y return (o render)
  2. Los eventos varían respecto al HTML, encontrando el estándar camel-case: onClick, onChange, onKeyPress, onSubmit
  3. Hay propiedades específicas que varían respecto al HTML, encontrando: className, htmlFor, dangerouslySetInnerHTML
  4. Para vincular datos y HTML se usan: props (propiedades) y state (estado)
  5. Se introduce el concepto de hooks (métodos vinculados al renderizado de un componente), siendo esencial: useState, useEffect, useContext
  6. La etiqueta de estilos (style) se expresa en JSON con otra llave {{...}} (cuando se usa directamente). De hecho, las expresiones usan llaves {...}

Para una noción general o temario sugerido, se puede continuar investigando o abordando tanto React como React Native con los siguientes tópicos:

  1. Propiedades (props)
  2. Estado (state)
  3. Métodos del ciclo de vida de componentes basados en clases:
    • constructor
    • render
    • componentDidMount
    • componentWillUnmount
    • getDerivedStateFromProps
    • shouldComponentUpdate
    • componentDidUpdate
  4. Eventos (onClick, onChange, onKeyPress, onSubmit, etc.)
  5. Hooks, nueva propuesta funcional que sustituye el ciclo de vida de clases e incorpora otros elementos:
    • useState (gestiona estado asignando un valor y la función que lo actualiza: const [state,setState] = useState({}))
    • useEffect (cubre componentDidMount, componentDidUpdate, and componentWillUnmount con funciones, el primero con un segundo parámetro así: [])
    • useContext
    • useRef
    • useMemo
    • useCallback
  6. Gestión de estado de la aplicación (React Context)
  7. Adicionalmente, enrutamiento de componentes (React Router). Y como propuesta visual agregada para React Native, el uso de componentes basados en el proyecto NativeBase.

Si recientemente te haz introducido en React o no tienes conocimiento aún y piensas hacerlo, te sugiero seguir el nuevo camino con Hooks en lugar del ciclo de vida clásico de las clases, incluso para proyectos nuevos si te consideras experto. Los Hooks son promovidos actualmente por Facebook, sólo que proyectos y ejemplos anteriores a 2020 eran programados con clases y por ello aún son soportadas, así que es un “plus” concocer sobre éstas.

React Native CLI & Android Studio (no Expo)

A continuación veamos como crear un proyecto directamente con la CLI de React Native. En este escenario es preciso instalar Android Studio y configurar su SDK. Además debemos tener instalado Python2 y JDK 8+ (si no se ha instalado antes). Si tienes instalado chocolatey en Windows, para lo último mencionado puede usarse una terminal en Windows con privilegios de Administrador y ejecutar:

choco install -y python2 openjdk8

En cuanto a Android Studio, puedes descargarlo desde el sitio oficial.

Una vez se cuenta con el entorno debidamente preparado, nos ubicamos en una carpeta del espacio de trabajo o folder anterior al proyecto que crearemos y ejecutamos:

npx react-native init project

Luego abrimos la carpeta creada por la CLI de React Native, usamos por primera vez el iniciador del proyecto y lanzamos el emulador para Android, así:

cd project
npx react-native start
npx react-native run-android

Este último comando es el que seguiremos usando para visualizar nuestra aplicación. Podemos modificar alguna parte del texto, guardamos y veremos que los cambios se reflejan en el emulador (o el dispositivo conectado por USB).

Un nuevo ejercicio

Lo que haremos a continuación es un proyecto con WebView pero con archivos locales (o estáticos) pensando en una aplicación offline que no dependería del acceso a internet para operar. Para esto, primero debemos detener el proceso (CTRL-C) para instalar el módulo requerido con el siguiente comando:

npm install react-native-webview

Es importante tener en cuenta que para nuestro ejercicio con Android, se deben colocar los archivos estáticos (index.html y main.js que veremos más abajo) en la ruta android/app/src/main/assets/ (relativo al proyecto).

Veamos primero el código del archivo App.js en React Native:

import React, { useState, useEffect } from 'react';
import { View, Platform } from 'react-native';
import { WebView } from 'react-native-webview';

const App: () => React$Node = () => {
  const [source, setSource] = useState({ html: '<strong>Loading...</strong>' });
  useEffect(() => {  // Something after render it
    if (Platform.OS !== 'ios')  // Android & Web
      setSource({ uri: 'file:///android_asset/index.html' });
    else  // iOS
      setSource('./resources/index.html');
  }, []);

  return (
    <View style={{ flex: 1 }}>
      <WebView
        allowFileAccess
        originWhitelist={['*']}
        source={source}
        javaScriptEnabled={true}
        domStorageEnabled={true}
      />
    </View>
  );
};

export default App;

Para los archivos index.html y main.js tendremos el siguiente código respectivamente:

<!doctype html>
<html lang="es">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width,initial-scale=1">
        <title>WebView & Local Files</title>
    </head>
    <body>
        <div id="root"><h1>My App</h1></div>
        <script defer src="main.js"></script>
    </body>
</html>
(() => {
    document.getElementById('root').innerHTML += 'Yeah! ~> from Javascript.';
})();

Si haces un ejercicio distinto en dónde tienes un script generado por webpack y no funciona, podría sospecharse de que el código que se tenga en javascript no sea soportado en alguna parte (o quizás la versión de Android no soporte ese código). Puedes validar con este simple ejemplo para tener un referente fiable, es decir, confirmar que al menos éste código si funciona.

La carga en caliente (hot-reload) no sirve de mucho con archivos estáticos, por lo que si modificas algo de html o javascript no lo asumirá el emulador inmediatamente, es decir, debe cancelarse el proceso (CTRL-C) y ejecutarse de nuevo npx react-native run-android para que vuelva a generar el binario de la plataforma Android. Distinto si colocas el código como cadenas dentro de React Native. Veamos el siguiente ejemplo reescribiendo toda nuestra aplicación, así:

import React from 'react';
import { View } from 'react-native';
import { WebView } from 'react-native-webview';

const App: () => React$Node = () => {
  const js = `
  (() => {
    document.getElementById('root').innerHTML += 'Yeah! ~> from Javascript.';
  })();
  `;

  const html = `<!doctype html>
  <html lang="es">
      <head>
          <meta charset="UTF-8">
          <meta name="viewport" content="width=device-width,initial-scale=1">
          <title>WebView & Local Files</title>
      </head>
      <body>
          <div id="root"><h1>My App</h1></div>
          <script>${js}</script>
      </body>
  </html>`;

  const source = { html: html };

  return (
    <View style={{ flex: 1 }}>
      <WebView
        allowFileAccess
        originWhitelist={['*']}
        source={source}
        javaScriptEnabled={true}
        domStorageEnabled={true}
      />
    </View>
  );
};

export default App;

También puede implementarse con Expo teniendo en cuenta que la línea dónde se define y exporta la aplicación tendría la siguiente presentación:

export default function App() {
  ...
}

Si una aplicación es puramente web con elementos estáticos (html y js) y no se usan recursos nativos (o quizás alguno muy específico), es preferible quedarse con la CLI de React Native en lugar de Expo, salvo por el flujo de despliegue de aplicación que ofrece esta última tecnología que incorpora la publicación en las tiendas respectivas, siendo un factor diferenciador. Debe tenerse en cuenta que Expo puede cargar más lógica para ofrecer un acceso más sencillo sobre las características nativas.

Con esto hemos cubierto los primeros pasos aprovechando conocimientos de desarrollo web, pero las aplicaciones nativas no suelen usar el componente WebView (que proporciona mas bien algo hibrido) sino los componentes para la interfaz nativa, así que para continuar en esa línea se debe explorar React Native como tal. Esto es tan solo un punto de partida o una perspectiva para el desarrollo móvil con tecnologías web convencionales o puras.

¿Qué es una aplicación móvil híbrida?: Tomando lo visto, podría decirse que es una aplicación web que usa el componente WebView para la interfaz de usuario en móviles y puede tener acceso a características nativas. El tiempo de respuesta respecto a los componentes nativos disminuye pero esto podría ser poco significativo si no se tienen exigencias sobre la interfaz de usuario y se valora conservar buena parte del código. Puede ser el caso de software de gestión que simplemente requiere presentar datos o una página web (estática) con diseño responsivo que puede envolverse o presentarse como una “app”.

Si queremos reorganizar o refactorizar el código anterior, podemos tener un archivo separado con las cadenas que representan el contenido web estático. Es decir, tendríamos los archivos App.js y web.js respectivamente así:

import React from 'react';
import { View } from 'react-native';
import { WebView } from 'react-native-webview';
import web from './web';

const App: () => React$Node = () => {
  return (
    <View style={{ flex: 1 }}>
      <WebView
        allowFileAccess
        originWhitelist={['*']}
        source={{ html: web }}
        javaScriptEnabled={true}
        domStorageEnabled={true}
      />
    </View>
  );
};

export default App;
const js = `
(() => {
  document.getElementById('root').innerHTML += 'Yeah! ~> from Javascript.';
})();
`;

const html = `<!doctype html>
<html lang="es">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width,initial-scale=1">
        <title>WebView & Local Files</title>
    </head>
    <body>
        <div id="root"><h1>My App</h1></div>
        <script>${js}</script>
    </body>
</html>`;

export default web;

Este último bloque de código correspondería al nuevo archivo llamado web.js. Revisa de nuevo, el resultado seguirá siendo el mismo pero nuestro código luce mejor.

Espero se haya logrado una prueba de concepto y un panorama para saber elegir el camino a seguir si requieres aplicar estos conceptos en un proyecto específico.

Uno poco de NativeBase

NativeBase es un proyecto de tecnología enfocado en el estilizado visual de los componentes que simplifica la declaración de estilos y se basa en ReactNative, además puede ser utilizado con Expo. En otras palabras, con las tres tecnologías mencionadas consigues aplicaciones móviles dóndole cierto tipo de apariencia con una declaración más simple o clara, y logrando acceder a características del dispositivo con las capacidades que brinda Expo. También puede usarse para la Web.

Para comenzar con NativeBase, teniendo instalado previamente Expo, nos ubicamos desde la línea de comandos en una carpeta que se haya destinado como espacio de trabajo para proyectos de código, y desde allí ejecutamos el siguiente comando:

expo init proyect --template @native-base/expo-template
cd project
expo upgrade
npm start

project se refiere al nombre asignado para un proyecto.
Para desarrollar, podemos usar como editor VSCode y la extensión para NativeBase

Al abrir el proyecto con un editor, encontraremos el archivo App.js con la función App, que tendría un esquema de código como el siguiente:

export default function App() {
  return (
    <NativeBaseProvider>
      ...
    </NativeBaseProvider>
  );
}

Notése que el componente NativeBaseProvider envuelve los componentes como proveedor.

Lo que haremos es buscar una imágen que tengamos en formato png con un tamaño mediano o apropiado para hacer un cambio a las siguientes líneas:

          <NativeBaseIcon />
          <Heading size="lg">Welcome to NativeBase</Heading>

Luego de identificar la imágen png y renombrarla a icon.png, sustituimos las líneas de código anteriores por las siguientes:

          <Image source={require('./assets/icon.png')} alt="Icon" />
          <Heading size="lg">Welcome to "The Jungle"</Heading>

∫ Pero para que el anterior código funcione, debemos importar Image de la librería native-base. Por ejemplo:

import { Image, Center, NativeBaseProvider } from "native-base";

Lo anterior quiere sugerir que se debe incluir Image en la línea de import respectiva.

Si fuera necesario reiniciar el proceso, ejecutaríamos de nuevo npm start y observamos los cambios. También puedes probar modificando el enlace del ejemplo para que se dirija a tu Blog o sitio de preferencia. Y para generar nuestro archivo distribuible para Android usamos el comando:

npm i -g eas-cli
eas build -p android