Kotlin Vs Java
Kotlin Vs Java
Realizado por
Francisco Sánchez Rueda
Tutorizado por
Luis Manuel Llopis Torres
Departamento
Lenguajes y Ciencias de la Computación
UNIVERSIDAD DE MÁLAGA
MÁLAGA, NOVIEMBRE DE 2019
DECLARO QUE:
Resumen ......................................................................................................................................... 1
Abstract ........................................................................................................................................... 1
Índice ............................................................................................................................................... 1
Introducción .................................................................................................................................. 3
1.1 Motivación .......................................................................................................................... 4
1.2 Objetivos ............................................................................................................................. 5
1.3 Estructura de la memoria ............................................................................................. 6
Comparación Kotlin y Java ....................................................................................................... 7
2.1 Introducción ...................................................................................................................... 8
2.2 Comparativa general ................................................................................................... 10
2.3 Aplicaciones híbridas .................................................................................................. 11
2.4 Kotlin como lenguaje de propósito general ........................................................ 12
2.5 Null safety ........................................................................................................................ 13
2.6 Casteo y comprobación de tipos .............................................................................. 20
2.7 Interoperabilidad ......................................................................................................... 25
2.8 Programación asíncrona (co-rutinas) ................................................................... 26
2.9 Concisión vs. verbosidad ............................................................................................ 35
2.10 Otros apartados .......................................................................................................... 46
2.11 Conclusión ..................................................................................................................... 46
Metodologías y fases del trabajo ......................................................................................... 47
3.1 Desarrollo de la aplicación ........................................................................................ 48
3.2 Documento teórico ....................................................................................................... 49
Especificación de requisitos, casos de uso y flujo del sistema ................................. 50
4.1 Método .............................................................................................................................. 51
4.2 Requisitos funcionales ................................................................................................ 52
4.3 Requisitos no funcionales .......................................................................................... 56
4.4 Casos de uso .................................................................................................................... 57
4.5 Secuenciación y flujo del sistema ............................................................................ 59
Diseño y modelado de datos ................................................................................................. 61
Elección e implementación de tecnologías...................................................................... 65
Desarrollo de la aplicación por requisitos ...................................................................... 69
Actualizaciones futuras .......................................................................................................... 76
Conclusión ................................................................................................................................... 78
Referencias ................................................................................................................................. 81
Referencias: sitios web ....................................................................................................... 82
Manual de Instalación ............................................................................................................. 85
Requerimientos .................................................................................................................... 85
1
Introducción
3
1.1 Motivación
4
1.2 Objetivos
5
1.3 Estructura de la memoria
6
2
Comparación
Kotlin y Java
7
2.1 Introducción
El principal motivo de este trabajo fin de carrera era ver cómo estaba la
situación en la programación Android y evaluar la incidencia de la entrada de Kotlin
en el panorama de la programación nativa. Para ello se decidió realizar un
documento teórico donde poder realizar un análisis de este lenguaje. Esto derivó en
un documento de comparación de Kotlin y Java en muchos de los aspectos
destacables de la programación donde se intentaría, de forma imparcial, analizar las
diferencias en muchos de los aspectos programáticos como la programación
asíncrona, tratamiento de objetos nulos, casteos y tipados, etc. Los temas han ido
cambiando y disminuyendo debido a la gran multitud de temas que se pusieron
encima de la mesa desde un principio, aunque al final se ha intentado comprimir el
contenido sin dejar de lado información importante.
Además, el documento contiene una introducción al lenguaje Kotlin situando
contextualmente su entrada al desarrollo y se analizan otros puntos interesantes de
la programación nativa como concepto, como puede ser el tema de Aplicaciones
nativas vs Aplicaciones híbridas o Kotlin como lenguaje de propósito general.
Todas las comparaciones con su compañero Java, van adjuntadas con ejemplos a
modo de capturas de código para lograr un mayor entendimiento y verificación del
caso teórico.
Por último, decir que este documento puede considerarse la pieza principal del
proyecto habiéndose llevado a cabo un extenso trabajo de investigación y
conglomeración de conceptos. Aun así, el trabajo más ingenieril viene del lado de la
aplicación de ofertas, la cual introduciremos en el siguiente apartado.
Todos hemos sido conscientes del auge de la telefonía móvil en las últimas décadas
y, en particular, de los smartphones en estos últimos años. Actualmente una
inmensa mayoría de las personas en el mundo poseen un teléfono móvil y una gran
parte de ellos tiene un smartphone.
A continuación, vemos una gráfica (1) de cómo han aumentado las inscripciones
(compras) de telefonía móvil en los últimos 40 años y de cómo continuarán en años
venideros:
8
Este incremento ha producido cambios de todo tipo en el campo de la tecnología,
pero el que nos atañe principalmente es el asociado al uso de smartphones para
navegar por Internet o el fenómeno de hace pocos años: las aplicaciones web.
¿Y dónde vamos con todo esto?
La incorporación continua de nuevas funcionalidades para los smartphones y de
facilidades para el usuario de estos, ha hecho que el principal uso de Internet (ya sea
en forma de web o de aplicación) provenga de los teléfonos móviles.
Esto cambió radicalmente la forma de desarrollar las webs hace unos años, teniendo
que adaptar el diseño y funcionalidad de estas al tamaño y uso de móviles lo que
posteriormente derivaría en las aplicaciones web actuales, como hemos dicho
anteriormente.
Aquí vamos a ver un gráfico (2) que demuestra cómo ha crecido el número de
páginas web servidas y adaptadas a los smartphones en la última década:
9
que Android introduce el entorno Android Runtime, con el que compila el bytecode
de Java en el proceso de instalación de la aplicación.
Es por esto último que el lenguaje más utilizado para la programación de
aplicaciones Android es Java, el más cercano a su entorno y máquina virtual y es
también por esto, que en 2011 surge un nuevo lenguaje intentando solucionar
algunos de los problemas de Java e intentando igualar las ventajas que este lenguaje
tiene en el desarrollo nativo Android: Kotlin.
JetBrains decide crear un nuevo lenguaje que se ejecute sobre la máquina virtual de
Java anteriormente nombrada y decide crearlo con el objetivo de que compile tan
deprisa como el propio Java en su máquina virtual.
El líder de JetBrains en el año 2011 comentó que ningún lenguaje tenía las
características que él buscaba (3) y creó Kotlin basándose en algunos pilares clave
como los son (4):
- Concisión frente a la verbosidad presente en un lenguaje con años como es
Java. Un ejemplo claro es la creación de clases con los métodos habituales get,
set, equals, hashcode y copy en una sola línea con la sentencia “data class”.
- Seguro intentando evitar errores en tiempo de compilación con su notada
característica Null Safe, gestionando los valores nulos de manera segura
garantizando así la no aparición de NullPointerException (NPE) de Java. La
única forma de obtener NPE en Kotlin es: llamando la excepción
explícitamente, usando el operador !! (se verá más adelante), inconsistencia
de datos o con la interoperabilidad con Java, otro pilar clave.
- Interoperabilidad, sin duda la más importante, la facilidad de
transformación de proyectos Java a Kotlin y su posible convivencia en una
misma aplicación. No tienen una sintaxis compatible, pero Kotlin sí está
diseñado para interoperar con Java. Kotlin además depende de la biblioteca
de clases de Java.
Por esto y mucho más que veremos más adelante, muchas aplicaciones son ahora
desarrolladas en este nuevo lenguaje más que emergente y, además, son muchas las
empresas que deciden trasladar su código a Kotlin por sus novedades y por su fácil
traslado e interoperabilidad, como hemos dicho antes.
En la siguiente gráfica (5) vemos cómo ha aumentado el número de desarrolladores
de Kotlin en los últimos dos años frente a los desarrolladores Java, todo esto
refiriéndonos a aplicaciones Android y no como lenguaje de propósito general (tema
a tratar más adelante):
10
Figura 3: Porcentaje de desarrolladores que programan en un lenguaje u otro por año
Otra gráfica (6) que demuestra el auge de este lenguaje se representa en la siguiente
gráfica referente a la actividad de Kotlin en Github, StackOverflow y el uso de plugins
programados en este lenguaje:
11
Otra opción que está emergiendo actualmente es el término PWA: aplicaciones web
que asemejan su comportamiento al de una aplicación móvil mediante el uso de
Service Workers que permiten seguir ejecutando la aplicación en segundo plano y
pueden ser instaladas en el móvil desde el navegador como si fueran una aplicación
del marketplace. Algunas de las funcionalidades destacadas pueden ser la
posibilidad de lanzar notificaciones push, funcionamiento sin conexión (aun siendo,
como hemos dicho, una aplicación web), menor peso que una aplicación nativa o
híbrida proviniendo del store de nuestro sistema operativo y, sobre todo, no tener
que compilar la aplicación para ningún sistema operativo: es una web, sin más.
En principio parecen ventajas de lo más significativas, la duda viene cuando se mide
el rendimiento de estas aplicaciones híbridas en juegos, ya sean 3D o HD,
animaciones, en todo lo referente a aplicaciones con gran requerimiento de gráficos
y, sobre todo, donde todo esto pueda unirse para aunar un proyecto, como puede
ser en el campo de la realidad aumentada, donde son necesarios multitud de
accesos a hardware y plugins nativos, así como gráficos: cámara, geolocalización,
giroscopio, acelerómetro, sensor de movimiento, modelado 3D… Esto unido a que
existen algunas características que no pueden ser accesibles mediante estos plugins
nativos comentados y que requieren de implementación propia desencadenando en
mayor complejidad de desarrollo hacen que el desarrollo nativo decaiga en
aplicaciones más simples, pero no en otras más complejas con los rasgos
descritos.
13
- La llamada explícita a la propia excepción: throw NullPointerException();
- Uso del operador que veremos a continuación.
- Inconsistencia en los datos y por tanto en la inicialización de los mismos.
- Interoperabilidad con Java
KOTLIN
Para la explicación se adjuntarán ejemplos básicos para el correcto entendimiento de
la teoría explicada, estos ejemplos son escalables y reflejan lo que puede ocurrir en
cualquier línea de cualquier programa por complejo que sea.
En primer lugar, conocer que en Kotlin existen las referencias que soportan valores
nulos y las que no. La declaración de un objeto o propiedad de la forma ordinaria en
un lenguaje orientado a objetos como es Java, en Kotlin se traduce en una variable
NO NULA y cuando se accede a ella y esta adopta el valor null por alguna razón, el
compilador de Kotlin nos lanza un error de que esta variable no puede adoptar el
valor nulo, y esto es importante: el compilador nos lanza el error sin necesidad de
que hayamos accedido a ninguna propiedad de esta variable nula (4).
NON-NULL TYPE
Figura 5
Figura 6
Como vemos en la imagen que representa el error, este se lanza en la línea 4, justo
donde le damos el valor null a nuestra variable, no le importa si accedemos o no a
esta variable posteriormente y nos dice eso: la variable que hemos declarado de la
forma NON-NULL no puede adoptar dicho valor por definición.
Esto es bastante potente cuando queremos que en un sistema un objeto, propiedad
o variable no queremos que adopte el valor null de ninguna forma.
Sin embargo, es posible que queramos esa versatilidad de poder tener un valor null
en una propiedad, para esto existe el tipo de referencia que posibilita un valor nulo
(nullable reference). En este caso, se podrá asignar el valor null sin problemas tal y
como ocurre en otros lenguajes. A diferencia de otros lenguajes, Kotlin avisará en
14
tiempo de compilación sobre el acceso a este tipo de propiedades cuando adopten
el valor nulo.
NULLABLE TYPE
Figura 7
Figura 8
Figura 9
- Llamada segura: sin duda la más potente y utilizada dentro del lenguaje,
esta llamada nos permite poder realizar, sin temor alguno, accesos a objetos
o propiedades posiblemente nulas. En el momento en el que el objeto al que
pertenece la propiedad a buscar sea nulo y queramos acceder, Kotlin
inmediatamente evalúa la sentencia a null en lugar de intentar acceder a
nuestro objeto nulo.
15
Figura 10
En el ejemplo podemos ver que, aunque nuestra variable “a” sea nula, se
imprimirá por pantalla “null” por acceder de manera segura y no hará el
acceso a la propiedad “length” especificado.
Figura 11
Vemos como se realiza una llamada segura, el operador Elvis y el valor que
queremos devolver: en este caso -1.
Figura 12
16
Figura 13
EJEMPLO COMPLETO
Figura 14
Figura 15
17
Figura 16
A pesar de todas estas buenas noticias, existe un inconveniente en los tipos no-
nulables que viene por definición para el cual el lenguaje ofrece también una
solución.
Las propiedades no-nulables deben ser inicializadas ya sea de forma global o en el
constructor. Muchas veces esto no es posible o no nos conviene ya que puede haber
variables que queramos inicializar por inyección de dependencias, en una función
de inicialización aparte, etc. Para esto podemos marcar una propiedad con el
modificador “lateinit”, lo cual nos permitirá inicializar la variable posteriormente.
Si accedemos a esta propiedad antes de su inicialización posterior, se lanzará una
excepción característica que identificará la propiedad a la que se ha accedido y el
por qué no se ha inicializado correctamente.
Figura 17
Figura 18
18
Figura 19
JAVA
En Java, como hemos comentado anteriormente, no queda otra que realizar
continuas comprobaciones con las sentencias if/else para no llegar a la excepción
NullPointer, esta es una de las cosas que hace a Java un lenguaje verboso y denso.
Vemos un ejemplo:
Figura 20
Figura 21
19
Figura 22
Figura 23
JAVA
Figura 24
20
Podemos ver cómo el operador ‘as’ es el equivalente al casteo típico en Java:
KOTLIN: x as String => JAVA: (String) x
Estos dos casteos realizados tienen el nombre de:
- Upcast: se representa como el casteo desde un tipo más genérico, en este
caso el tipo String, a otro menos genérico, en este caso el tipo Object o Any,
es el menos peligroso ya que nunca se produce error al ser siempre posible
por ir a una capa de abstracción mayor y ser un objeto padre en la jerarquía
orientada a objetos el tipo del resultado del casteo.
- Downcast: es el casteo de un tipo menos genérico a otro más genérico, en
este caso de tipo Object a tipo String, es de los dos el más peligroso y puede
llevar a excepción (ClassCastException) como vamos a ver en los siguientes
ejemplos:
JAVA
Figura 25
KOTLIN
Figura 26
Figura 27
21
Figura 28
Figura 29
Figura 30
Figura 31
Para realizar la misma funcionalidad que Java, deberíamos llamar a una función y
que esta nos transformara el número a entero, es razonable ya que el objetivo del
casteo no es modificar el valor que se tiene previamente, si no tiparlo sin perder la
información previa.
Un mayor acercamiento a esto es dar un tipo nulable a las variables como vimos en
el apartado Null Safety, esto aun así dará como resultado ‘null’: le ha sido imposible
realizar el casteo, pero el ‘safe cast’ no produce la excepción y devuelve null.
Aquí vemos las dos situaciones nombradas:
22
Figura 32
Figura 33
Dicho todo esto, ¿cómo se comprueban los tipos en ambos lenguajes y por qué es
esto importante en el casteo?
Dado que estamos hablando de dos lenguajes fuerte y estáticamente tipados, nos
facilitan bastante el conocer qué tipo tiene cada variable en cada momento, un
ejemplo contrario podría ser Javascript. Sin embargo, puede haber algún momento
en el que queramos conocer el tipo de una variable por haber pasado por una serie
de casteos, por haber pertenecido esta a una clase padre anteriormente, etc.
En este caso, vamos a ver cómo comprueba esto Java y Kotlin:
JAVA
Figura 34
Figura 35
KOTLIN
Figura 36
23
Figura 37
En estos ejemplos podemos ver qué sentencias utilizan cada lenguaje y realmente
poco más ya que los resultados son prácticamente triviales, solo destacar la primera
comprobación que como vemos compara una clase padre con una hija cuyo
resultado es verdadero ya que el valor de la variable con tipo Object corresponde a
un valor String válido. Es por ello que ambos lenguajes coinciden en que es intancia
del tipo String.
¿Qué relación más allá de la obvia tiene la comprobación de tipos con el casteo?
Esta pregunta viene por una de las cualidades de Kotlin. Este lenguaje ofrece un
casteo inteligente que mezcla ambos conceptos (casteo y comprobación) en la
sentencia ‘is’ al que llaman ‘smart cast’ y que tiene muchas más cualidades a parte
de la mostrada en el siguiente ejemplo:
KOTLIN
Figura 38
Figura 39
En principio no parece nada especial que no hayamos contado ya… Pero vamos a
probar en Java a ver qué ocurre:
Figura 40
Figura 41
24
Figura 42
Figura 43
Hay que especificar y programar un casteo previo y asignarle este a una nueva
variable a la cual accederemos con la propiedad ‘length’: comprobación y casteo
posterior.
Yendo más allá sobre la potencia que nos aporta la sentencia ‘is’ en Kotlin y viendo
algún sobre ello, podemos aplicar esto al tipado característico de este lenguaje y
convertir tipos nulables en tipos no-nulables solo con esta sentencia:
Figura 44
Figura 45
Podemos ver cómo, a propósito, intentamos acceder con el ‘safe access’ (?.) a la
variable en cuestión dentro del if que comprueba si la variable es tipo String no-
nulable y, efectivamente, verificamos que se ha tipado a String no-nulable ya que el
compilador nos lanza el warning de que no es necesario el ‘safe access’ en el objeto.
Aunque ponga de tipo ‘Any?’ en el mensaje del compilador, realmente está haciendo
referencia al nuevo tipado String non-null casteado por la sentencia ‘is’.
De igual manera en la primera sentencia, el objeto pasa a ser de tipo Any? a tipo
String?, el resultado, por cierto, es trivial explicado ya el desarrollo:
Figura 46
2.7 Interoperabilidad
25
con Java ha sido un pilar fundamental en la creación del lenguaje en todo momento
para facilitar el paso de los desarrolladores de este último al novedoso lenguaje de
JetBrains, de hecho existen traductores Java-Kotlin en la mayoría de los entornos de
desarrollo para facilitar este paso; así como la posibilidad de interactuar con
módulos de Javascript en la programación más orientada a web, y el desarrollo
nativo interactuando con lenguajes como C, Objective-C, Swift, etc (4).
Centrándonos un poco en la interoperabilidad fundamental: Java, sabemos que la
interoperabilidad completa es posible ya que Kotlin se compila en la máquina virtual
de Java. La interoperabilidad es, como decimos, completa hasta tal punto que
podemos conciliar ambos lenguajes en un mismo proyecto e incluso en un mismo
paquete.
Existen algunos problemas con esto y uno de ellos son las palabras reservadas por
ambos lenguajes: imaginemos que tenemos una clase escrita en Java y queremos
llamarla desde un archivo Kotlin, algo que se da bastante a menudo. Ahora uno de
los métodos de esa clase en Java tiene el nombre de una palabra reservada en Kotlin,
por ejemplo: ‘in’, ‘object’, ‘is’, ‘as’… Estas palabras no están reservadas en el lenguaje
Java, pero sí en Kotlin, por lo que es posible llamar a una función de esa forma en
Java, pero en Kotlin no. Cuando intentamos llamar a ese método del objeto en Kotlin,
el compilador no lo permite. Para ello se utiliza el método de escaping con el cual
escribimos las palabras entre tildes invertidas tal que así: `ìn`, `object`, `is`, `as`. De
esta forma el compilador no reconoce la palabra reservada y la máquina, al compilar
el código reconoce la llamada a la función implementada en Java.
De la misma forma, es posible ofrecer información a Kotlin desde los archivos Java
mediante notaciones con el carácter @. Un ejemplo lo tenemos en el tratamiento de
los valores nulos. Kotlin toma todos los valores de archivos Java como valores
nulables puesto que este lenguaje no contiene nada que lo impida. Sin embargo, si
sabemos que estos valores nunca van a tomar el valor nulo, podemos hacer que
Kotlin los pase a no-nulables en su código mediante la notación @NotNull delante
de cualquier variable, función, etc. Incluso podemos crear nuestras propias
anotaciones personalizadas que creen un comportamiento u otro en cualquier
lenguaje.
Estas son una de las muchas cualidades de interoperabilidad que ofrece Kotlin con
Java, pero no nos olvidemos que fuera de estas cualidades específicas, existe una
interoperabilidad clave entre los dos lenguajes a nivel de proyecto. Antes de
decíamos que era habitual llamar desde un archivo Kotlin a otro Java, y es que
todavía hay muchas librerías de Android que todavía no existen en lenguaje Kotlin
o que todavía no han migrado su código al lenguaje de JetBrains y siguen en Java,
por lo que, si se quiere dar uso de ellas en lenguaje nativo, se ha de llamar a estos
archivos Java y por lo tanto, esta interoperabilidad es indispensable para la
transacción continua de un lenguaje a otro. Esto es lo que hace a Kotlin especial, el
no despreciar a su lenguaje antecesor, si no arroparlo y unirse a él para beneficiar a
la comunidad novel y no defraudar a aquellos que sigan programando en Java pero
que temen el cambio a este nuevo lenguaje.
Como podemos conocer por Java, Kotlin se compila y ejecuta de manera procedural,
es decir, de forma síncrona. A diferencia de otros lenguajes basados en bucles de
26
eventos (event-loop) como Javascript, en Kotlin el código se compila y ejecuta línea
a línea, por lo que puede producirse un bloqueo en el flujo del programa o aplicación
cuando se intenta llevar a cabo un comportamiento más costoso temporalmente
hablando de lo habitual.
La programación asíncrona evita precisamente eso, el bloqueo del programa por
el alto tiempo de ejecución de una de sus partes ya sea por tareas de alto consumo
o por el tratamiento de datos masivo. Si sabemos localizar estas partes, podemos
hacer que nuestro programa prosiga sin haber obtenido resultado alguno de la
función costosa y recogerlo posteriormente para su uso.
Este bloqueo es ciertamente molesto sobre todo en aplicaciones con interfaz, en las
que un usuario está en todo momento esperando una respuesta, ya sea visual,
auditiva o de cualquier tipo, por parte de la aplicación.
Veamos un ejemplo de un problema que nos puede ocurrir habitualmente:
Figura 47
En este fragmento de código hemos preparado una llamada a una API externa y la
hemos ejecutado (última línea), el código posterior a esta última línea esperará
hasta que termine su ejecución independientemente del tiempo que lleve, llegamos
en esa línea pues a un estado de bloqueo de nuestra aplicación.
27
Figura 48
Figura 49
28
Figura 50
Figura 51
29
Nuestra función costosa requestAPI ahora devuelve una promesa, esto es una
desventaja de esta solución ya que cualquier función costosa de la que
posteriormente quieras obtener su resultado, debes devolver una promesa
con ese resultado. En este caso, devolvemos una promesa con un valor
Response<List<Sale>>. Posteriormente, hacemos uso de la librería de
promesas y obtenemos el valor de la función asíncrona con el ‘then’
característico.
Toda la sintaxis y métodos que nos ofrece esta librería de promesas va a ser
explicada en el apartado del lenguaje Java de programación asíncrona un
poco más adelante, dicho esto podemos ir directamente a las ventajas y
desventajas de esta solución.
A pesar de que son bastante usadas actualmente y que se han convertido en
una solución popular a los callbacks, las promesas tienen algunas
desventajas que conviene repasar:
- Se cambia bastante la forma de programar de una forma imperativa a
otra basada en cadena de promesas donde, por ejemplo, los bucles no
tienen mucha cabida.
- Cuando se encadenan varias promesas el manejo de errores se hace
complicado produciéndose errores de propagación y
encadenamiento como el no saber que eslabón ha sido el que ha
producido el error al capturarlo, dificultad de pasar valores de un
eslabón a otro no consecutivo, etc.
- Ya hemos comentado antes sobre la necesidad de devolver un tipo en
específico de manera obligatoria, en lugar de devolver el dato del tipo
que queremos se ha de devolver un objeto Promise con el ‘subtipo’
que queramos recoger.
- Por último y la más importante, la necesidad de aprendizaje de una
nueva API que ha de ser importada de forma externa prácticamente
en todos los lenguajes.
30
Dado que esta programación reactiva viene como sustitución a las promesas,
podemos ver unas pocas mejoras de las muchas que nos aporta frente a estas
y ver si solucionan las desventajas anteriormente mencionadas:
- Mientras que con las promesas solo podemos devolver un valor, con
los Observables es posible la devolución de múltiples valores ya que
lo que devolvemos es un stream, secuencia de múltiples datos, a los
cuales nos suscribimos (subscribe) para escucharlos todos y cómo
cambian en el tiempo.
- Los Observables son perezosos. A la hora de construir una promesa el
constructor de esta ejecuta la función que se le pasa por parámetro,
mientras que los Observables solo ejecutan esa función cuando en
algún punto del código existe una suscripción a dicho Observable.
Esto ayuda al rendimiento pues no tenemos multitud de promesas
ejecutándose y consumiendo recursos sin ser llamadas en nuestro
programa.
- Los Observables dan la posibilidad de ser cancelados. Si tenemos un
comportamiento de larga duración y entramos en una situación en la
cual ya no requerimos del resultado de dicho comportamiento,
podemos cancelarlo si se trata de un Observable pero no si se trata de
una Promesa, la cual seguiría ejecutando su función hasta terminar.
Hemos visto muchas funcionalidades y mejoras de los Observables pero,
¿solucionan algo de lo anteriormente mostrado? Pues seguimos teniendo
que: cambiar nuestra forma imperativa de programar en el lenguaje Kotlin,
devolver el valor requerido mediante un tipo en específico, en este caso, un
Observable y llevar un aprendizaje extra sobre la API de los Observables. Lo
que si hemos mejorado ha sido la obtención de múltiples valores que
soluciona el tedioso paso de datos entre promesas solucionando así los
errores de encadenamiento pero manteniendo los errores de propagación
que permanecen tanto en callbacks como en promesas.
31
Figura 52
Figura 53
32
un contexto y la adición del prefijo ‘suspend’ para la función
suspendible.
- No es necesario el aprendizaje de una nueva API como así ocurre con
las promesas.
- Esta solución es independiente de la plataforma en la que se ejecute y
no afecta a la multitud de compatibilidades que tiene Kotlin, como si
afectaba la solución de hebras en compatibilidades como Javascript.
- Volvemos a poder utilizar bucles, ya que nuestro modelo
programático no cambia, y el manejo de errores vuelve a ser
sostenible tal y como era desde un principio en el propio lenguaje.
- Los problemas de propagación, presentes en las soluciones con
callbacks, ya no están presentes pues solo existe el único anidamiento
que produce el ‘launch’.
Como vemos, las desventajas que hemos ido teniendo a lo largo de toda la
explicación de las diferentes soluciones a la asincronía se han solucionado
con las co-rutinas, es por esto por lo que actualmente es la solución más
utilizada y recomendada en Kotlin. Las co-rutinas no son un concepto
inventado por JetBrains para su lenguaje, ya se usan de forma muy parecida
en lenguajes como Go, lo ventajoso en Kotlin es que toda esa funcionalidad
está exportada en librerías que pueden ser importadas o no dependiendo si
nos son necesarias, con todo lo bueno que eso conlleva.
2.8.1 ¿Y en qué lugar nos deja esta explicación dentro de nuestra comparación
entre Kotlin y Java?
La comparación entre ambos lenguajes ya está hecha, solo queda ver qué solución o
soluciones de las nombradas anteriormente es por la que opta Java.
33
CompletionStage hace posible la obtención del resultado de forma asíncrona,
solventando así el problema de la interfaz Future, pudiendo añadir funciones
callback tras la ejecución de la llamada. Ahora es posible obtener el resultado sin
bloquear la hebra principal.
Figura 54
Figura 55
34
especificado en el callback. Sería el equivalente al ‘catch’ en las promesas de
Javascript.
Cuando se requiere de un comportamiento más complejo se produce el
anidamiento, característica en los callbacks. Para no caer en el mismo error, esta
librería nos ofrece varios métodos para solucionarlo: thenCompose y thenCombine.
Figura 56
Con thenCompose podemos realizar una cadena de promesas tal y como podemos
hacerlo en Javascript sin problemas de anidamiento.
thenCombine tan solo es una función que puede combinar dos CompletableFuture
para un mejor manejo de errores y una mayor eficiencia.
Uno de los aspectos clave de Kotlin y donde JetBrains ha hecho bastante hincapié ha
sido en la concisión de su lenguaje frente a Java. De la misma forma, sabemos de la
carencia de esta concisión en el lenguaje de Oracle, a esto se le suele llamar
verbosidad. En este apartado veremos cómo consigue esta concisión Kotlin y, si es
el caso, en qué basan sus ideas comparando cada una de estas con Java y viendo si
realmente son relevantes o no.
35
Figura 57
JAVA
Figura 58
Figura 59
Propiedad en Kotlin
Figura 60
36
que hace Kotlin, en Java deberíamos de implementarlos nosotros mismos en
la clase como hemos visto en el primer ejemplo de la clase Persona.
En caso de que queramos modificar dichos métodos, se haría de una forma
muy sencilla dentro de la propia clase:
Figura 61
Figura 62
Figura 63
Con esto podremos crear un objeto Persona de la misma forma que llamamos
en Java, incluso omitiendo la palabra “new”:
Figura 64
37
Figura 65
Así nos queda una clase útil tal y como es utilizada en Java y concisa, pero ¿y
si queremos definir otros constructores propios? Es posible tal y como lo
hacemos en Java, así tendremos tantos constructores como queramos definir
más el dominante proporcionado por Kotlin:
Figura 66
Figura 67
38
Como vemos, el método “toString” realiza una representación bastante pobre del
objeto basando su último número en el hashCode de la clase con la forma:
“nombre_de_la_clase”$”función_que_la_ejecuta”$”nombre_de_la_clase”$”hashCode_
HEX”, y el método “equals” compara las referencias de ambos objetos. En esto
profundizaremos más adelante con el comportamiento en Java, pues es muy similar
y nos sirve para explicar la herencia de métodos y la sobreescritura de estos.
La innovación de Kotlin en este aspecto viene aquí:
Figura 68
Figura 69
- Método equals: para el método “equals” aplica el mismo método a cada una
de las propiedades del objeto, si todas las comprobaciones son verdaderas,
los objetos son iguales.
Figura 70
Figura 71
Figura 72
39
Al fin y al cabo, lo que Kotlin nos ofrece es una implementación básica basada en la
experiencia y utilidad que han mostrado los usuarios tras años y años de utilización
de Java.
JAVA
Ya sabemos que en Java hay que implementar estos métodos manualmente. Vamos
a intentar ir más allá.
Antes hemos visto la superclase Any y los métodos que implementan todas las clases
hijas que creamos en Kotlin. En Java pasa algo parecido, todos los objetos que
creemos en este lenguaje heredarán de la clase Object, esta a su vez tiene una
implementación de los métodos nombrados anteriormente (toString, equals y
hashCode), es por eso que al añadir una implementación manual en nuestra clase de
estos métodos es correcto utilizar la anotación @Override, ya que indica al
compilador que vamos a modificar un método de la clase padre. Vamos a analizar si
esta “implementación padre” es suficiente para tratar con datos relacionados a la
clase que creemos o es totalmente necesaria su implementación manual.
- Método toString: el método toString de la clase Object imprime el nombre de
la clase seguido del hashCode en forma hexadecimal. Así que se nos hace
bastante necesaria su reimplementación.
Figura 73
Figura 74
Como vemos, a pesar de darle a los dos objetos los mismos valores en sus
campos, el método “equals” de la clase Object no los tiene en cuenta y
devuelve falso.
40
Figura 75
Figura 76
Podemos deducir, como era de esperar, que es necesaria una implementación de los
métodos “toString” y “equals” en la clase a representar, así como la implementación
de una interfaz perteneciente a la clase Object para poder utilizar el método clone,
equivalente al copy de Kotlin.
Aun así, esta demostración ha servido para ilustrar que, aunque a priori estos
métodos no sean muy útiles, existen y el hecho de implementarlos en tu clase
implica la sobreescritura de estos que provienen de la clase Object.
Figura 77
41
En este caso lo que hemos querido hacer es que al aplicar la función de incremento
a nuestra clase “Coordenada”, se incrementen ambas propiedades (ejes de
coordenadas).
Aquí es donde Kotlin se distancia de Java en este subapartado. Java no soporta la
sobrecarga de operadores definida por el programador como acabamos de
hacer, solo la sobrecarga ya definida por el propio lenguaje.
Figura 78
Figura 79
42
Figura 80
Vemos como el argumento en este caso podría ser de tipo entero y los casos
devuelven un valor booleano, es decir, podemos operar con dicho argumento y no
solo limitarnos a comparar su valor con otro.
Figura 81
Figura 82
Figura 83
43
Vemos como creamos un constructor privado de forma que podamos controlar las
instancias que se crean de nuestra clase Singleton y creamos la instancia única que
queremos de forma privada. De la misma forma construimos un getter de esta
instancia para que clases externas puedan acceder a ella.
Dicho esto, vamos a ver cómo lo puede llegar a hacer Kotlin:
Figura 84
Una línea, dos palabras. Hay un gran contraste con la forma de crear clases en Java,
tanto por la densidad de código como por la palabra “object” en lugar de “class”.
Realmente estamos definiendo un solo objeto, que es semánticamente lo mismo que
definir una clase Singleton: una clase que siempre de la que siempre tendremos uno
y solo un objeto.
Un apunte para ambos lenguajes es que tanto en Java como en Kotlin puedes elegir
entre hacer inmutable o mutable tu objeto / clase Singleton.
No nos quedamos aquí, volvamos a Java. Porque existe una forma poco conocida que
cabe mencionar para crear un Singleton.
Figura 85
44
Figura 86
Resulta tedioso tener que realizar la misma llamada y asignación para todos y cada
uno de los elementos con los que queramos operar. Esto conlleva tanto problemas
con la lectura (volvemos a la verbosidad) como problemas de rendimiento.
El problema de concisión es trivial, cada vez que queramos operar con algún
elemento de la interfaz tenemos que acceder a él de la forma vista anteriormente
mínimo una vez (se puede guardar en una variable).
Figura 87
Figura 88
El propio id del elemento de la interfaz (en este caso “text1”) es una propiedad de la
vista en cuestión, y a su texto podemos acceder como si de una propiedad se tratase.
Es solo un ejemplo de lo que Kotlin puede ofrecernos en cuanto a versatilidad y
facilidad al programar para este propósito.
Gracias a un plugin llamado Kotlin Android Extension que nos ofrece el IDE de
JetBrains, el cual viene preinstalado al iniciar el proyecto y solo tenemos que
habilitar, podemos hacer cosas como esta. JetBrains, al igual que el propio lenguaje
Kotlin, vuelve a fijarse en los comportamientos más usados y engorrosos de su
antecesor y logra añadir nueva funcionalidad; ya sea formando parte del propio
lenguaje o, si no es posible, en su propio IDE para que programar, en este caso
Android, en su lenguaje vuelva a ser mucho menos engorroso que con su
“antecesor”.
Otro punto por destacar, comentado anteriormente, es el del rendimiento. Poco se
puede hacer en ese aspecto ya que, a pesar de que el plugin de Kotlin nombrado
anteriormente soluciona problemas de concisión, no lo hace en cuanto a
rendimiento pues el compilador a fin de cuentas termina entendiendo un
findViewById detrás de todo eso.
Imaginemos una interfaz con unos cuantos elementos, digamos 10 o 20. Tenemos
que acceder a varios de ellos en un momento del ciclo de vida de nuestra actividad.
Android por cada llamada findViewById, recorre todo el árbol jerárquico de la
interfaz XML para buscar nuestro elemento y todos conocemos del posible coste de
esa operación en determinados qué casos. Sumándole que probablemente
queramos modificar el valor de alguna propiedad de este elemento buscado (como
hemos visto anteriormente), aumentamos aún más la complejidad de nuestra
funcionalidad.
45
La solución a este problema no está en uno u otro lenguaje, nos la ofrece Android
independientemente del lenguaje con el que se complemente y no es más que
utilizar una función “findViewTraversal” que no tendrá que recorrer el árbol
jerárquico tantas veces como queramos acceder a dicho elemento en nuestro código.
En caso de que este elemento se utilice varias veces podemos guardar el elemento
en una variable miembro para poder así utilizarla tantas veces como se quiera (13).
Tratados todos estos temas en relativa profundidad, solo nos queda comentar
algunos casos que se nos hacen triviales o que no requieren de demasiada
explicación y que contribuyen a la concisión de Kotlin igual o tanto como los
anteriormente mencionados: la omisión del punto y coma siempre que sea
necesario choca con la obligatoriedad de Java para escribirlo, dotando a Kotlin de
nuevo con mayor ligereza a la hora de programar; hablando anteriormente de
sentencias de control como el when-switch no podemos dejar atrás la sintaxis
sencilla de los bucles “for” en Kotlin frente a la sintaxis repetitiva de Java; el ya
nombrado y explicado apartado del Smart-cast deja bastante clara la balanza en este
aspecto a favor de Kotlin; por último, decir que el hecho de que Kotlin no sea un
lenguaje tan fuertemente tipado como Java, da un respiro y versatilidad al
programador sin dejar de lado la robustez que te ofrece el tipado de datos.
2.11 Conclusión
Como conclusión, y rompiendo una lanza a favor de Java, podemos decir que Kotlin
ha obtenido toda esta concisión a través de años estudiando la usabilidad que se le
da al lenguaje que ha venido a sustituir. Ha sabido corregir de manera correcta uno
de los puntos flacos de Java, que es su verbosidad, a través del estudio de sus
usuarios y lo que constantemente llevan pidiendo años y años pero que Java por su
antigüedad y presencia no es capaz de dar.
Al igual que a día de hoy estamos hablando de esta diferencia de concisión entre Java
y Kotlin, en un futuro y no muy lejano el papel de perdedor podría tenerlo Kotlin
contra un posible sucesor llevado ahí por la experiencia.
46
3
Metodologías y
fases del trabajo
47
La aplicación desarrollada nace de la necesidad por exponer en un ejemplo
real las características de Kotlin nombradas, ya que los ejemplos de código del
documento teórico pueden discernir de la realidad. Sabiendo esto, se barajó la
posibilidad de desarrollar una aplicación de mucho menor tamaño y hacerlo en
ambos lenguajes para ver las diferencias entre ambos. Pero al final se acabaría
pecando de igual forma en la simpleza de los ejemplos, por lo que se decidió hacer
una aplicación de mayor tamaño y centrarse en las características propias de Kotlin
dejando la comparativa solo al documento teórico.
Como primer acercamiento se decidió realizar una aplicación móvil únicamente
para así centrarnos en las cualidades del lenguaje y poder profundizar un poco más
en las propiedades nativas. Pero después se decidió hacer un esfuerzo extra por
realizar un desarrollo completo en torno a una aplicación cliente-servidor de
administración para los propietarios de las tiendas además de la aplicación móvil
para los clientes y así poder realizar un proceso de ingeniería más completo y que
los datos de la aplicación no estuvieran almacenados localmente.
Por lo tanto, el proyecto queda dividido en la aplicación nativa, programada en
lenguaje Kotlin, a la cual accederá el usuario para ver las ofertas de las tiendas que
le rodean, y una aplicación cliente-servidor que servirá como administración a los
propietarios de dichas tiendas, programada en NodeJS y Angular 7.
Podemos empezar hablando sobre la organización del trabajo y como hemos ido
llevando a cabo las tareas.
Una vez aclaradas cuáles han sido las fases del trabajo, queda ver cuál ha sido la
metodología para desarrollar esas fases. Para el desarrollo de la aplicación se ha
aplicado la metodología ágil Scrum adaptada a trabajos “individuales”. El desarrollo
ágil basa sus fundamentos en el desarrollo iterativo e incremental, los requisitos y
demás componentes del proyecto van variando en cantidad y complejidad. Scrum te
48
da las instrucciones para aplicar este tipo de desarrollo a tu proyecto y, en este caso,
vamos a tener un backlog (lista) de tareas ordenadas por prioridad de la cual se van
a ir extrayendo y desarrollando de mayor prioridad a menos, pudiendo quedar
algunas de estas últimas incluso sin hacer en la iteración correspondiente.
En el caso de este proyecto, se ha aplicado un
método especializado para la extracción y
priorización de requisitos: el método
MOSCOW (14). Este método intenta dividir los
requisitos en 4 partes principales: M (must)
son requisitos indispensables para el proyecto
sin los cuales no podría existir el mismo, S
(should) son requisitos importantes pero que
no se consideran vitales para el proyecto, C
(could) son requisitos que no aportan tanto
valor como los que están en las dos categorías Figura 89: Método MoSCoW
anteriores pero que aun así representarían una
buena incorporación al proyecto y, por último, W (wont o wish) son requisitos que
no son considerados parte de la iteraciones por su poco valor y a los que se acudirán
en caso de tener tiempo sobrante. Por lo tanto, si comenzamos una extracción de
requisitos con este método, al terminar los tendremos priorizados en 4 ramas y
posteriormente se extraerán las tareas de cada uno de estos.
Las tareas obtenidas de estos requisitos forman el backlog mencionado
anteriormente, del cual se irán extrayendo tareas y siendo realizadas con la mayor
brevedad posible.
Una vez acabados los requisitos-tareas de una de las listas, se han realizado
reuniones para ver cómo abordar el siguiente bloque y si el realizado cumple con
las características pensadas y especificadas en un principio.
Por otra parte, para la redacción del documento teórico se iban acordando qué
apartados tenían mayor prioridad dependiendo del peso programático, así como de
las nuevas aportaciones del lenguaje Kotlin. Una vez acordados los puntos a tratar
en la reunión, se redactaban con la información obtenida previa a la redacción y se
implementaban los ejemplos junto a la explicación hasta la siguiente reunión.
En cuanto a la experiencia con este método ha sido buena. Al haberse aplicado con
anterioridad a proyectos personales e inclusos de empresa, no ha habido problemas
con la adaptación a un proyecto pequeño e individual. Las reuniones han
transcurrido con normalidad y se han ido exponiendo los problemas que se han ido
teniendo a lo largo del desarrollo con su correspondiente solución.
49
4
Especificación
de requisitos,
casos de uso y
flujo del sistema
50
4.1 Método
Roles:
- Usuario (utiliza la aplicación móvil para ver las ofertas)
- Propietario (utiliza una aplicación de administración para inscribir sus ofertas en
la plataforma)
MÉTODO MOSCOW
Se deberá redirigir
El usuario podrá ver El usuario podrá ver
Los datos visibles por con la información
la vista previa de la un tutorial al iniciar
los usuarios están adecuada el tráfico
cámara al iniciar la la aplicación como
almacenados en línea aplicación-web de la
aplicación guía de uso
empresa
51
Los datos visibles
por el usuario
estarán almacenados
localmente
Una vez tenemos todos los requisitos extraídos y priorizados queda separarlos en
requisitos funcionales y no funcionales. En primer lugar, vamos con los requisitos
funcionales ya que la mayoría de los requisitos no funcionales derivarán de estos.
Además, dividiremos los requisitos funcionales en los que han sido cubiertos para
la exposición de este TFG y los que se dejarán para posteriores actualizaciones.
El usuario podrá navegar entre las ofertas de las diferentes tiendas RF-3
El usuario podrá ver una oferta en concreto y sus datos relacionados de una RF-4
tienda
El usuario podrá navegar hacia el enlace externo de la oferta, que se encontrará RF-5
en web de la tienda
El propietario podrá iniciar sesión en la plataforma de administración RF-6
El propietario podrá gestionar sus tiendas (crear, ver, modificar y borrarlas) RF-8
El propietario podrá gestionar las ofertas asociadas a sus tiendas (crear, ver,
modificar y borrarlas) RF-9
52
El usuario podrá ver la vista previa de la cámara al iniciar la
RF-1 aplicación
Prioridad M (Must)
Comentarios
Prioridad M (Must)
Comentarios
RF-3 El usuario podrá navegar entre las ofertas de las diferentes tiendas
Prioridad M (Must)
Comentarios
Dependencias RF-3: El usuario podrá navegar entre las ofertas de las diferentes
tiendas
53
Descripción El usuario al seleccionar la oferta verá todos los datos relacionados
con esta, así como un botón para acceder al enlace externo de la
oferta.
Prioridad M (Must)
Comentarios
Dependencias RF-4: El usuario podrá ver una oferta en concreto y sus datos
relacionados de una tienda
Prioridad M (Must)
Comentarios
Dependencias
Descripción El usuario propietario de una tienda podrá iniciar sesión en una
plataforma de administración habilitada para gestionar todos los
datos referentes a la aplicación móvil.
Prioridad S (Should)
Comentarios
Prioridad S (Should)
Comentarios
54
El propietario podrá gestionar sus tiendas (crear, ver, modificar y
RF-8
borrarlas)
Descripción El usuario podrá crear tiendas asociadas a su marca, así como ver
todos sus datos, modificarlos y borrar cualquier tienda. La operación
de borrado también elimina todas las ofertas asociadas a esa tienda.
Prioridad M (Must)
Prioridad S (Should)
Ahora veremos qué requisitos se han quedado, por falta de tiempo y escasez de
prioridad, fuera de nuestro proyecto actual pero que en un futuro serán candidatos
para entrar:
55
Requisitos funcionales sin cubrir Código
El usuario podrá ver un tutorial al iniciar la aplicación como guía de uso RF-10
Vemos como todos los requisitos pertenecen a las iteraciones C (could) y W (wont o
wish) que como comentamos en el apartado Metodologías y fases del trabajo,
constan de requisitos que no son importantes para versiones del proyecto. Podemos
decir, por lo tanto, que el método MOSCOW se ha realizado perfectamente y, aunque
normalmente no pasa, ha especificado desde un principio correctamente las
prioridades y especificación de los requisitos y esto se debe, en parte, a la poca
complejidad y extensión del proyecto. En proyectos de mayor calibre, la prioridad
de los requisitos suele variar y cambiar de una fase a otra. De hecho, es una de las
cualidades del desarrollo ágil, la capacidad de cambiar en cada iteración del
proyecto.
56
Requisitos no funcionales Código
Los datos visibles por los usuarios están almacenados en línea RNF-4
Los requisitos RNF-3 y RNF-4 son excluyentes el uno del otro. Cuando RNF-4 se
complete, el requisito RNF-3 ya no será necesaria y quedará apartado del proyecto
como un requisito obsoleto.
De estos requisitos que hemos analizado, nacen los casos de uso del sistema. En
primer lugar, analicemos los casos de uso asociados a la aplicación móvil de ofertas.
Para ello hemos utilizado la herramienta MagicDraw que proporciona un potente
creador de diagramas de todo tipo entre los que se encuentran los utilizados en este
proyecto: diagrama de caso de uso, diagrama de secuencia y diagrama de clases.
57
localizar una tienda depende de la geolocalización y de los sensores de orientación
para una actualización futura (CU-5 y CU-6 respectivamente). Como único actor
tenemos al usuario, cliente de las tiendas y participante de la aplicación sin
necesidad de registro.
Ahora asociaremos los casos de uso representados con los requisitos del sistema:
Al ser la aplicación móvil el principal motivo del desarrollo y dado que el panel de
administración no deja de ser una aplicación cuyas bases de usabilidad ya están más
que establecidas, vamos a llevar a cabo el diagrama de secuencia de la aplicación
móvil, del cómo el usuario pasa de iniciar la aplicación por primera vez a ser
redirigido a la oferta de forma externa por nuestra aplicación.
59
Figura 92: Diagrama de secuencia de aplicación
60
5
Diseño y
modelado de
datos
61
Comenzar con el análisis de datos diciendo que se ha utilizado una base de
datos relacional y es por varios motivos: esta nos facilita un esquema exacto de los
datos que vamos a almacenar, es el único formato de base de datos compatible con
el framework utilizado para la implementación del servidor API y, el principal
motivo, es más escalable para actualizaciones futuras.
Las bases de datos no relacionales suelen tener una finalidad más científica al no
tener una estructura clara de los datos y almacenar todo tipo de datos sin filtro. Esto
permite tener almacenado todos estos datos independientemente de su tipado y su
origen, para su posterior análisis y procesamiento. No es nuestro caso.
Nosotros realizamos un análisis de datos previo y conocemos la estructura de
nuestros datos y aportamos escalabilidad al proyecto tanto con el análisis como con
la elección del tipo de base de datos.
Ahora se va a adjuntar el diagrama de clases implementado y vamos a pasar a
explicar cada uno de los modelos, atributos y relaciones:
** Todos los modelos tienen un id único para identificar de forma inequívoca a cada
instancia de la clase que será autoincrementable y que, por lo tanto, no es un dato
manipulable por el cliente.
62
Tengamos en cuenta en primer lugar al modelo Sale (oferta): cada oferta tiene su
nombre, precio, descuento en forma de número que posteriormente, en la
aplicación, mostraremos en forma de porcentaje, la url a la oferta original de la
página web de la tienda donde se navegará de forma externa, la url de la imagen
mostrada en la aplicación y dos fechas de actualización y creación de la instancia
para ámbito estadístico o de información. Todos estos campos son introducidos, a
excepción del mencionado id a pie de diagrama, por la empresa en el panel de
administración y visualizados posteriormente por el usuario en la aplicación móvil.
Esta clase tiene, además, una relación de muchos a uno con la clase Shop (tienda):
una oferta pertenece a una tienda y una tienda puede tener muchas ofertas.
El modelo Shop (tienda) tiene como atributos un nombre para ser identificada, una
latitud y longitud con el objetivo de ser localizada por el usuario con la aplicación
móvil y las fechas de actualización y creación ya mencionadas anteriormente. Estos
atributos de geolocalización (latitud y longitud) son leídos por la aplicación móvil y
son los que utiliza la función de comparación de coordenadas en Kotlin para
comprobar que estamos cerca de la tienda en cuestión.
Así mismo, esta clase tiene una relación de pertenencia de muchos a uno con la clase
Person (persona) la cual hace referencia a que una tienda pertenece a una persona
y una persona puede ser propietaria de muchas tiendas.
63
Por último, el modelo Role (rol) que contiene el nombre del rol (en este caso,
propietario), una posible descripción del rol y las fechas de actualización y creación.
Como hemos dicho esta clase contendrá una relación con Person (persona) de
muchos a muchos implementada con una tabla intermedia llamada RoleMapping,
que no ilustramos en el diagrama. Tanto este modelo como el de AccessToken (token
de acceso) son expuestos por el framework utilizado y es por ello que se ha utilizado,
aunque ya se explicará con más detenimiento en el apartado de Elección de
tecnologías (16).
64
6
Elección e
implementación
de tecnologías
65
Dividiendo en dos de nuevo el análisis, ahora de las tecnologías elegidas, nos
queda en primer lugar y más importante la aplicación móvil. Con la aplicación
móvil no se han tenido dudas. Desde un principio se quiso hacer en Kotlin porque
de ello va el proyecto completo. En lo que si había cuestiones era en si hacer un
desarrollo paralelo igual con el lenguaje Java.
Como ya dijimos en el apartado Introducción al desarrollo de la aplicación, se barajó
la posibilidad inicialmente de desarrollar la aplicación en ambos lenguajes. Pero
debido a que tampoco era el tema principal del proyecto y que no se le dedicarían
tantas horas como para realizar un desarrollo lo suficientemente extenso como para
comprobar diferencias prácticas entre ambos lenguajes, se decidió aportar un
mayor número de ejemplos sencillos en el apartado teórico y dejar la aplicación
móvil como desarrollo Kotlin únicamente.
Es por eso que esta parte del desarrollo tampoco requiere de mucho análisis pues la
elección de este lenguaje para la aplicación formaba parte del propio proyecto y no
ha habido nunca discusión sobre ello.
Destacando un poco por encima, la experiencia ha sido bastante más llevadera que
programando con Java. La obtención de vistas del layout por ids es mucho más
concisa y en general es un lenguaje mucho más limpio Gracias a la interoperabilidad
con Java se ha aprovechado alguna que otra librería implementada en Java y el
sistema de tipado de Kotlin ha resultado ser todo un descubrimiento en la
programación Android.
Gracias a la programación nativa se han podido implementar de forma eficiente y
controlada el gestor de permisos, tanto de cámara como de geolocalización, y, sobre
todo, la vista de la cámara; con un lenguaje híbrido hubiera sido bastante tedioso y
probablemente bastante más ineficiente. Te da bastante seguridad el acceder a
librerías nativas donde sientes tener el control del más mínimo detalle.
Aun así, el análisis con las diferencias entre ambos lenguajes aplicados a la
programación Android se encuentra en el documento teórico situado en el anexo.
Por otra parte, tenemos tanto el servidor API como la aplicación de administración
en los cuales se ha utilizado el lenguaje JavaScript y en concreto TypeScript en la
aplicación de administración, un JavaScript tipado con semejanzas a Java por tener
clases, interfaces, etc. Desgranando un poco, en el servidor API como decimos se ha
utilizado JavaScript, en concreto un entorno de ejecución de JavaScript orientada a
eventos asíncronos llamado NodeJS (17). Al ser NodeJS un entorno de ejecución, se
necesita un framework para la creación de un servidor API. Uno de los frameworks
más conocidos para este tipo de uso es Express, te da las herramientas necesarias
para poder crear una API desde cero y poder manipular totalmente la estructura de
tu servidor. Pero no es nuestro caso. Finalmente se ha decidido utilizar Loopback,
otro framework que al igual que Express, te facilita el poder crear tu API REST sin
ningún problema. La ventaja de Loopback es que ya te proporciona una estructura
base en la que está implementado un sistema de roles y tokens de acceso, así como
estructuras para modelos de Usuario y gestión de contraseñas. También te facilita
una interfaz para poder ver los métodos de tu API REST que utiliza Swagger y, por
si fuera poco, te da todos los métodos básicos para cada modelo que crees desde
cero: get, put, post, delete, patch, head… A todo esto, se le suma que tiene un CLI (o
terminal) propio en el que podemos gestionar, con comandos específicos, la
66
creación y modificación de modelos y relaciones, roles y accesos a los métodos de
los modelos, creación de nuevos métodos, modificación de los ya existentes,
servidores proxy, etc.
Este framework nos proporciona escalabilidad y facilidad para la implementación
de nuestra API. La documentación de Loopback es bastante extensa y es un software
implementado por IBM, empresa de las más grandes y exitosas del sector por lo que
tiene una gran comunidad y solución a los problemas que plantean los
desarrolladores de la propia comunidad. En el proyecto se utiliza la versión 3 de este
framework, pero actualmente está en producción la versión 4 que pasa a utilizar
TypeScript y hace más robusta tu servidor API con el lenguaje tipado y más
controlable con una nueva estructura basada en módulos.
Así pues, la elección de la base de datos es fácil una vez que se ha elegido el
framework. Loopback tiene compatibilidad con bases de datos relacionales, a su vez
tiene dos conectores potentes en este momento: PostgreSQL y MySQL. Por
experiencia en el trato, se ha decidido utilizar MySQL que es una base de datos
relacional que te proporciona un entorno de trabajo llamado MySQL Workbench con
el que puedes tratar los datos con una interfaz gráfica más cómodamente. Aun así, y
como se ha comentado ya en el apartado de Diseño y modelado de datos, se hubiera
elegido una base de datos relacional por la escalabilidad que esta proporciona y por
conocer en todo momento la estructura de los datos introducidos, leídos,
modificados o borrados, cualidad que casa bastante bien con el lenguaje TypeScript
que vamos a nombrar a continuación.
Por último, comentar que, para la elaboración de los diagramas de casos de uso,
secuencia y de clases se ha utilizado el lenguaje UML como lenguaje más conocido y
utilizado actualmente con el software MagicDraw que, por experiencia, da un buen
67
nivel de detalles a los diagramas y aporta bastantes funciones que poder
implementar.
68
7
Desarrollo de la
aplicación por
requisitos
69
Podemos comenzar con el desarrollo de la aplicación móvil Android en Kotlin.
El primer requisito a abarcar fue el de intentar mostrar la realidad aumentada en la
aplicación asociado a RF-1, para eso utilizamos la vista previa de la cámara y la
implementamos en una clase a parte que implementaba la interfaz SurfaceView,
interfaz que permite crear vistas de todo tipo en tu aplicación. Se utilizaron ideas de
algunas páginas web y todas estaban en lenguaje Java, por lo que tuvimos que
implementar la traducción a Kotlin, problema que ha sido recurrente durante el
desarrollo del proyecto. Ya hemos dicho que Java puede convivir perfectamente con
Kotlin, pero la idea de la aplicación era de hacerla completamente en Kotlin.
Con esto finalizamos la implementación de la vista previa de la cámara añadiendo
funciones de pausa y reanudación de la aplicación en la actividad principal para
liberar la cámara a otras aplicaciones o que nuestra aplicación pueda obtenerla sin
tener que arrancarla desde cero una y otra vez.
Lo siguiente a analizar es la implementación de los
permisos de cámara y geolocalización. La gestión de
permisos se ha llevado de forma síncrona: primero
hemos lanzado el aviso de autorización para operar con
la cámara, la cual hemos hecho que siempre se muestre
en orientación vertical, y después el permiso para la
localización. La gestión de estos permisos en móvil
siempre es una implementación complicada debido a las
numerosas posibles respuestas que puede dar el usuario
(rechazar siempre, rechazar esta vez, permitir en uso,
permitir en segundo plano…).
Para el permiso de cámara asociado a RNF-1, si la
respuesta era afirmativa, daba igual que fuera en primero
o en segundo plano inclusivo pues liberamos la cámara
cuando la aplicación no está en primer plano,
Figura 94: Permiso de
procedíamos a lanzar la autorización de permiso de cámara
geolocalización. En caso negativo, cerramos la
aplicación, pues nuestra aplicación no tiene sentido sin
poder tener este acceso.
Para el permiso de geolocalización asociado a RNF-2,
tampoco nos importa que la respuesta afirmativa sea
para primer plano o segundo plano inclusive, pues la
aplicación intentará localizar las tiendas cuando pueda
independientemente del plano en el que se encuentre la
aplicación. Si es negativa, procederemos a mostrar la
visión de la cámara, pero evidentemente no será
localizada ninguna tienda y, en caso de que haya
rechazado solo una vez la solicitud, se mostrará un aviso
de que la localización es necesaria para el correcto
funcionamiento de la aplicación. En este caso, si el
usuario quiere seguir haciendo uso de la aplicación,
deberá dar permisos a la aplicación a través de Ajustes.
En cambio, si es afirmativa, mostraremos de igual forma
Figura 95: Permiso de
la vista previa de la cámara y se formarán nuevamente dos
geolocalización
posibilidades. La función GPS puede estar encendida o no,
70
en caso de que esté encendida se mostrará el mensaje
predeterminado de Android para encender la
ubicación precisa desde la aplicación y poder seguir
funcionando, si se enciende la aplicación funcionará
perfectamente y si no, el usuario deberá encenderla
manualmente desde los Ajustes del teléfono.
Posterior a esto, obtenemos todas las tiendas de
nuestro servidor mediante una llamada HTTP a
nuestro servidor API al recurso ‘/tiendas’. Este recurso
está abierto a cualquier llamada, no es necesaria
autenticación para obtener las tiendas de la base de
datos. Esto lo realizamos de forma asíncrona mediante
una co-rutina. Aunque ya ha sido explicado en el
documento teórico, la co-rutina va a ejecutar la llamada
a la API REST mientras el usuario lleva a cabo la gestión
de permisos asíncronamente, es decir, paralelamente.
Figura 96: Geolocalización
Una vez obtenidos los datos del servidor, nos
ayudamos de la geolocalización conseguida
previamente para comparar latitud y longitud de las
tiendas cercanas con latitud y longitud del dispositivo
del usuario, asociado a RF-2. La comparación se realiza
con el valor absoluto de la resta tanto de latitudes
como de longitudes en decimales. Si esta resta es
menor que 0.0003, que es el equivalente en decimales
de 1 segundo, tanto en latitud como en longitud, es
mostrado un mensaje con la tienda en cuestión para
poder acceder si el usuario quiere a las ofertas de dicha
tienda. En el caso de que se pulse ‘CANCELAR’, el
cuadro de texto desaparecerá y se volverá a intentar
identificar las tiendas cercanas con el mismo método.
En caso de pulsar ‘ACEPTAR’, mostramos el listado de
las ofertas de la tienda seleccionada lanzando la Figura 97: Tienda encontrada
actividad con Intent.
Una vez seleccionamos la tienda en cuestión, como
decíamos, mostramos todas las ofertas actuales en
forma de lista con ListView de la tienda encontrada,
asociado a RF-3. Podremos ver la información
suficiente y podremos navegar entre ellas. Las ofertas
son obtenidas mediante otra llamada a nuestro
servidor API, esta vez al recurso ‘/tiendas/:id/ofertas’,
donde ‘id’ es el identificador único de la tienda
anteriormente seleccionada y ‘ofertas’ las ofertas
asociadas a la tienda en cuestión.
Una vez seleccionada una de las ofertas, veremos todos
los detalles de esta en una nueva actividad lanzada y la
imagen de esta en mayor tamaño. Esta llamada que se
realiza al servidor para retraer toda la información de Figura 98: Ofertas
la oferta se ejerce mediante el recurso
71
‘/tiendas/:id/ofertas/:fk’, donde ahora ‘fk’ es el
identificador único de la oferta seleccionada. Esta
acción está asociada al requisito RF-4.
Cuando el botón de compra es pulsado, la aplicación
redirige su flujo al enlace externo de la oferta,
introducido por el propietario mediante el panel de
administración, por lo que el usuario será llevado a la
web oficial de la tienda en su navegador
predeterminado. Hecho esto nuestra aplicación para
de obtener información acerca del usuario y el flujo
queda de manos de la aplicación de la tienda. Esta
acción está asociada al requisito RF-5.
Todas y cada una de estas acciones descritas han sido
parte de una iteración posterior en la que se ha hecho
uso de una base de datos en línea administrada por
nuestro servidor. Previamente a esta versión, los
datos eran retraídos de una base de datos SQLite la Figura 99: Oferta
cual se implementaba con un manejador (DBHandler)
que contenía todos los métodos necesarios que traducían a lenguaje SQL la llamada
y con un creador de instancias y modelos (DBHelper) que creaba, ayudándose de la
interfaz SQLiteOpenHelper los modelos e instancias necesarias para hacer las
pruebas localmente.
Esta primera implementación local obedece al requisito no funcional RNF-3 y la
segunda implementación en línea se relaciona con RNF-4.
72
no sean correo electrónico, y la contraseña, que puede ser visible al escribir. Esta
acción queda asociada al requisito funcional RF-6.
Para listar las ofertas el diseño es el mismo y se quiere representar los datos para
que sean lo suficientemente diferenciables en la tabla. Para listarlas se llamará al
método ‘/people/me/sales’ que trae todas las ofertas de las tiendas de las que es
73
propietario el usuario. Realmente la llamada atraviesa 3 tablas: la tabla Persona,
desde donde se realiza la llamada, la tabla Tienda, por donde tiene que pasar para
saber de qué tiendas es propietario, y la tabla Oferta.
74
Figura 103: Administración de tienda
Esta gestión tanto de tiendas como de ofertas quedan asociadas a los requisitos
funcionales RF-8 y RF-9 respectivamente.
El último requisito por cubrir es el RF-7: podemos cerrar sesión de la plataforma en
la parte de abajo a la izquierda, en la barra de navegación. Esto borrará el token
creado por el inicio de sesión del almacenamiento local y redireccionará a la página
de inicio de sesión vista previamente.
75
8
Actualizaciones
futuras
76
Gracias al método MOSCOW, conocemos las posibles mejoras del sistema que fueron
requeridas desde un principio. En este caso, habría mejores tanto para la aplicación
como para el panel de administración. Podemos enumerar esos requisitos
especificados de forma inicial ordenados por prioridad:
- El usuario podrá ver un tutorial al iniciar la aplicación como guía de uso
- El usuario podrá iniciar y cerrar sesión en la aplicación
- El propietario podrá ver los datos de su empresa asociada y modificarlos
- El propietario podrá cambiar la contraseña de su cuenta
- Se deberá redirigir con la información adecuada el tráfico aplicación-web de
la empresa
- El usuario podrá guardar las ofertas como favoritas
- El usuario podrá ver las ofertas guardadas en un apartado a parte de la
aplicación
- El propietario podrá registrarse en la plataforma
A parte de estas mejoras ya especificadas, podemos añadir otras más realizando una
tormenta de ideas y filtrando algunas por su poco peso y valor dentro del sistema:
- Se mejorará el algoritmo de realidad aumentada para que sea más preciso y
eficiente, siendo en eso en lo que se basa la aplicación
- Se aumentará la cantidad de información que obtenemos de las ofertas,
aportando más fotos, tallaje y algunas valoraciones de clientes
- Se implementará una pasarela de pago para que se puedan comprar las
ofertas desde la aplicación de forma rápida y segura
- Mejora del diseño para adaptarlo a cada tienda seleccionada
- El usuario podrá iniciar sesión con Google o Facebook en la aplicación
77
11
Conclusión
78
Acostumbrado a trabajar en grupos de desarrollo tanto en el ámbito
estudiantil como en el laboral, este proyecto ha sido todo un reto al tener que
afrontarlo de manera individual solo con la ayuda del tutor.
El ámbito más costoso ha sido sin duda el teórico. Aun sin ser un gurú de la
programación nativa ni de Kotlin, he aprendido bastante de ellos a la hora de
realizar esta comparación. He visto, hablando de manera más global, la diferencia
entre dos lenguajes de distinta época independientemente de que estemos hablando
de Kotlin y Java en específico porque, al final, las principales diferencias se
concluyen en una diferenciación en la época de aparición de estos. Java nació mucho
antes que Kotlin y eso puede notarse en prácticamente cualquier lenguaje al avanzar
tan rápido la tecnología.
El trabajo de investigación realizado ha sido bastante fructífero y, acostumbrado a
investigar para el desarrollo, esta forma de investigación teórica ha resultado ser un
descubrimiento para mí y ser bastante satisfactorio. Si logras abarcar los grandes
puntos de la programación, a la hora de ponerte a programar, no tendrás muchas
dificultades complejas o de estructuración.
El documento no empezó a escribirse si no tras un extenso proceso de investigación
y de estructuración para ver qué temas se iban a tratar, de qué forma y analizar lo
más profundamente posible estos. Los ejemplos de código fueron un gran acierto
pues me ayudaron incluso a mí mismo a entender ciertos puntos expuestos.
Decir que con este análisis he llegado a la conclusión que esperaba llegar desde que
se me ocurrió este proyecto. ¿Estaría bien que cualquier desarrollador rompa un
poco su monotonía y se abarque en un análisis de los nuevos lenguajes que llegan
para así intentar mejorar su producto final? Está claro que sí, pero con estos dos
lenguajes pienso que se acentúa aún más, que es donde quería llegar. Kotlin
actualmente es un portento en el desarrollo nativo por los motivos ya expuestos,
pero, sobre todo, como ya hemos dicho unas cuantas veces a lo largo del documento,
lo es por no hacer de su competidor un enemigo, si no un aliado en el que apoyarse
para crecer. La interoperabilidad es la clave de Kotlin y chapó para JetBrains.
79
intentan aprovechar al máximo sus librerías y acercarse al máximo también a lo
nativo, por experiencia en ambos lados debo decir que cuando haces uso de librerías
nativas, estas librerías híbridas no te ofrecen muchas veces todo lo que querrías.
Estas librerías, además, no las suelen hacer la propia empresa, si no desarrolladores
de la comunidad que se vuelcan en el código libre y compartido.
Al fin y al cabo, si tu aplicación depende de acceso nativo en gran parte, te acabarás
viendo en la necesidad de realizar desarrollos nativos diferenciados. Así que el
poder de la programación nativa, evidentemente, sigue ahí.
80
12
Referencias
81
Referencias: sitios web
82
14. Korolev, Sergey. MoSCoW Method: How to Make the Best of Prioritization.
Railsware. [Online] 6 Marzo 2019. [Cited: 11 Noviembre 2019.]
https://railsware.com/blog/moscow-prioritization/.
15. Junta de Andalucía. Atributos de los requisitos. Marco de Desarrollo de la Junta
de Andalucía. [En línea] 12 de Abril de 2011. [Citado el: 11 de Noviembre de 2019.]
http://www.juntadeandalucia.es/servicios/madeja/contenido/recurso/409.
16. StrongLoop by IBM. Making authenticated requests. Loopback IO. [Online] 29
Noviembre 2016. [Cited: 13 Noviembre 2019.]
https://loopback.io/doc/en/lb3/Making-authenticated-requests.html.
17. Nodejs. Acerca de Node.js. Nodejs. [En línea] 23 de Julio de 2016. [Citado el: 13
de Noviembre de 2019.] https://nodejs.org/es/about/.
83
Apéndice A
Manual de
Instalación
Requerimientos
Para la aplicación móvil, hará falta tener Android Studio instalado. Dentro de él,
abrir el proyecto llamado sales-app, que es nuestro proyecto Android. Compilamos
dentro del entorno de desarrollo y ejecutamos teniendo un dispositivo Android
conectado previamente, con acceso a transferencia de archivos y con la depuración
USB activada. Hecho esto, al ejecutar, la aplicación se ejecutará en el dispositivo
Android. En su defecto, se puede utilizar un emulador instalado mediante Android
Studio previamente.
Un dato importante es que tanto el ordenador donde se ejecuta el servidor como el
dispositivo móvil donde se ejecuta la aplicación móvil deben estar conectados a la
misma red. Además, se deberá conocer la IP de nuestro PC en esa red accediendo al
85
comando “ipconfig” en la terminal del ordenador y esa IP debe ser cambiada en el
código de las 3 llamadas que se hacen a la API dentro de la aplicación móvil. Todas
estas al principio de cada archivo y se encuentran en las actividades: MainActivity.kt,
SalesActivity.kt y SaleActivity.kt. SOLO hay que cambiar la IP, no el puerto.
86
87