¡Atención! Este sitio utiliza cookies.
Si no cambias la configuración de tu navegador, aceptas su uso.

Controladores en Android para desarrolladores de Cocoa

ATENCIÓN. Este artículo podría incluir fragmentos en Java™. Si sufre taquicardias, migrañas, o se encuentra en periodo de gestación, se recomienda no continuar leyendo. En caso de alucinaciones, desprendimientos de retina, vómitos, ardor anal, o todas a la vez, por favor, consulte a su médico, eso no tiene nada que ver conmigo. Del mismo modo, el autor no se hará responsable de los daños físicos o psicológicos generados por la lectura de las líneas mostradas a continuación.

Llaba el Jat

Jabba ‘the Hutt’ aprueba este contenido

La navegación entre pantallas fue una de las cosas que más me chocaron del desarrollo de Android. Acostumbrado en iOS a moverme entre ellas como quien pone una carta encima de otra, la quita, la vuelve a poner, cruza otra sobre la anterior, la retira, o incluso tira toda la pila del mazo para dejar solo la primera… me encontré con un paradigma que —de forma horripilante, catastrófica, apocalíptica, como diría Piqueras— me recordaba al desarrollo web. Eso de hacer push sobre una pantalla y esperar que cuando el usuario le dé a ‘Volver’ en la siguiente todo siga como estaba… qué ingenuidad.

En Android esos ‘saltos’ entre pantallas no funcionan con segues, y por supuesto no existen las push y las present de la clase UIViewController. El concepto para avanzar es más bien el siguiente: “Dime qué quieres hacer, qué necesitas y a dónde quieres llegar… y déjame el resto a mí”, y el de volver este:”recuerdo dónde te habías quedado, de qué pantalla venías, pero no me preguntes qué ponía en esa etiqueta porque no tengo ni p*** idea”. En ciertos casos me pareció similar a utilizar javascript para volver a la pantalla anterior en un navegador (o pulsar el botón de ‘Atrás’ pertinente), que provoca que vuelvas a donde estabas antes, pero sin garantías de que se haya guardado el estado. Vamos, que sería más probable que el gitano de la esquina te extendiera factura a que un controlador en Android se comprometiera a dejarte una pantalla tal y cómo estaba antes de perderla de vista. Y por ‘cambio’ quiero decir cosas como rotar la pantalla. Y por ‘cambio’ quiero decir cosas como rotar la pantalla. Y por ‘cambio’ quiero decir cosas como rotar la pantalla.

No, no es una errata. Lo he puesto tres veces. Para que entre.

Uno no entra en Android y navega libremente

Uno no entra en Android sin más y navega tan ricamente

En Mordor Android cada controlador no solo debe hacerse responsable de los datos que maneja, sino que preservarlos por su cuenta. Cada vez que es alterado, el sistema llama al método onCreate() , que no tiene equivalente en Cocoa, pero que viene a ser un viewDidLoad() con muchos ‘peros’, entre ellos, que mientras en Cocoa tenemos un viewWillLayoutSubviews() para el repintado de las vistas, en Android volvemos a pasar por la creación completa. Esto quiere decir que, si por ejemplo, cargases una página web y navegases tan ricamente por diferentes enlaces, en el momento en que girases el dispositivo volvería a repintarse con la pantalla inicial, ignorando por completo el recorrido ya realizado.

¿Cómo evitar que se pierda el estado?

Toda instancia de un controlador recibe como parámetro, en este onCreate(), y en el argumento savedInstanceState, un objeto de tipo Bundle (una saco de datos mapeado) que el sistema retiene en forma de caja negra. Revisando su contenido podemos recuperar el estado previo del controlador para restablecer sus valores anteriores. Ni que decir tiene que en el primer arranque de cualquier controlador, este valor savedInstanceState será nulo, así que tendremos que ser nosotros los que aportemos los valores iniciales, como siempre. Si es nulo, es una primera vez. Si no lo es, más vale que recuperes todo lo que hubiera dentro, porque Android no tiene misericordia.

Esta técnica es la misma otros tipos de eventos dentro del ciclo de vida dentro de los controladores, no solo para las rotaciones. Para volver de una pantalla detalle a un listado en ciertos casos tendremos que hacer lo mismo (ya que el sistema no nos garantiza que la vaya a preservar en memoria), así que podemos hablar de un patrón de diseño en Android. Recuerda guardar el estado antes de perderlo para siempre, porque cosa que no guardes, cosa que desaparece.

  • Para guardar antes de que el controlador se detenga: OnSaveInstanceState().
  • Para recuperar el estado cuando se retoma la actividad, utilizamos onCreate() o OnRestoreInstanceState(), según el caso.
