Desarrollo de Aplicaciones Móviles con NativeScript

Publicado en

El mundo móvil ha venido cambiando drásticamente, haciendo sumamente difícil para los desarrolladores crear y mantener sus aplicaciones móviles. Para alcanzar la mayor audiencia posible, las aplicaciones desarrolladas necesitan mantenerse en  muchas plataformas y dispositivos. Para seguir al ritmo de este mundo cambiante, los desarrolladores comenzaron a ubicar en un lugar preponderante  un solo código base para escribir y mantener las aplicaciones.

En este contexto podemos incluir una larga lista de empresas y tecnologías alrededor de la creación y entrega de aplicaciones móviles de plataforma cruzada (cross-platform mobile apps), las cuales, en su mayoría plantean un solo código base para el desarrollo y entrega de aplicaciones móviles para diferentes plataformas: tecnologías como PhoneGap/Cordova, React Native, Appcelerator Titanium y  Xamarin entre otras.

Asimismo, podemos mencionar que el mundo del desarrollo móvil se sustenta en 4 grandes categorías: nativo, híbrido, compilación cruzada  y  justo a tiempo (just in time –JIT– por sus siglas en inglés,).

fig1

Figura 1. Diferentes tipos de aplicaciones móviles y sus frameworks más populares.

Las aplicaciones JIT o compiladas justo a tiempo son aquellas aplicaciones que son compiladas en tiempo de ejecución (runtime) en oposición a aquellas que son compiladas antes de la ejecución de la aplicación. Por ejemplo, en una aplicación JIT el código fuente no es compilado a código máquina nativo sino hasta el último minuto, o inmediatamente antes de ejecutar cada sentencia. NativeScript  es JIT, compila justo a tiempo.

Conociendo NativeScript

NativeScript es un entorno de trabajo de código abierto  (open-source mobile framework) para construir aplicaciones móviles para las plataformas iOS, Android —y próximamente Windows— usando JavaScript, CSS y XML. De manera opcional, podemos utilizar TypeScript  y Angular 2 para obtener un mejor desempeño, manejo y escalamiento de nuestras aplicaciones.

NativeScript difiere de otros entornos de desarrollo  de muchas maneras. La más importante es que se trata de un entorno de trabajo que verdaderamente permite escribir una sola vez y entregar a  todas las plataformas. Con Nativescript podemos:

  • Aprovechar el conocimiento existente (no tienes que saber Objective C, Swift o Java). NativeScript ha sido diseñado para ser aprovechado por desarrolladores con diferentes formaciones.

  • Implementación de hojas de estilo CSS. Podemos cambiar la apariencia y estilo de vistas y elementos en una aplicación NativeScript de manera similar a como lo hacemos en una aplicación web, usando hojas de estilo o cambiando el estilo del objeto de los elementos en JavaScript. Sin embargo, sólo un subconjunto del lenguaje CSS es soportado.

  • Acceso a las APIs nativas de la plataforma. Si el framework de Nativescript no expone una API nativa que necesitemos, podemos implementar plugins con NativeScript.

  • Tu código se escribe una vez. En experiencias pasadas, trabajando con otros frameworks cross-platform, he visto que se requiere usar muchos shims (código de cuña) para que la aplicación funcione bien en las distintas plataformas; por ejemplo, puede que necesitemos agregar un poco de código para desplegar un botón sólo en la versión de Android o, tal vez, necesitamos escribir código adicional para hacer que un menú de lista se vea bien en iOS. Pero en NativeScript esto rara vez es necesario.

¿Cómo funciona NativeScript ?

Tal vez la característica más importante de NativeScript es su mecanismo para brindar acceso directo a las APIs nativas de cada plataforma. Pero, ¿cómo funciona?. Examinemos el siguiente código de una aplicación NativeScript para Android.

var time =new android.text.format.Time();
time.set(12,12,2016);
console.log( time.format("%D"));
 

Te preguntarás: ¿es código JavaScript creando una instancia de un objeto Java? Sí,  JavaScript crea una instancia del objeto, android.text.format.Time(), invoca su método set() y luego manda al log de la consola el valor de retorno de su método format, el cual es la cadena “12/12/16”.

Veamos un ejemplo  de una aplicación NativeScript para iOS.

var alert =new UIAlertView();
alert.message ="Hello world!";
alert.addButtonWithTitle("OK");
alert.show();

 

fig2

Figura 2. Ejemplo de ventana de diálogo con NativeScript

Este  código JavaScript crea una instancia de una clase de Objective-C llamada UIAlertView, establece su propiedad message y luego llama a sus métodos addButtonWithTitle() y show().

Vale la pena aclarar que estos casos en los que accedemos a las APIs nativas invocando objetos específicos de Android o iOS, son excepciones. NativeScript incluye muchos módulos para tareas comunes, como hacer una petición HTTP,  construir componentes UI, etc.  Sin embargo, a veces las aplicaciones necesitan acceso a las APIs, y el runtime de NativeScript hace este acceso muy sencillo cuando se requiere.

 

