State restoration API en Flutter

Alfredo Bautista Santos
7 min readAug 29, 2022

¡Buenas a todas y todos! Volvemos con los artículos sobre Flutter con una API muy potente y de la que hay muy poco contenido explicativo sobre como implementarlo, ventajas de usarlo y algunas recomendaciones de su uso.

Introducción

En el desarrollo mobile, tenemos que tener en cuenta algunos factores que interactuarán con nuestra aplicación cuando el usuario empiece a usarla, uno de ellos será como el sistema operativo va a gestionar el ciclo de vida de nuestra aplicación.

Seguro que te ha pasado, estabas usando una aplicación, te llega una notificación, vas al Home de tu dispositivo… Haces algunas acciones y cuando vuelves a la app anterior, está se ha reiniciado y no ha “guardado” lo que estabas haciendo. Dependiendo del caso, esto puede ser una muy mala experiencia, imagina un registro con varios pasos donde tienes que salir a comprobar tu email, buscar una foto… Puede hacer que el usuario nunca vuelva a entrar en tu app.

¿Porqué ocurre esto? Bueno, en un mundo ideal, tendríamos Hardware capaz de gestionar infinitas aplicaciones corriendo en segundo plano, pero la realidad no es así. Por lo que el S.O debe gestionar que aplicaciones puede mantener activas y cuales debe cerrar.

En estos casos, podemos hacer uso de la API de la que vamos a hablar para que el usuario no tenga la sensación de que la app se ha cerrado y que sigue activa por donde la había dejado anteriormente.

Guardar el estado de la navegación

Podemos empezar guardando el stack de navegación que el usuario ha tenido en nuestra app. Una solución muy rápida y sencilla que va a mejorar muchísimo la experiencia del usuario cuando tenemos una navegación compleja en nuestra app.

Dentro de las docs del componente Navigator podemos ver como tenemos los métodos que siempre hemos usado, el push, pushNamed, pop, popUntil… pero también tenemos otros que empiezan por restorable… ¿Te suena para que pueden servir? ;)

Directamente, y sin más cambios, podemos pasar de un pushNamed a un restorablePushNamed para que Flutter persista esa ruta en el stack de navegación cuando la aplicación se cierre, ¿Increíble, verdad? Aunque todavía nos queda un paso más para que todo funcione.

Sin entrar en mucha teoría, que podéis consultar en los docs de la API, necesitamos incluir un restorationScopeId, un String único, en nuestra materialApp para que Flutter pueda tener una referencia donde guardar la información necesaria:

Solo nos haría falta un cambio más, que tú seguramente no tengas que hacer, y es que las rutas deben ser estáticas. Seguramente ya tengas un componente dedicado a esto, en nuestro ejemplo, para hacerlo lo más sencillo posible, vamos a tener una clase con métodos estáticos que nos devuelven el MaterialPageRoute

¡Listo! Ya tendremos todo lo necesario para que persista nuestro stack de navegación, pero como sé que sin verlo nadie se lo va a creer… Vamos a tener 3 pages y vamos a navegar entre ellas, cerramos la app y vemos que pasa.

Preparando el entorno

Para simular que el sistema cierra la app por falta de recursos, tenemos que habilitar la siguiente característica dentro de las opciones de desarrollador de nuestro Android

¡Y aquí tenemos la prueba!

Guardar estados de inputs, checkbox, radio buttons…

A parte de guardar el estado de la navegación, la API de Restoration nos permite guardar y recuperar información para hidratar nuestra app a la hora de ser restaurada. Esto incluye cualquier Widget que pueda hacer uso de variables para manejar su estado, como algunos ejemplos podemos tener los TextEditingController, booleanos para saber si tenemos marcado alguna opción…

Para probarlo, vamos a convertir nuestro HomePage a un StatefulWidget, necesario para poder utilizar la restauración de propiedades.