Cartucho de Jalf Laif 3

¿Te acuerdas de eso de ‘Guardar partida’?

Por cierto, pulsar la tecla de retorno destruye tu controlador de forma explícita. Salvar el estado solo sirve cuando es el sistema quien retoma la actividad. No puedes mantener el estado de una pantalla que ya no está en la pila; solamente si has abierto otras desde ella.

Los ‘Activity’ y los ‘Fragment’

Activity

Son dos conceptos totalmente nuevos para desarrolladores de Cocoa, más que nada porque no tenemos cosas que se les parezcan en nuestro framework, aunque sí que tenemos sustitutos a ellos. Vamos a comprender qué son las actividades y los fragmentos, y también a reconocer cuándo utilizar unos u otros de forma correcta.

Trataré de comparar su uso con UIKit, pero es importante notar que no puede servir como un diccionario de uso, sino como una referencia.

Activity

Un Activity representa una única tarea realizar por elusuario. Es importante notar que hablamos de desarrollo móvil y, por eso mismo, es una buena práctica no dotar a las pantallas de nuestras aplicaciones de más de una función, como haríamos en una de escritorio, abarrotada de controles, iconos, paneles, barras y demás.

Una forma de determinar si nuestra nueva clase va a ser una actividad es utilizar el sentido común, pero cuando esta función no esté disponible (todavía no te has bebido el café, es lunes a primera hora o viernes a última) yo recomiendo hacerse una pregunta simple: ¿puedo enunciar esta clase con un verbo y un sustantivo?

Por ejemplo, si estamos hablando de crear un nuevo usuario para nuestra base de datos, y el código que estamos a punto de perpetrar va encargarse de realizar dicha alta, la tarea podría denominarse “Realizar Alta”. No te preocupes si el sustantivo lo hace demasiado específico, que para lo genérico usaremos los fragments (en seguida vamos con ellos). Yo me guío por esta técnica —simplemente— porque así me es más fácil relacionarlas con funcionalidades concretas de la app. Destruir actividades no elimina lógica de negocio, sino solamente la capa de presentación. De nuevo, los fragments son mejores para manejar tareas unitarias.

Los Activities son las tareas y lo ;relacionado con ellas. Todo aquello que pueda ser externalizado para un uso posterior (o porque, narices, código que no necesita ‘mamar’ de una clase, código que se tiene que pirar a ‘mamarla’ a otra) no debe incluirse en un Activity. Es más, intenta crear uno solamente cuando sea imprescindible.

Ciclo de vida de un Activity

Ciclo de vida de las ‘Activity’ © Google

Y acuérdate de repasar bien su ciclo de vida, porque podrías encontrarte con Alderaan en más de una ocasión…

Fragment

Disponibles desde Android 3.0, o sea, la API 11, “Honeycomb” —’panal’ o algo así—, el framework tiene a tu disposición los fragments, las partes reutilizables (o modulares) de nuestros controladores. Tienen un layout muy similar al de un Activity, de modo que, en realidad, es la clase enlazada a ellos la que los define como Fragments. Son solo una diferenciación funcional, puesto que casi funcionan igual que sus hermanas mayores.

Cada fragmento maneja sus propias vistas y solamente se ocupa de ellas de forma egoísta, sin entender dónde se han colocado ni quién les invoca. Deben ir siempre dentro de un Activity, pero pueden aparecer en varios.

De la misma forma que una receta con arroz tiene unos ingredientes básicos (¿arroz?) pero puede alterarse con otros ingredientes —más o menos cocinados— para crear una combinación única, según comensales y el efecto a conseguir. El plato es único, tiene su nombre, sus instrucciones elementales para ser considerado tal, y un toque personal que ya hemos aprendido de otras recetas anteriores. Si considerásemos el plato de arroz un Activity, sería CocinarPlatoDeArroz.

Ciclo de vida de los 'Fragment'

Ciclo de vida de los Fragments © Google

Comparativa con UIKit

Salvando diferencias evidentes, las actividades y los fragmentos son reproducibles en muchos casos en UIKit. Quizá, una de las formas más efectivas (que no realista) de entender cómo funciona Android es compararla con la de UIPageViewController.

