Mantenimiento y desarrollo móvil: APP de revelado de fotos para

Anuncio
Ennio Casas Puglielli
Mantenimiento y desarrollo móvil: APP de revelado
de fotos para Android
TRABAJO DE FIN DE GRADO
Dirigido por el Dr. Jordi Duch Gavaldà
Grado en Ingeniería Informática
Tarragona
2014
2
ÍNDICE
1
Índice
1
Índice ................................................................................................................... 3
2
Objetivos ............................................................................................................. 5
3
Especificaciones .................................................................................................. 6
3.1 Primera Parte ................................................................................................... 6
3.2 Segunda Parte .................................................................................................. 7
3.2.1
Fase de Selección de Producto. ................................................................ 7
3.2.2
Fase de Producto Postal............................................................................ 7
3.2.3
Fase de Selección de Foto. ....................................................................... 7
3.2.4
Fase de Crop de la Postal. ........................................................................ 8
3.2.5
Fase de Personalización de la Postal. ....................................................... 8
3.2.6
Fase de Datos del Usuario. ....................................................................... 8
3.2.7
Fase de Resumen de Compra. .................................................................. 9
3.2.8
Fase de Pago. ............................................................................................ 9
3.2.9
Fase de Subida. ......................................................................................... 9
3.2.10 Fase de Resumen de Compra y Número de Pedido. ............................... 9
4
Diseño................................................................................................................ 10
4.1 Primera Parte ................................................................................................. 10
4.2 Segunda Parte ................................................................................................ 11
5
4.2.1
Fragment de Selección de Producto ....................................................... 13
4.2.2
Fragments de Selección de Álbum y Foto ............................................. 14
4.2.3
Fragment de Crop de la Foto .................................................................. 14
4.2.4
Fragment de la Personalización de la Postal .......................................... 15
4.2.5
Fragment de Datos del Usuario .............................................................. 16
4.2.6
Resumen de Compra, Subida de Imagen y Número de Pedido ............. 16
Desarrollo .......................................................................................................... 17
5.1 Primera Parte ................................................................................................. 17
5.2 Segunda Parte ................................................................................................ 20
6
5.2.1
Fragment de Selección de Producto ....................................................... 21
5.2.2
Fragment de Selección de Álbum y Foto ............................................... 22
5.2.3
Fragment del Crop de la Foto ................................................................. 22
5.2.4
Fragment de la Personalización de la Postal .......................................... 23
5.2.5
Fragment de Datos del Usuario .............................................................. 28
5.2.6
Resumen de Compra, Subida de Imagen y Número de Pedido ............. 29
Evaluación ......................................................................................................... 31
3
ÍNDICE
7
Conclusiones ..................................................................................................... 45
8
Recursos Utilizados ........................................................................................... 46
4
OBJETIVOS
2
Objetivos
El proyecto “fotoprintUltimate” cuyo nombre he modificado a petición del cliente,
llegó a mí para que realizase un mantenimiento exhaustivo debido al mal funcionamiento
de la aplicación. Me interesó mucho el proyecto desde un principio ya que se me planteó
como un reto, debido a mis pocos conocimientos de Android y a una aplicación bastante
grande, ya en producción y con problemas muy graves. Sería una gran oportunidad para
mí: obtendría más habilidad a la hora de programar en Android, ganaría conocimientos a la
hora de llevar cualquier tipo de proyecto y crecería más a nivel profesional y a nivel de
comunicación con un cliente.
Esta aplicación trabaja el tratado de imágenes, utiliza una libraría externa para
realizar recortes en las fotografías del dispositivo, permite elegir una cantidad de fotos
determinada y continuar un proceso de obtención de datos personales para poder realizar el
envío y finalmente el pago de este mismo.
Para empezar me gustaría destacar que hay dos objetivos principales en este
proyecto. El primero de ellos era el mantenimiento urgente, ya que la aplicación estaba en
producción y su funcionamiento no era el adecuado. El segundo, y no por eso menos
importante, era la implementación de una nueva funcionalidad dentro de la aplicación, el
diseño y el envío de postales.
Dentro del primer objetivo es donde pretendo desarrollar mis habilidades de análisis
y refactoring, además de agregar un nuevo método de pago dentro de la aplicación, lo cual
me ayudará a adaptar rápidamente librerías externas con su correspondiente API a un
proyecto, además de mejorar mis conocimientos de Android.
Finalmente este primer objetivo se resume en análisis, mantenimiento y pasarelas de
pago.
En el segundo de los objetivos principales veo de nuevo una oportunidad para
mejorar el análisis, ya que debo coger una aplicación cuya base no he diseñado ni
implementado e intentar agregar un módulo nuevo que funcione a la perfección con el
resto de la aplicación (que, a partir de este momento, simplificaremos como APP).
Gracias a esta parte desarrollaré mi habilidad para analizar y determinar el tiempo
necesario para realizar dicha implementación, así como el trato con el cliente para aclarar
peticiones y especificaciones, y la comunicación cliente (Android) – Servidor (Web
Service).
5
ESPECIFICACIONES
3
Especificaciones
3.1
Primera Parte
Esta APP fue un encargo de un profesional de la fotografía. Su funcionamiento
básico era el siguiente:
1. Splash de la APP.
2. Selección de álbum.
3. Selección de imágenes a editar y/o subir y comprar.
4. Pantalla de edición de imágenes.
a. Editar imagen (opcional).
5. Pantalla de datos de envió.
6. Resumen de la compra y acceso a métodos de pago.
7. Ventana de Pago (PayPal).
8. Fase de subida de fotos.
9. Resumen de la compra con el número del pedido.
Esta APP tenía otro programador trabajando en ella, pero por motivos personales no
pudo continuar y quedo completamente fuera del proyecto. En ese momento la APP estaba
en producción en la tienda de GooglePlay. No obstante tenía ciertos errores en los
apartados de 3 y 4.
El cliente me pedía agregar una nueva pasarela de pago llamada Paymill junto con
PayPal para realizar pagos con tarjeta de crédito directamente desde el propio móvil, sin
ningún webView como funciona PayPal.
Entonces tenemos que la primera parte de las especificaciones resultan ser, en primer
lugar, la comprensión de todo el código fuente para arreglar los errores en dichas fases y
luego la implementación del método de pago nuevo. Esta APP funciona con un total de
cuatro librerías externas, pero la más importante era la encargada del crop y el tratado de
imagen, ya que toda la lógica de la APP estaba montada encima de esta.
La primera especificación proveniente del cliente es el mantenimiento de la fase 3,
en dicha fase las imágenes escogidas, al desmarcarse hacían crashear la APP. El análisis de
este problema me dejo ver que detrás habían más problemas con la selección de imágenes,
así como problemas a la hora de navegar por la APP, por ejemplo, “elegir, volver atrás,
desmarcar, volver a marcar” y otros tipos de combinaciones que hacían que la APP dejase
de funcionar, o simplemente multiplicaba las imágenes escogidas.
6
ESPECIFICACIONES
La segunda de las especificaciones del cliente es el mantenimiento de la fase 4 y 4-a.
En esta fase se ven solamente las imágenes seleccionadas, pero el problema, propiamente,
era que al editar una imagen y volver a la fase 4, no se veía el cambio efectuado en la
miniatura. También al jugar con el scroll de dicha fase, las imágenes se desordenaban por
completo, pero solo de manera visual, lo cual hacia que al clicar alguna para editar te
llevase a la edición de una foto incorrecta.
La última de las especificaciones del cliente para esta primera parte fue la
implementación del método de pago de Paymill, que incluye el estudio de la SDK y poner
las pantallas de una manera muy específica, teniendo en cuenta los posibles errores que
podrían ocurrir a la hora de realizar un pago. Todo esto se debe realizar con llaves públicas
en modo de TEST.
3.2
Segunda Parte
Para esta segunda parte, las especificaciones del cliente fueron más exactas y
completas, ya que se trataba de toda una funcionalidad nueva en paralelo a la ya existente.
Estas especificaciones se dividen en diferentes ventanas.
3.2.1 Fase de Selección de Producto.
En esta fase, que va justo después del home, debe elegirse que producto se desea
comprar, si el clásico de las fotos o el nuevo producto.
El diseño debe tener 3 imágenes desplazables con el táctil, un indicador de
página para saber qué foto se ve, y dos botones con una pequeña descripción para los
productos.
Tanto las imágenes como los botones deben llevar a los respectivos productos.
3.2.2 Fase de Producto Postal.
El otro botón continuaría con la APP original. Aquí tenemos una elección de
álbum que debe ser idéntica a la del otro producto añadido.
3.2.3 Fase de Selección de Foto.
Aquí se especificará qué foto será la elegida para la postal. Solo puede escogerse
una y deben saltar 2 tipos de alertas diferentes: una alerta para avisar al usuario que
solo puede elegir una foto que debe saltar si el usuario pulsa otra imagen y otra alerta
para advertir sobre la calidad de la imagen, ya que con unas especificaciones mínimas
7
ESPECIFICACIONES
de tamaño, el cliente quiere asegurarse que el usuario está advertido sobre la posible
baja calidad de la foto en su posterior recorte.
3.2.4 Fase de Crop de la Postal.
Después de elegir una imagen llegaremos a esta ventana donde tendremos un
área de cropping para que la imagen escogida se ajuste a los gustos del usuario.
Además de esto el usuario debe ser capaz de rotar 90º la imagen las veces que quiera.
En esta fase saltará una alerta si el área final del crop no cumple los requisitos
mínimos para mantener al usuario informado de que su foto puede salir pixelada o muy
pixelada.
3.2.5 Fase de Personalización de la Postal.
Esta fase debe tener 5 elementos básicos:
-
Una fecha que aparezca ya completada pero que el usuario sea capaz de
editarla y que los slashes (/) se coloquen automáticamente, además de
comprobar que se trata de una fecha posible y real.
-
Un campo para colocar a quien va dirigida la Postal.
-
Un campo con el mensaje de la postal (el cual será el único obligatorio).
-
Un campo para la firma de la persona.
-
Un campo que debe estar rellenado como la fecha con la ubicación actual del
usuario (por ejemplo, “España, Tarragona”), a parte el usuario debe ser capaz
de poner lo que desee.
3.2.6 Fase de Datos del Usuario.
La fase de los datos será exactamente igual que la del otro producto, no obstante,
se ha agregara la opción de entrega a diferentes países, ya que el deseo del cliente es
realizar pagos internacionales. Con todo esto tendremos una lógica nueva para los
precios que se ve en la siguiente fórmula (1).
PrecioTotal = precioBase + precioPaís* + precioProvincia* + precioCP*
(1)
Para las postales se seguirá exactamente la misma lógica pero con el precio base
de una postal.
*
Opcional si no existe
8
ESPECIFICACIONES
Los países válidos para los pedidos serán dados por WS, el campo nuevo de país
debe ser auto-completable al ir escribiendo el país, no se debe dejar escribir ningún
país que no esté en la lista, ya que el campo es obligatorio.
3.2.7 Fase de Resumen de Compra.
Debe ser idéntica a la del otro producto, sin ningún cambio.
3.2.8 Fase de Pago.
Independientemente del método de pago escogido, será igual en ambos
productos.
3.2.9 Fase de Subida.
Igual que la del otro producto.
3.2.10 Fase de Resumen de Compra y Número de Pedido.
Sin ningún cambio, igual al otro producto.
Finalmente se pide la implementación del mismo sistema de alertas para el otro
producto y una lógica que revise la integridad de los archivos subidos al FTP para ver si
alguno no está y re-subirlo por si ocurre algún problema en la red.
Para concluir con las especificaciones, el cliente pide que nueva información, como
la versión de Android del dispositivo, lenguaje, versión de la APP, país, y tipo de pedido,
se guarde en la base de datos al realizar el putOrder del pedido.
9
DISEÑO
4
Diseño
4.1
Primera Parte
Para la primera fase del proyecto no hay mucho diseño que comentar, tuve que hacer
un análisis de todo el código, revisar los fallos e intentar encontrar dónde y porqué fallaba.
A continuación haré una breve explicación de cómo pensé en realizar esta parte.
Antes que nada debía analizar la estructura del proyecto, y ver así, cómo poder
organizarme más adelante para futuros problemas. Con este análisis pude ver que el
proyecto se estructuraba de la manera que se explica a continuación.
Para empezar, era un proyecto de Android basado en fragments. Los fragments son
una nueva clase de Android que nos permite trabajar el contenido de las activities de una
manera mucho más ordenada. De esta manera puede llegar a realizarse una APP en la que
solo existan fragments que nos ayuden a navegar por nuestra APP mientras que solo
tenemos una activity principal. Debemos tener cuidado a la hora de trabajar con fragments,
ya que ellos disponen de su propio ciclo de vida independiente, al igual que una activity.
A parte de esto, tenemos apartados dedicados únicamente a la conexión, en este caso
este proyecto se organizaba de una forma muy concreta, normalmente no se enviaban
objetos muy grandes por WS, solo el de la Orden, que contiene todos los datos del pedido.
El resto de ocasiones solo se enviaban peticiones de datos concretos, como códigos
promocionales, países disponibles…
El caso es que para poder realizar una consulta a un WS, la estructura de la APP era
de cuatro ficheros distintos, cosa que no vi muy ordenada ni muy cómoda, pero debí
adaptarme y tenía un tiempo límite, por tanto mejor dejarlo como estaba. Un fichero era el
dedicado a hacer la operación de la consulta, otro se dedicaba a tratar la respuesta, otro
lanzaba la Request y el ultimo común entre todos, que era donde se guardaban y lanzaban
las peticiones.
Todo esto se trataba con JSON. Yo personalmente aprendí a crear un sistema de
comunicación basado en JSON sin necesidad de tantos ficheros, solo una interficie y la
llamada asíncrona dentro del fragment que necesita la consulta. Gracias a esto y mediante
Callbacks, podemos operar con normalidad. Necesitamos callbacks porque las llamadas
asíncronas obviamente se realizan en otro hilo, por tanto no tenemos acceso al contenido
del hilo principal. Si esperamos un callback de la llamada asíncrona dentro del hilo
10
DISEÑO
principal tendremos acceso a los datos obtenidos y a la posibilidad de tocar la view del
dispositivo.
Volviendo al tema de la estructura, disponía también de un apartado solo dedicado a
los fragments, otro para las utilidades y un último para los modelos que podría haber en la
APP.
Después de estudiar en profundidad el tema de la estructura, mi parte de diseño
dentro de esta primera fase fue el sistema de pago nuevo, debía integrar la SDK de Paymill
dentro de la APP. Por suerte para mí, la SDK no era muy compleja de montar, no obstante
requería ciertos cambios a la hora de tratar el resto de fragments y el precio.
Para empezar la SDK de Paymill lo que hace es crear una instancia de una activity,
por tanto debía intentar integrar una activity que llama a diversos fragments de pago, uno
por cada tipo de tarjeta o entidad dentro de la APP.
Mi decisión final fue que intentaría integrar un botón que lanzase la llamada al nuevo
Intent con los datos de inicialización de Paymill y el precio (más otros datos internos a
petición del cliente) dentro del fragment de resumen de pago. Con esto podría hacer las
modificaciones necesarias en el precio, ya que Paymill solo acepta enteros, por ejemplo,
1,67 euros son 167. Por ello debía tratar los precios con cuidado de no cambiar el precio
real, ni arrastrar ningún tipo de decimal que pudiese causar problemas al usuario a la hora
de pagar.
Teniendo todo esto en cuenta no debería tener problemas para realizar la
implementación de esta fase.
4.2
Segunda Parte
Para la segunda fase, donde se pide una implementación completa de una
funcionalidad nueva, hay un poco más que comentar con lo que al diseño se refiere, tuve
que hacer un análisis de las peticiones del cliente y ver si sería capaz de realizarlas sin
mucha dificultad.
Mi decisión fue que toda la nueva implementación la haría en fragments, ya que
aparte de aprender a tratar con ellos, se integraría mucho mejor en una APP basada en
ellos. Entonces, a partir de ahora me referiré a las ventanas de la funcionalidad como
fragments.
11
DISEÑO
Figura 1. Mockup de la APP
12
DISEÑO
4.2.1 Fragment de Selección de Producto
Figura 2. Diseño del cliente para ventana de selección
El primero de los fragments sería uno intermedio que sería el encargado de la
selección de producto, estaría entre el Home y los productos. Me gustaría añadir que el
diseño de toda la APP es muy concreto y muy exigente debido al cliente. Tenemos que en
este primer fragment la selección de productos requeriría de cuatro elementos en total, una
pareja de botones y una pareja de viewPagers, un elemento de Android para cargar imagen
o texto a modo de página. Los botones, aparte de su funcionalidad obvia, informarían sobre
el producto y el precio del mismo, mientras que los viewPagers mostrarían seis imágenes,
tres para cada producto.
Además de esto, ambos viewPagers deben tener un tipo de indicador en forma de
pelota, para saber en qué imagen está ahora mismo el usuario. Adicionalmente si se clica
un viewPager debe llevar a su producto correspondiente (VP superior a Producto uno y VP
inferior a Producto dos).
13
DISEÑO
Una vez aclarado esto, el diseño de este fragment era más visual que lógico, ya que
simplemente se trataba de simples transiciones.
Tomé una serie de decisiones en este diseño: que necesitaría una librería externa para
algún tipo de indicador en los viewPagers y que dependiendo del tamaño del dispositivo
me daría problemas a la hora de la visualización, por tanto todo ese fragment estaba basado
en un tipo de Layout relativo. De esta manera todo está colocado con una referencia y se
modificará según la resolución del dispositivo.
4.2.2 Fragments de Selección de Álbum y Foto
Los fragments de elección de álbum y foto no deberían suponer un problema ya que
son idénticos a los fragments del otro producto. Por ello tomé otra decisión de diseño: no
crearía dos fragments adicionales, si no que reaprovecharía ambos con una simple
distinción entre producto 1 y 2. Para el producto 2 solo podría escoger una foto, mientras
que el 1 son hasta un máximo determinado por el cliente.
4.2.3 Fragment de Crop de la Foto
Figura 3. Diseño del cliente de la ventana de recortes
14
DISEÑO
El siguiente fragment requeriría un poco más de diseño, ya que se trataba de uno
nuevo, el cual era el recorte de la foto seleccionada para su uso como postal. Decidí seguir
utilizando la librería del crop que disponían antes de mi llegada, ya que toda la APP estaba
basada en ella y su cambio significaría un aumento de tiempo elevado, sería como volver a
empezar la APP.
Desde en principio pensé que su implementación sería un problema, no obstante su
diseño es simple. Tenemos la foto seleccionada dentro de un área de recorte donde
podremos poner el tamaño deseado, cumpliendo unos márgenes. A su vez el usuario puede
decidir si rotar la imagen 90º las veces que desee. Hubo muchos cambios en esta ventana a
medida que la diseñaba, por ello su diseño no es definitivo hasta que hable de ella en la
implementación.
4.2.4 Fragment de la Personalización de la Postal
Figura 4. Diseño del cliente para ventana de personalización de postal
15
DISEÑO
El fragment que le procede es el de la personalización de la postal, aquí tendremos
un campo de fecha que decidí diseñar e implementar yo mismo el hecho de hora
automática y barras (/) automáticas. El resto son campos normales, campo para
destinatario, mensaje y remitente, salvo el campo de la ubicación. Este campo decidí
implementar una función mediante la longitud, latitud y la ayuda del Geocoder (una clase
de Android que nos da información sobre nuestra ubicación) para determinar el país, la
ciudad y la calle. Este último más tarde será eliminado por diseño del cliente. Finalmente
este fragment no podrá continuar si no hay mensaje escrito.
4.2.5 Fragment de Datos del Usuario
Ahora llegamos al fragment que más cambios tuvo en la parte de diseño. Empezó
siendo idéntico al fragment de datos del producto 1, pero con la llegada de los pedidos
internacionales este fragment recibiría cambios importantes en el diseño.
Tendría un nuevo campo para el país, de esta manera necesitaba crear una lógica
nueva para que al consultar los WS con el ID del país, recibir si había algún plus de dinero
para añadirlo más adelante al pago. No solo por país sino también por provincia y CP. A
parte que requeriría también un nuevo WS para los códigos promocionales de las postales.
Dependiendo del retorno del código promocional debía aplicarse un descuento o por
porcentaje o por una simple resta.
4.2.6 Resumen de Compra, Subida de Imagen y Número de Pedido
Con esto volvemos a los últimos fragments que son idénticos a los del producto 1,
por ello decidí de nuevo reaprovecharlos utilizando los mismos pero haciendo casos
separados para un producto o el otro.
La lógica de los últimos pasos consiste en la recompresión del archivo, el cambio de
nombre, la subida al FTP y la vuelta del número de pedido. Dentro de la lógica de estos
pasos descubrí un fallo muy grave a la hora de recomprimir el archivo. Resulta que si el
archivo había sido editado se comprimía dos veces, por tanto tenía una pérdida de calidad
importante. Por ello decidí cambiar un poco el diseño de la Orden de la APP para que saber
si un producto había sido editado o no, así poder decidir si se comprime o no.
A parte de esto también decidí cambiar ciertas cosas en el diseño de la subida de
archivos al FTP, propuse la consulta de datos totales después de la subida para así saber si,
comparando locales con remotos, estaba todo en orden. En caso contrario se resubirían los
archivos.
16
DESARROLLO
5
Desarrollo
El primero de los problemas fue montar todo el entorno de trabajo para el proyecto
por tantas librerías externas a depender. Después de varios intentos conseguí montar un
entorno de trabajo estable, de momento, para el resto de mi desarrollo para la APP.
Montar el entorno en un principio era fácil: importar el proyecto principal, importar
el resto de proyectos, con la etiqueta de “librería” marcada, y finalmente realizar la
dependencia entre ellos.
Una vez conseguido esto podemos dar paso al desarrollo de la primera parte.
5.1
Primera Parte
Tras realizar muchos test en proyectos separados, llegué a una versión que
funcionaba de Paymill tal y como la quería el cliente. A continuación explicaré la
implementación de Paymill.
El primero de los pasos era claro, registrarse en Paymill para futuras pruebas. Una
vez hecho esto y descargada la SDK pude ponerme manos a la obra.
Otro paso importante antes de tocar código era la instalación de un MobileAPP
dentro de la propia cuenta de Paymill, ya que sin este la APP no aceptaba los pagos.
Su funcionamiento es similar al resto de plataformas de pago, el usuario al realizar
una transacción, esta, genera un token que sirve para validar la operación. Una vez hecho
esto, y controlando los errores que podría devolver, el resto del pago lo llevaba a cabo el
resto del SDK.
Implementar esto en Android equivaldría primero a crear un servicio dentro de
nuestro AndroidManifest.xml, que debería ser similar a este:
<!-- paymillsdk service -->
<service Android:name="com.paymill.Android.service.PMService"
Android:enabled="true" Android:exported="false">
</service>
Código 1. Inicialización de servicio de Paymill
17
DESARROLLO
Para poder debugear esta SDK no nos basta con los simples Logs conocidos de
Android, debemos crear una serie de Listeners dependiendo para qué tipo de operación, e
imprimir las respuestas por allí.
Primero de todo debemos inicializar el servicio de Paymill que acabamos de declarar.
PMManager.init(getApplicationContext(),PMService.ServiceMode.TEST,
"yourpublickey",null, null);
Código 2. Inicialización del servicio por código
La llave pública es una especie de código de letras y números que nos dan al
crearnos la cuenta. Existen dos tipos de parejas de llaves, la pareja pública y la pareja
privada. Para la privada se quiere rellenar un formulario muy estricto y cumplir ciertas
condiciones de Paymill. Yo monté toda la APP con las llaves públicas, a la hora de la
verdad el cliente creó sus llaves privadas y yo las substituí.
Después de esto había dos pasos importantes a seguir, el primero era la declaración
del “method”, que simplemente es una declaración del método de pago a utilizar. El que yo
utilizaba para testear era este:
PMPaymentMethod
method
=
PMFactory.genCardPayment("Max
Mustermann", "4111111111111111", "12", "2015", "1234");
Código 3. Declaración del method
Donde el primer parámetro es el nombre del titular de la tarjeta, el segundo es el
número de la tarjeta, el tercero y el cuarto son la fecha de caducidad y el quinto es el
número de detrás de la tarjeta.
La segunda declaración que necesitamos es el “params” donde se especifica en qué
moneda se pagará, cuanta cantidad y la descripción del pago.
PMPaymentParams
params
=
PMFactory.genPaymentParams("EUR",
100,
null);
Código 4. Declaración dels params
Recordemos que Paymill recibe el precio en número entero, es decir, ese 100 en
realidad significa 1 euro.
Después de esto, el último paso es simple. Crear la transacción y esperar la respuesta.
18
DESARROLLO
Tendríamos algo similar a esto, pero lanzándolo en un Intent.
PMManager.transaction(getApplicationContext(),
method,
params,
false);
Código 5. Inicio de transacción
En este punto es donde automáticamente el servicio de Paymill lanza la petición y un
Intent esperando el resultado. Por ello esperaremos en un onActivityResult.
public void onActivityResult(intrequestCode, intresultCode, Intent
data) {
PMActivity.Result
result
=
PMActivity.Factory.getResultFrom(
requestCode, resultCode, data);
if (result == null) {
// Algo muy malo ha pasado.
return;
} else {
if (result.isCanceled()) {
// ventana de pagocancelada.
Log.d("PM", "Payment screen was canceled");
} else if (result.isError()) {
// error de Paymill: ver codigo de error.
Log.d("PM", "Error:" + result.getError().toString());
} else {
// Todo ha salido como se esperaba.
Log.d("PM", "Token:" + result.getResultToken());
}
}
}
Código 6. OnActivityResult
En este punto simplemente me dediqué a sacar mensajes de error, o Dialogs,
dependiendo de cada error y a pasar a la siguiente ventana si todo era correcto.
19
DESARROLLO
De esta manera, y tras muchos intentos fallidos, fui capaz de crear la nueva pasarela
de testeo de pago dentro de la APP de mi cliente para su posterior uso en producción.
Figura 5. Ventana de pago por Paymill una vez finalizada
5.2
Segunda Parte
Esta era, sin duda, la parte más delicada ya que se trataba de la implementación de
una funcionalidad nueva. Dentro de esta misma funcionalidad fueron surgiendo muchos
cambios por parte del cliente a lo largo del desarrollo, que hicieron que seguir el diseño
fuese bastante más complicado. No puedo concretar mucho a nivel de código a petición del
cliente, pero a continuación explicaré como fue el desarrollo de esta nueva funcionalidad.
Antes que nada debía intentar ajustar la estructura interna del programa para que se
ajustase a las nuevas peticiones del cliente, esta APP aparte de ahora tener la nueva
funcionalidad de las postales, también aceptaría compras desde el extranjero y envíos al
extranjero. Los WS fueron rediseñados por el cliente para estas nuevas necesidades por
tanto tuve que coger los Request a estos WS dentro de la APP y modificarlos para enviar
y/o recibir estos nuevos datos. Esto no fue muy complicado, a pesar de estar diseñado de
una manera, a mi parecer, muy caótica simplemente tuve que modificar las clases que se
enviaban por medio de JSON.
El primero de los Request a los WS que tuve que modificar fue el comprobaba que el
código de promoción escrito por el usuario era correcto, y además aplicar el descuento
20
DESARROLLO
correspondiente. Más adelante, descubrí que tal y como estaba montado me daría
problemas a la hora de aplicar descuentos sobre precios una vez calculado el total. Es
decir, descubrí que como en la versión anterior, los otros programadores no se preocuparon
en dónde aplicar el descuento, esto hace que si resulta que tengo modificaciones de precio
según la provincia, por ejemplo, no se aplicaba bien. Fue sencillo arreglar el problema y no
hizo falta ni advertir al cliente.
Otro de los Request a modificar fue el que envía todos los datos del pedido “Orden”
en nuestro caso. Su modificación fue mucho más sencilla que ninguna otra de los Request.
La tarea fue completada simplemente añadiendo los nuevos parámetros a la llamada.
A parte de modificaciones, también tuve que crear otro tipo de llamadas para los WS
nuevos, como el de consultar la lista de países disponibles, así como el nuevo de obtener
provincia/ciudad mediante el código postal.
Con las llamadas a los WS corregidas y funcionando, pude continuar con el resto del
desarrollo.
Ahora hablaremos sobre todo el desarrollo de los fragments.
5.2.1 Fragment de Selección de Producto
El desarrollo de este fragment fue simple en cuanto a su funcionamiento lógico, el
dolor de cabeza vino al momento de hacerlo “bonito” o fiel al diseño, ya que el cliente
siempre intenta que la APP sea bonita a la vista. Por ello los botones que llevan al resto de
fragments fueron tan simples como cualquier botón de Android, para estos casos concretos
los programadores anteriores tenían unas funciones encargadas de la administración de los
fragments, por ello tuve que llamar a estas funciones con los nombres de los fragments
nuevos.
Como decía la parte compleja fue que se adaptase al diseño, entonces para las
imágenes que se pasaban con el dedo simplemente utilice unos viewPagers de Android,
cargando las imágenes que necesitaba, mientras que para los indicadores fui más allá y
utilicé una librería externa, Android-viewPagersIndicator, cuya implementación resulto ser
bastante sencilla, incluso al tener dos en el mismo layout.
Simplemente debía importar la clase y ponerla justo debajo de la declaración del
viewPager, luego, en el código, instanciar la clase del indicador que nos interese, en
nuestro caso son los circulares.
21
DESARROLLO
Después de esto el cliente estuvo muy contento con el resultado final. Aún y así hubo
algunos cambios menores en el layout, como la separación de las letras, la separación de
los viewPagers y el tamaño de las fuentes.
5.2.2 Fragment de Selección de Álbum y Foto
Cómo comenté en el diseño, decidí reaprovechar las ya existentes, simplemente
añadí condiciones para los casos del producto 1 y para el producto 2. A parte de eso,
implementé una serie de mensajes de alerta si el usuario elegía una foto cuya calidad no
cumpliese los lindares propuestos por el cliente. Si el caso era de tipo producto 1, podían
elegirse hasta un máximo de 47 fotos, y a cada foto que se seleccionaba se advertía si se
cumplían o no dichos lindares. Para la nueva parte, las postales, cómo solo se podía elegir
una lo que hice fue permitir la elección de una, saltar error si se coge más de una, y mostrar
alerta de calidad si no se cumplen las especificaciones de calidad.
5.2.3 Fragment del Crop de la Foto
Cómo ya se ha explicado, una vez escogida la foto, esta pasa a ser recortada a las
dimensiones deseadas por el usuario para que entren en una medida de una postal. Ya
existían unas ventanas que realizaban recortes sobre imágenes, como comenté casi toda la
APP estaba montada encima de la lógica de esta librería, por ello intenté replicar ese
código en mi nuevo fragment. Este fragment fue el que más problemas me causo de todos,
ya que el área de crop no se aplicaba correctamente en el layout si intentaba seguir las
especificaciones del cliente.
Resulta que la ventana podría hacerse de una manera muy simple siguiendo una
estructura de layouts relativos, no obstante la librería solo funciona si el layout es de tipo
lineal. Por culpa de esto el cliente tuvo que adaptar su diseño a esto, de otra manera no
funcionaría.
El resto del fragment era simple: rotar la imagen las veces deseadas. Esto fue fácil de
conseguir mediante transformaciones de matrices teniendo el bitmap de la foto. Pero esto
implica crear un bitmap cada vez que se desea rotar la imagen, por lo que es menos
eficiente. El código de la rotación sería algo como esto:
22
DESARROLLO
public static Bitmap rotate(Bitmap source, float angle) {
Matrix matrix = new Matrix();
matrix.postRotate(angle);
return Bitmap.createBitmap(source, 0, 0, source.getWidth(),
source.getHeight(), matrix, true);
}
Código 7. Método para rotar un bitmap
Después de cada rotación debía refrescar la View para que se mostrase la foto
correcta rotada y a la hora de realizar una nueva rotación se tratarse el bitmap correcto.
5.2.4 Fragment de la Personalización de la Postal
Este fragment en concreto también fue bastante simple, solo se trata de ciertos
campos de textos que se enviarán en el Request del putOrder para personalizar la postal.
El cliente quería que la fecha se rellenase sola al iniciar la APP y que se pudiese
editar pero sin un DatePicker, quería que se pudiese introducir texto y que las slashes (/) se
pusiese solas. Esto desconozco si hay una manera más simple de hacerlo pero expondré a
continuación cómo lo he hecho.
Calendar c = Calendar.getInstance();
SimpleDateFormat df = new SimpleDateFormat("dd/MM/yyyy");
String formattedDate = df.format(c.getTime());
postcard_date = (EditText) view.findViewById(R.id.postcard_date);
postcard_date.addTextChangedListener(mDateEntryWatcher);
Código 8. Dar formato a la fecha
Para empezar me declaré una instancia del Calendar de Java con DateFormat para
que la fecha estuviese bien formateada, y finalmente añadí un textChangeListener que a
cada edición del texto realizaría una serie de comprobaciones para que todo funcionase
como el cliente deseaba.
23
DESARROLLO
private TextWatcher mDateEntryWatcher = new TextWatcher() {
@Override
public void onTextChanged(CharSequence s, int start, int
before,
int count) {
String working = s.toString();
boolean isValid = true;
if (working.length() == 2 && before == 0) {
String enteredDay = working.substring(0);
try {
if (Integer.parseInt(enteredDay) < 1) {
isValid = false;
} else {
working += "/";
postcard_date.setText(working);
postcard_date.setSelection(working.length());
}
} catch (Exception e) {
isValid = false;
}
}
if (working.length() == 5 && before == 0) {
String enteredMonth = working.substring(3);
try {
if (Integer.parseInt(enteredMonth) < 1) {
isValid = false;
} else {
24
DESARROLLO
working += "/";
postcard_date.setText(working);
postcard_date.setSelection(working.length());
}
} catch (Exception e) {
isValid = false;
}
} else if (working.length() == 7 && before == 0) {
String enteredYear = working.substring(6);
try {
int
currentYear
=
Calendar.getInstance().get(Calendar.YEAR);
if
(Integer.parseInt(enteredYear)
currentYear) {
isValid = false;
}
} catch (Exception e) {
isValid = false;
}
} else if (working.length() != 7) {
isValid = false;
}
if (!isValid) {
// postcard_date.setError("ERROR");
postcard_date.setError(null);
} else {
postcard_date.setError(null);
}
}
25
<
DESARROLLO
@Override
public void afterTextChanged(Editable s) {
}
@Override
public void beforeTextChanged(CharSequence s, int start,
int count, int after) {
}
};
Código 9. Listener que da formato a la fecha a medida que se escribe
En pocas palabras este Listener simplemente analiza el input cada vez que se añade o
se edita, a base de hacer substrings de 2, 2 y 4 (día, mes y año). Todo este Listener se
encarga de comprobar que, primero, introduzco 2 dígitos, que estos son válidos para ser un
día e introducir un slash; segundo, que una vez llevo un string de 5 puedo partir otro trozo
de 2 y analizar que es un mes valido e introducir slash; y finalmente una vez lleve una
longitud de 7, puedo partir un substring de 4 para comprobar que es un año valido.
Una vez acabada la edición, el texto no se comprueba hasta que no pasas a la
siguiente pantalla, en caso de que se haya introducido una fecha mal, o sin sentido como
por ejemplo 31 de febrero, saldrá un mensaje de error impidiendo el paso del usuario hasta
que una fecha correcta sea colocada. Esto lo compruebo simplemente aprovechando el
simpleDateformat y comparando el string obtenido con el formato y el date.
public boolean validateDate(){
if(postcard_date.getText().toString() == null){
return false;
}
String
enteredyear
postcard_date.getText().toString().substring(6);
if(enteredyear.length()<4){
return false;
}
26
=
DESARROLLO
SimpleDateFormat
df
=
new
SimpleDateFormat("dd/MM/yyyy");
df.setLenient(false);
try {
Date
date
=
df.parse(postcard_date.getText().toString());
} catch (Exception e) {
return false;
}
return true;
}
Código 10. Comprovación de fecha válida
Después de esto, el último de los campos era un campo que permitía la inserción de
texto o si se quería se debía presionar un botón para el autorellenado del campo con la
provincia y el país que cogía el GPS.
Al arrancar el fragment el cliente quería que este campo ya estuviese rellenado por
defecto. Toda esta lógica la diseñé basándome en la clase Geocoder de Android, que con
una latitud y una longitud era capaz de devolver ciertos parámetros que eran de interés para
este aparatado.
No colocaré todo el código porque es algo pesado a la lectura, pero su explicación es
simple.
Básicamente se necesita un location Listener para poder obtener la localización del
dispositivo, con esta, y comprobando ciertos requerimientos como tener Internet y GPS
habilitados podemos obtener la latitud y longitud actuales.
Con esto podemos utilizar el Geocoder para obtener los datos que necesitamos.
27
DESARROLLO
Geocoder geocoder;
List<Address> addresses;
Log.d("POSIT",MyLat+","+MyLong);
geocoder = new Geocoder(getActivity(), Locale.getDefault());
addresses = geocoder.getFromLocation(MyLat, MyLong, 1);
CityName = addresses.get(0).getLocality();
CountryName = addresses.get(0).getCountryName();
Código 11. Geolocalización del dispositivo
En este caso concreto se puede observar como cojo solamente le localidad y el país,
no obstante con el Geocoder se puede profundizar un poco más pidiendo tanto calle como
número exacto, si existe.
Para concluir con este fragment solo había que comprobar que el campo del mensaje
no estuviese vacío para poder continuar.
5.2.5 Fragment de Datos del Usuario
Este fragment no es más que el formulario antiguo pero con la actualización de la
geolocalización en el campo del país, el autocompletado de este campo consultando la lista
de países si se quiere buscar otro y arreglando los campos para los países sin código postal
y/o sin provincia.
El problema reside en el que el cliente quiere que el campo de provincia y población,
aparte de ser obligatorios, deban ser no editables por el usuario. Estos deben rellenarse de
manera automática al introducir el código postal del país correspondiente. De esto se
encargan los WS de los que hablé al principio. El caso es que si se trata de un país sin CP o
sin provincia, estos campos deben dejar de ser obligatorios automáticamente y convertirse
en editables por si se desea añadir una referencia a la dirección de entrega. Y que además
los campos siempre fueran clicables.
El tema de la lista de países, y la consulta de estos por id para obtener los montos a
añadir al precio lo arreglé con una tabla de hash que rellenaba al acabar la consulta de
países, en esta tabla guardaba el identificador del país como llave y un objeto de tipo Pais
en el valor, así tenía acceso a casi todo el contenido que necesitase. La creación de diversas
tabas de hash con contenido rápido como precio o nombre del país por id o al revés me
fueron necesarias debido a las consultas rápidas al cambiar de país, ya que siempre son
Strings, necesitaba otra referencia para saber a qué equivalían. También aprovechaba a
28
DESARROLLO
rellenar un array con los nombres de los países para el que el campo del autocompletado de
países funcionase.
Pondré como pude realizar la parte que me pareció más compleja, el autocompletado
del campo país.
Primero debemos declarar la instancia del autocomplete:
private AutoCompleteTextView autoCountry;
autoCountry = (AutoCompleteTextView) view
.findViewById(R.id.form_country_edit);
Código 12. Inicialización del campo autocompletable
Luego debemos declarar qué adapter utiliza, que en nuestro caso simplemente se
trata de un ArrayAdapter de Strings, y a partir de qué cantidad de caracteres se empieza a
buscar.
autoCountry.setAdapter(adapter);
autoCountry.setThreshold(1);
Código 13. Asignación el adapter al campo autocompletable
Finalmente dentro del desarrollo de este fragment se me pidió adicionalmente del
diseño que al introducir un país que no saliese en la lista, no dejase al usuario cambiar de
focus hasta colocar un país valido. Además de establecer esta misma funcionalidad en el
campo del código postal, así como borrar automáticamente todos los campos de CP,
provincia y localidad si se cambia el país.
5.2.6 Resumen de Compra, Subida de Imagen y Número de Pedido
Decidí mezclar aquí los tres fragments porque no tienen mucha explicación,
básicamente no tuve que implementar casi nada ya que la lógica estaba hecha en la versión
anterior y después de testearla parecía funcionar correctamente. En el resumen de compra
tuve que preocuparme por que el precio apareciera correctamente, con o sin descuento, y
que si el importe era igual o inferior a 0, debía dar paso directamente a la ventana de
subida de imagen, sin pasar por ninguna pasarela de compra.
El proceso de subida crea la carpeta mediante FTP en el servidor donde se almacenan
las fotos, la carpeta contendrá el número del pedido y este devolverá el path y el número,
29
DESARROLLO
de esta manera sabe dónde subir las fotos y que referencia debe tener el cliente a la hora de
esperar o consultar su compra.
En este proceso la foto de comprime si no ha sido editada (para no comprimir dos
veces) y se suben las fotos una a una mostrando el progreso con una barra de carga.
Poca implementación puedo seguir comentando por mi parte ya que gran parte de
esto ya estaba implementado, solo tuve que arreglar pequeños detalles de implementación
como fallos en la compresión por culpa de una mala organización a la hora de aplicar el
porcentaje de compresión y el envío de la foto.
30
EVALUACIÓN
6
Evaluación
Para testear esta APP el cliente me proporcionó códigos de promociones para pagar
los artículos a 0 euros, mientras que ellos testearían la parte de Paymill con pagos reales
después de probar todos los pagos en la fase de test.
La única evaluación que puedo poner a continuación es la simulación de un usuario
real, pagando con un código promocional gratuito.
Figura 6. Ventana de inicio de la APP
31
EVALUACIÓN
Figura 7. Ventana de selección de producto
32
EVALUACIÓN
Figura 8. Ventana de selección de álbum
33
EVALUACIÓN
Figura 9. Ventana de selección de foto
34
EVALUACIÓN
Figura 10. Ventana de recorre y rotación
35
EVALUACIÓN
Figura 11. Ejemplo mensaje alerta por resolución
36
EVALUACIÓN
Figura 12. Ventana de personalización de la postal
37
EVALUACIÓN
Figura 13. Ventana de la personalización de la postal con los campos.
38
EVALUACIÓN
Figura 14. Ventana de datos del usuario, combobox de paises
39
EVALUACIÓN
Figura 15. Ventana de datos del cliente, rellenados automaticos de población y provincia
40
EVALUACIÓN
Figura 16. Ventana de resumen del pago
41
EVALUACIÓN
Figura 17. Ventana de resumen del pago con descuento del 100%
42
EVALUACIÓN
Figura 18. Ventana de progreso de la subida
43
EVALUACIÓN
Figura 19. Ventana del final del pedido con el ID del orden
44
CONCLUSIONES
7
Conclusiones
Me gustaría finalizar mi proyecto con unas conclusiones que reflejen mi aprendizaje
durante este mismo, así como los problemas que surgían a mi paso y cómo otras cosas
parecían resolverse sin ninguna dificultad.
Cómo se ha podido observar, el primero de los objetivos fue cumplido con éxito, tras
unas largas semanas fui capaz de arreglar y mejorar la aplicación, aprendí cómo podría
realizar el mismo trabajo pero de manera más eficaz, y a intentar llevar lo mejor posible los
problemas que iba resolviendo. Este objetivo al ser el primero y cogerme con menos
experiencia, fue un golpe muy duro pero útil para mi futuro profesional. Saber reaccionar,
o mejor dicho, aprender a reaccionar según determinados problemas y bajo una presión
muy grande de tiempo considero que es una de las mejores aptitudes que pudo darme esta
primera parte.
Con lo que a la pasarela de pago se refiere, ya se ha visto que no supuso mucho
problema, solamente las peticiones del cliente respecto al SDK y la dificultad que me
supuso explicarle ciertos detalles de la implementación.
Con el fin de esta primera parte llegó el momento de más nervios, subir la APP a la
GooglePlay y esperar un buen feedback del cliente y de los usuarios.
Tras buenas críticas y un cliente muy contento se me planteo la implementación de la
nueva funcionalidad, que como ya hemos visto, también ha resultado ser tanto
extremadamente útil para mi aprendizaje como molesta a la hora de adaptarse a las
constantes modificaciones del cliente. Tras un mes de trabajo y largas charlas con el cliente
finalmente pude conseguir ajustar lo máximo posible la APP a sus intereses. El problema
básico es que esta APP tiene una versión para iOS, y ellos querían una versión idéntica,
cosa bastante imposible. Aún y así el resultado final fue muy positivo para ambos, ellos
obtuvieron su APP tal y como querían, aunque el diseño principal no se parece mucho al
final debido a los cambios repentinos del cliente, y yo conseguí mejorar en gran medida
mis habilidades de programación y análisis.
Para concluir me gustaría añadir que pese a los dolores de cabeza, el estrés y las
discusiones por ambos bandos, no solo fui capaz de terminar la APP como el cliente
deseaba, sino que también he aprendido a superar los problemas que se me planteaban.
45
RECURSOS UTILIZADOS
8
Recursos Utilizados
Para poder realizar este proyecto he necesitado el siguiente hardware y software:
Programación:
-
Eclipse con plug-in de Android.
-
Nexus 4 y galaxytrend para realizar pruebas.
-
Librerias externas, entre ellas: simple cropLibrary, sherlockActionBar, …
Ayuda:
-
http://stackoverflow.com/ (foros de Android)
-
www.paymill.com (apartado de la Mobile SDK)
-
https://github.com/JakeWharton/Android-viewPagersIndicator
(Android-viewPagersIndicator)
Documento del programador anterior que nos dio el cliente:
Directorio de librerías utilizadas en el proyecto:
/libs
Las librerías que será necesario importar como proyecto y posteriormente
referenciarlas al mismo, son:
- DataDroid: https://github.com/foxykeep/datadroid
- PhotoView: https://github.com/chrisbanes/PhotoView
-simple-crop-image-lib:
https://github.com/biokys/cropimage/tree/master/simple-crop-image-lib
- actionbarsherlock: https://github.com/JakeWharton/ActionBarSherlock
46
Descargar