NativeScript Runtime

El runtime de NativeScript es donde se hace la magia. Todo comienza con la máquina virtual de JavaScript que utiliza NativeScript para ejecutar comandos JavaScript. Específicamente NativeScript usa V8 en Android y JavaScriptCore en iOS.  Así que todo el código que escribimos para tener acceso a las APIs nativas necesitan las construcciones y sintaxis de  JavaScript que V8 y JavaScriptCore entienden.

Esta es la primera parte del proceso, regresemos a la primer línea de código del ejemplo que mostramos anteriormente para Android:

var time =new android.text.format.Time();

En el runtime de NativeScript en Android, este código es compilado justo a tiempo y ejecutado por V8. Pero, ¿cómo sabe V8 qué es android.text.format.Time()?

V8 sabe qué es Android porque el runtime de NativeScript lo inyecta, pues resulta que V8 tiene una tonelada de APIs que nos permiten configurar muchas cosas acerca del ambiente JavaScript. Podemos insertar nuestro propio código C++ para perfilar el uso del CPU de JavaScript, manejar la recolección de basura y  cambiar cómo trabajan las variables internas, entre otras cosas.

Entre estas APIs existen algunas clases de “Contexto” que permiten manipular el alcance global, haciendo posible a NativeScript inyectar un objeto android global. Este es, de hecho, el mismo mecanismo que usa Node.js para hacer públicas sus APIs globales —por ejemplo, require( )— y NativeScript lo usa para inyectar APIs que permiten el acceso al código nativo.

JavaScriptCore tiene un mecanismo similar que hace la misma técnica posible para iOS.

Regresemos a nuestro ejemplo:

var time =new android.text.format.Time();

Sabemos que nuestro código corre en V8 y que éste sabe qué es android.text.format.Time() porque NativeScript inyecta los objetos necesarios en el alcance global. Ahora bien, ¿cómo sabe NativeScript qué APIs inyectar o qué hacer cuando la llamada a Time() sea realizada? NativeScript usa la técnica de reflexión (reflection) para construir la lista de APIs que están disponibles en la plataforma en que corre. La reflexión es la capacidad que tiene un programa para examinar su estructura y comportamiento en tiempo de ejecución, y es una técnica común en el lenguaje Java. NativeScript utiliza reflexión para construir una lista exhaustiva de las APIs de cada plataforma, incluyendo en este caso android.text.format.Time.

Generar esta información no es trivial desde una perspectiva de desempeño, es por eso que NativeScript lo hace antes de tiempo y embebe los metadatos generados previamente durante el paso de construcción en Android/iOS.

Invocando código nativo

La respuesta a cómo NativeScript invoca código nativo nuevamente recae en las APIs de las máquinas virtuales JavaScript.  En esta ocasión, veremos una serie de llamadas que nos permiten ejecutar código C++ en puntos determinados durante la ejecución de JavaScript.

Por ejemplo, el código new android.text.format.Time() invoca una función JavaScript, para la cual V8 tiene una devolución de la llamada o callback. Así es como V8 tiene un callback que permite que NativeScript intercepte la llamada a la función, ejecutar ciertas acciones en un código C++ personalizado y proveer un nuevo resultado.

En el caso de Android, el código del runtime de NativeScript en C++ no puede acceder directamente a las APIs de Java como android.text.format.Time. Sin embargo, Android JNI o Java Native Interface provee la habilidad para hacer el puente entre C++ y Java, así que NativeScript utiliza JNI para hacer el salto. En iOS este puente extra no es necesario ya que el código C++ puede invocar directamente las APIs de Objective-C.

 

Dicho lo anterior regresemos a nuestra línea de código.

var time =new android.text.format.Time();

 

Sabemos que este código corre en V8  y que éste conoce qué es android.text.format.Time porque NativeScript inyecta dicho objeto. Posteriormente, NativeScript corre un proceso para la generación de los metadatos para obtener esas APIs y sabemos que cuando Time() se ejecuta, ocurre lo siguiente:

 
  1. La función callback de V8 se ejecuta.

  2. El runtime de NativeScript usa los metadatos para saber qué significa Time() y qué necesita para crear una instancia del objeto android.text.format.Time.

  3. El runtime de NativeScript usa JNI para crear una instancia del objeto android.text.format.Time y guarda un referencia a éste.

  4. El runtime de NativeScript regresa un objeto JavaScript que provee una interfaz (proxy)  al objeto Java Time.

  5. El control regresa a JavaScript donde el objeto proxy se almacena como una variable local time.

El objeto proxy  es la manera como NativeScript mantiene un mapeo entre los objetos JavaScript y los objetos nativos.

Veamos la siguientes líneas de código de nuestro ejemplo anterior:

 

var time =new android.text.format.Time();
time.set(1,0,2015);

 

Debido a los metadatos generados, NativeScript sabe todos los métodos que tiene que poner en el objeto proxy. En este caso, el código invoca el método del objeto Time set(). Cuando este método corre, V8 nuevamente invoca su función callback. NativeScript detecta que ésta es una llamada al método y luego NativeScript utiliza el Android JNI para hacer la correspondiente llamada al método en el objeto Time de Java.