Este “Controlador de Páginas”, tan conocido por ser el esqueleto de la app ‘iBooks’, puede utilizarse para fines tan comunes como la creación de asistentes en una app: una instancia de UIPageViewController que contiene el objeto que estamos “cumplimentando” (por ejemplo, un usuario) y un montón de UIViewControllers normaluchos (yo para esto suelo crear una subclase llamada ‘PageVC’) con los que vamos rellenando el nombre completo, nombre de usuario, contraseña, foto de perfil… etc.

Pues bien, en este sencillo ejemplo, nuestro PageViewController sería un Activity, puesto que tiene una única misión: ¡permitir la creación de un nuevo usuario! Como es un proceso largo, no vamos a meter el código de todos los pasos en un solo controlador, así que lo partimos metiéndolo en estos ViewControllers normaluchos, que serían una suerte de Fragments, cada uno con una única caja de texto. ¿Qué pasaría si tuvieras que mostrar el mismo ViewController dos veces? ¿Crearías dos clases diferentes? ¿Meterías una segue con calzador para volver al mismo? Mejor creas uno genérico y lo instancias cuando a ti te da la gana, ¿no?

¿Otro ejemplo? Cada vez que metes un ‘ChildViewController’ dentro de un UIViewController estás, de alguna forma, haciendo algo similar a lo que un androidero haría con un Fragment.

Repito, no son equivalencias entre clases, sino entre formas de trabajar en ambos sistemas. Que no se me tire ningún talibán al cuello. Para aportar ‘matices’ están los comentarios.

Los ‘Intents’

Intent

Como programador de Cocoa… ¿podría decirse que un Intent es como hacer un push o tener una segue? La respuesta corta es “sí”. La segunda más corta es “ni de coña”.

An Intent is basically a passive data structure holding an abstract description of an action to be performed.

En cristiano.

Un ‘Intent’ es simplemente una estructura de datos pasiva que retiene una descripción abstracta sobre la acción que está a punto de ser ejecutada.

Es decir, es como un telegrama de guerra con datos sobre la ubicación a atacar, información y acciones a tomar una vez llegados a destino. Los Intent son el pegamento entre actividades (Activity) o servicios, y si quieres que funcionen bien, más vale que no te olvides de darles valores antes de ir de un controlador a otro; es decir, coge aquellos datos del modelo relevantes para la siguiente acción a realizar y guárdalos en el Intent, para que quien lo reciba haga lo propio (SRP). En Cocoa normalmente inyectaríamos esta estructura de datos directamente en la instancia del controlador siguiente, ya sea porque la hayamos creado nosotros, o porque la estamos recibiendo en el querido prepareForSegue:sender: a través de la segue, ese vórtice que nos absorbe hasta la siguiente pantalla. En Android la comunicación entre controladores está bastante más desacoplada, para que emisor y receptor no se conozcan y se mantengan tan independientes como sea posible. Plas, plas, plas.

intent-filters@2x

¿Y qué parámetros hay que pasarle al ‘Intent’ este?

  • action (requerido): El tipo de acción a realizar del campo name como String (por algún sitio tenían que aparecer los malditos). Las más comunes son ACTION_MAIN y ACTION_EDIT.
  • data (requerido): datos MIME aceptados y ortos como host, port, path
  • category: Especifica información adicional sobre la ‘action’ ya especificada, en el campo name (de nuevo, otro String). Podemos utilizar la categoría, por ejemplo, para especificar cómo queremos presentar los datos al usuario.
  • type: Sobrescribe el tipo (MIME) de datos del campo ‘data’. Lo normal es que se infiera, así que solo sirve para forzarlo.
  • component: El nombre del componente que se utiliza para pasar los datos al Intent, normalmente se crea a partir de los otros campos.
  • extras: Información adicional que podemos incluir en el componente. Esto puede ser cualquier cosa, desde un ID, una URL, etc.

Y sí, podemos crear más de una instancia de cada uno de estos parámetros si es conveniente.

Es interesante notar que los Intent son generalmente asíncronos y suelen resolverse en tiempo de ejecución (no se compilan con el resto del código), lo que, a diferencia de las segues, nos permite mayor agilidad a la hora de invocar controladores, ya que normalmente no podremos estar del todo seguros de quién realizará la tarea. No quiere decir que el sistema sea adivino, sino que utiliza los llamados intent-filters para determinar quién está capacitado (o dice estar capacitado) para hacer lo que pedimos a partir del Android-Manifest. A este tipo de Intent se le denomina implícito. Es algo así como preguntar “¿hay algún médico en la sala?”. Ni el Activity que llama ni el que recibe la tarea se conocen, es más, este último ¡podría estar fuera de nuestra app!

