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