Bastante bueno, ¿no? De manera general así trabaja NativeScript, porque convertir Objective-C y objetos Java en objetos JavaScript puede ser complejo, especialmente cuando consideramos los diferentes modelos de herencia que cada lenguaje usa.

NativeScript CLI (command line interface).

La línea de comandos de NativeScript o CLI es el pegamento que mantiene todo el desarrollo unido. Esta línea de comandos está involucrada en todas las etapas del desarrollo, desde los andamios hasta la entrega de una aplicación en un emulador o en un dispositivo físico.

Para resumir y desde una vista a 10,000 pies: la CLI de NativeScript abstrae la complejidad de las herramientas nativas y SDKs, brindando al desarrollador un conjunto de comandos agnósticos a la plataforma para construir, mantener  y entregar sus aplicaciones móviles.

 

fig3

 

Figura 3. Componentes utilizados para producir una aplicación [2].

 

TypeScript

Los desarrolladores NativeScript que prefieran la programación orientada a objetos pueden utilizar TypeScript.

TypeScript es un lenguaje originalmente creado por Microsoft pero posteriormente liberado como software libre.  TypeScript se originó a partir de las deficiencias “percibidas” de JavaScript para el desarrollo de aplicaciones grandes, como entre los clientes externos. Los retos de lidiar con código JavaScript complejo conduce a una demanda de herramientas específicas para facilitar el desarrollo de componentes en el lenguaje. Cabe mencionar que Anders Hejlsberg arquitecto líder de C#, creador de Delphi y Turbo Pascal trabajó en el desarrollo.

TypeScript actualmente es trans-compilado, es decir Typescript es un superconjunto tipado de JavaScript que compila a JavaScript plano, pero se espera que eventualmente no sea así a medida de que las máquinas virtuales JS  y navegadores den soporte el estándar ES6.

Angular 2

Angular 2 es la nueva versión del popular MV* framework de Google para construir aplicaciones complejas en el navegador (y más allá). Podemos decir que Angular 2  ha mutado de ser un framework a una plataforma y no simplemente una nueva versión. Angular 2 es una solución completa. Incluye rendering, compilación, vinculación, comunicación a servidor y pruebas unitarias, todo junto. Sin preocupaciones de escoger entre 20 librerías diferentes cuando solamente se necesita hacer una llamada HTTP.

Google liberó Angular 2 en septiembre de este año y ya tiene planeado liberar Angular 3 en marzo de 2017. Actualmente NativeScript se integra con Angular 2 y se espera una mayor integración y soporte para la nueva versión. Los  equipos de desarrollo de ambas empresas han trabajado juntos para lograr esto. De tal forma que, si ya conoces Angular 2, estás listo para desarrollar aplicaciones móviles con NativeScript con un poder y desempeño 100% nativo.

Componentes UI para NativeScript

Se estima que un gran porcentaje del tiempo de un proyecto de desarrollo móvil se invierte en la creación de componentes de la interfaz de usuario y, en general, en la interfaz de usuario. De ahí que, en vez de desarrollar los componentes uno a uno, resulta mejor en términos de productividad utilizar componentes prediseñados. En este sentido, Telerik, la empresa creadora de NativeScript, provee Telerik UI que es un conjunto de componentes nativos de interfaz de usuario, que agrega muchas características avanzadas por encima de la capa de componentes por defecto que se entregan en el framework de NativeScript. Telerik tiene una larga historia desarrollando componentes UI para distintas plataformas (.Net, PHP, Node) que van desde listas, botones, formas de captura, y componentes de  visualización de datos como gráficas, calendarios, etcétera.

Estatus actual y futuro

Actualmente NativeScript se encuentra en la versión 2.4, la cual ha tenido importantes y significativas mejoras, además de más de 360 plugins, lo cual es un claro indicador de que la comunidad  está produciendo mucho más código que el equipo que lleva el núcleo. Esto es un gran logro para cualquier proyecto de código abierto.

Algunas de las principales mejores en esta versión son: Flexbox layouts, web workers, soporte a Angular 2.2, Node 6 LTS, ECMAScript 2015 (ES6) y ES7.  La versión 2.5 se planea estará lista para enero de 2017.

Si quieres aprender más acerca de NativeScript, el mejor lugar para empezar son las guías y entrenamientos oficiales que puedes encontrar en www.nativescript.org y en www.telerik.com

Si estás en la Ciudad de México, hay un meetup de NativeScript con reuniones mensuales a las que puedes asistir.

¡Feliz NativeScript-ing!

Referencias

  1. NativeScript. http://nativescript.org

  2. N. Branstein. What NativeScript means to Mobile Development. http://swgu.ru/s8

Bio

Alejandro Mercado Peña es Director de Tecnología en KMMX, socio de entrenamiento Progress-Telerik en México. amercado@kmmx.mx