¡¡¡Medic!!!

¡¡¡Medic!!!

Te preguntarás… “¿Y si no hay un médico en la sala qué pasa?” Pues sí, lo que esperas: si creas un Intent implícito y no hay clase para manejarlo, tu app se caerá. Resolver esta embarazosa situación es sencillo: antes de iniciar cualquier otra actividad desconocida es más que recomendable llamar a resolveActivity() dentro del Intent. Es un poco como nuestro respondToSelector, ¿no? Si alguien responde al otro lado, iniciamos la nueva actividad, y si no, pues hacemos lo propio.

En caso de conocer la actividad de antemano, y si tienes muy claro qué clase debe manejar qué tarea, quizá te interesa usar un Intent explícito.

Hay mucha más miga acerca de los Intent, pero no es el propósito de esta entrada profundizar tanto en ellos.

Subclases de ‘Activity’ más importantes

FragmentActivity

Podríamos considerar esta clase como la ViewController básica dentro de las actividades. Permite soporte para introducir los fragments, así que, a no ser que entiendas muy bien en el lío en el que te estás metiendo, procura heredar a partir de esta.

ActionBarActivity (desciende de FragmentActivity)

Es la que se crea por defecto. Viene con una barra superior ‘de regalo’ (ActionBar). Como en Android no existe nada similar al NavigationController, una de las maneras de utilizar una barra de navegación nativa es esta, y bastante recomendable por cierto. Utiliza esta en lugar de soluciones de terceros, por el amor de Job… por las golosinas. Hazlo por las golosinas.

Incluye métodos para sobrescribir la botonera de opciones superior derecha, que viene a ser una especie de BarButtonItem de tipo ‘Action’ con un ActionSheet automático. ¿Me estoy pasando con las comparaciones? Puede. ¿Voy a parar? No.

ListActivity (extiende de Activity)

Incluye métodos para cargar datos a modo de lista y los eventos cuando se seleccionan elementos en ella. Sí, es un UITableViewController en toda regla, con su datasource y su delegate. Una cosa es que nos hagan programar en Java, y otra muy diferente que nos priven de esto. Todavía queda misericordia.

Como habrás observado no desciende de FragmentActivity, pero eso es porque, efectivamente, una tabla no requiere de fragments. No quiere decir que no los podamos combinar, pero este controlador en concreto no los necesita para hacer su función básica, que es listar cosas.

Sin embargo, sí que tiene una pega utilizar esta clase, y es que si queremos que una actividad se componga de una sola tabla, pero al mismo tiempo no queremos renunciar a la barra de navegación superior (ActionBar), al no existir la herencia múltiple en Java tendríamos que renunciar a ListActivity e implementar los métodos nosotros mismos (lo mismo que introducir una UITableView en un UIViewController e implementar el datasource y el delegate). En la práctica suele ser necesario hacer esto porque es muy habitual y recomendable utilizar la barra de título superior; sin embargo, otra opción perfectamente aceptable sería utilizar la clase ListFragment, que en el fondo sería como introducir un childViewController con un UITableViewController dentro. De esa forma separamos la lógica de la tabla del resto de la actividad, y de paso nos permitimos introducir más elementos además de la tabla dentro del ActionBarActivity. No será por opciones.

Patrón de diseño: MVA

¿Acostumbrado a utilizar el clásico-mítico ModelViewController? En Android me temo que no lo vas a encontrar. No es que sea muy diferente (no vamos a engañarnos, es casi lo mismo), pero no es igual. En la plataforma se utiliza extensivamente el ModelViewAdapter, que es el primo de Cuenca del MVC. ¿Qué es lo que cambia? Que realmente, cuando aquí hablo de controladores, me estoy refiriendo a controladores de vista: los Fragments y los Activities. Lo que se utiliza para comunicar el modelo con las vistas es una clase ‘Adapter’ que intercede entre el acceso a datos y la representación en pantalla de los mismos, siguiendo los principios de reutilización y separación de conceptos (“separation of concerns”).

Eso es todo

Iré ampliando y actualizando esta entrada con el tiempo, así que cualquier corrección, comentario o aportación será bienvenida.

Escrito por Miguel Hernández Jaso

Autor del blog. Desarrollador especializado en iOS.

Una respuesta a “Controladores en Android para desarrolladores de Cocoa”

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Mi Gobierno me obliga a decirle que utilizo 'cookies' en mi página. Si continúa navegando, quiere decir que está conforme con ello.