Una vez convertido a StatefulWidget, tenemos que hacer uso de RestorationMixin, que nos obligará a extender dos métodos:

  1. RestorationId: Getter que devuelve un String único para poder restaurar el contenido de ese page, simplemente utiliza algún string como “MyHomePageRestorationId”. Si este getter devuelve un null, la API considerará que no debe ser restaurado.
  2. RestoreState: Callback que recibe el RestorationBucket y un boolean. En este método incluiremos toda la lógica para hidratar nuestro page una vez que la aplicación vuelve a estar activa. El RestorationBucket será null si es la primera vez que se ejecuta o si se ha desactivado la API porque el RestorationId ha cambiado a null.

La API es muy extensa y te recomiendo echarle una buena lectura, pero vayamos a un ejemplo práctico que va a hacer que nos quede más claro.

Imagina que tenemos dos inputs, un checkbox y un Radio Button, para ello necesitamos tener las siguientes variables para manejarlas. Aunque ahora vamos a ver algunas diferencias a como lo haríamos normalmente.

Para guardar y restaurar los valores, no vamos a usar primitivos como boolean, String, int, double… Si no que vamos a utilizar nuevos objetos de la API de Restoration, aquí tenemos algunos ejemplos:

  • TextEditingController = RestorableTextEditingController
  • Boolean = RestorableBool
  • Int / Double = RestorableInt / RestorableDouble

Vamos a montar una UI sencilla para probar estos campos, las únicas diferencias utilizando el Restoration es el uso de _nameController.value para asignar el controller o el _isTermsChecked.value para asignar el nuevo booleano.

¡Y ahora probemos!

Con esta configuración ya tenemos bastante flexibilidad. Podemos guardar la suficiente información y restaurarla de manera rápida para dar una experiencia de que la app no se ha cerrado en ningún momento, pero… ¿Y si queremos restaurar objetos más complejos que los que nos da la API?

Bonus tip: ¡Crea tus propios restorables!

Antes de empezar este apartado, tenemos que tener en cuenta la limitación de espacio que tienen los SO a la hora de reservar espacio para este tipo de acciones, Android en este caso tiene un límite de 1 MB.

Flutter utilizará la API de Parcel para transmitir los datos entre la app y el sistema operativo, si te excedes del tamaño, esto puede provocar un TransactionTooLargeException. Teniendo esto en cuenta, veamos como implementar nuestros restorables.

La API de Restoration nos ofrece la interfaz RestorableValue para poder implementar nuestra propia lógica. Para este ejemplo vamos a usar un enum. Veamos los métodos que tenemos que implementar a la hora de extender la interfaz.

Vamos a detallarlos uno a uno, aunque son bastante autodescriptivos:

  • createDefaultValue: Getter para devolver un valor por defecto
  • didUpdateValue: En este método tendremos que llamar al notifyListeners() si chequeando el antiguo valor que obtenemos en la función con el valor actual ha cambiado y debe ser registrado
  • fromPrimitives: Devolver nuestro objeto creado desde el primitivo que hemos usado para guardar el estado de nuestra instancia
  • toPrimitives: Función que devuelve el objeto a guardar por el sistema.

Aunque siempre es mejor verlo en práctica

Como vemos, hemos seguido lo que hay que hacer en cada método. Como cosas a destacar el uso del índice para guardar / restaurar el enum y el name para saber si hay cambiado, aunque también podríamos utilizar el índice.

Creamos un pequeño DropdownButton para probar que nuestra restauración funciona con los diferentes valores de nuestro enum.

¡Y ahora probémoslo!

Repositorio con el ejemplo

Para poder replicar el ejemplo y utilizarlo en vuestros proyectos, os dejo el link del repositorio con el código mostrado en el tutorial y dividido en commits para que podáis seguir todos los pasos de lo explicado anteriormente.

Por último dejo os mi twitter donde me encontraréis hablando sobre tecnología en general pero sobre todo Flutter 💙

¡Un saludo y hasta la próxima!

--

--

Alfredo Bautista Santos

Sysadmin and web developer. Co-organizer of GDGMarbella & FlutterConf in Marbella, Spain. Flutter enthusiastic.