Desarrollo de Android simplificado con Simple-Stack | de Gabor Varadi | Noviembre de 2020

Ha pasado mucho tiempo desde que quería escribir este artículo. Es hora de hacerlo, ¿verdad? 🙂

La gente siempre parece sorprenderse cuando digo: «Espera, ¿NO usas Jetpack ViewModel, Jetpack Navigation y Dagger-Hilt? Entonces, ¿qué usas?»

Este artículo está destinado a responder a eso. Usamos Pila simple.

Pila simple es un marco de navegación en el que he estado trabajando desde principios de 2017, aunque sus raíces se remontan a 2015, cuando todavía estábamos experimentando con múltiples versiones de Mortar & Flow. (Los niños de los 90 todavía pueden recordar).

SeguiryoBasándonos en la propuesta original de Square, «Abogar contra los fragmentos de Android» y «Aplicaciones de Android más simples con Flow y Mortar», hemos estado utilizando el enfoque de actividad única en nuestras aplicaciones desde 2015.

Ahora, la historia de Flow and Mortar no es todo sencillo. Escuché a gente decir «lo intentamos y no nos funcionó». Puedo dar fe de ello: tuvimos que gestionar nuestra bifurcación, pero aún así, con el tiempo hemos llegado a algunos de sus límites.

Necesitábamos algo confiable y versionado, así que reescribimos el comportamiento original de Flow y queríamos mejorar sus límites. Esta reescritura es lo que llevó a la creación de Simple-Stack.

Pila simple se basa en la idea de que intenta realizar un seguimiento de su estado de navegación como una lista de clases inmutables y parcelables: identificadores de pantalla (que la biblioteca llama «claves»).

Siempre que la implementación de navegación subyacente permita la «navegación de cualquier estado a cualquier estado», puede ajustar esa implementación utilizando Pila simple.

Por ejemplo, algo que escucho a menudo es que «navegar con fragmentos es difícil».

Siempre señalo que en nuestro código ha sido así desde 2016:

backstack.goTo(new SecondScreen(detailId)); // java

Sin transacciones de fragmentos explícitos, sin paquetes, sin newInstance(), llamando solo a un método. ¿Como es posible? ¿No es difícil navegar por los fragmentos? Puede ser, pero los fragmentos son poderosos y pueden ser abstractos.

Volver es muy similar:

backstack.goBack(); // java

No finish(), sin magia.

Obviamente, a lo largo de los años, la Pila simple ha mejorado. Esto es algo que esperaría de una biblioteca que comenzó con 0.1.0 y ahora es 2.4.0.

Originalmente, Pila simple tenía como objetivo simplificar el trabajo con grupos de vistas compuestos como interfaz de usuario principal, pero este artículo se centra en su uso con Fragmentos.

En unos sencillos pasos, podemos simplificar independiente de tipos navegación entre fragmentos.

Como dice en el archivo Léame, la configuración inicial es agregar dependencias:

allprojects {
repositories {
// ...
maven { url "https://jitpack.io" }
}
}
// build.gradle
implementation 'com.github.Zhuinden:simple-stack:2.4.0'
implementation 'com.github.Zhuinden:simple-stack-extensions:2.0.1'

y agregue el punto de integración en la actividad:

Agregar Simple-Stack a la actividad

Con eso, instalamos Pila simple‘S Navigator, que hará que el backstack disponible para nosotros, prácticamente en cualquier lugar (y maneja la recuperación del estado, los eventos del ciclo de vida y las pulsaciones inversas, junto con la navegación por fragmentos en general).

Si definimos FirstKey, esto mostrará nuestro primer fragmento en nuestra pantalla, y se ve así:

@Parcelize 
class FirstKey: DefaultFragmentKey() {
override fun instantiateFragment() = FirstFragment()
}

Por defecto, DefaultFragmentKey utilizará javaClass.name como una etiqueta de fragmento, pero se puede personalizar.

Para hacer el FirstKey accesible a través de getKey(), podemos extender nuestro fragmento de KeyedFragment:

FirstFragment

Obviamente no necesitar extender KeyedFragment, como getKey() equivalente a requireArguments().getParcelable(DefaultFragmentKey.ARGS_KEY), ocultarlo es por conveniencia.

Para agregar un nuevo fragmento, lo que hacemos es:

Agregar SecondFragment y SecondKey

Crea una clave, crea un fragmento y podemos alcanzarlo a través backstack.goTo(SecondKey()). Bastante estereotipado.

Los argumentos se pueden pasar directamente a la clave. Siempre que se puedan parcelar, @Parcelize lo manejará por nosotros.

Segunda clave con argumentos

La navegación es una simple llamada a un método.

Hay ocasiones en las que necesitamos transferir eventos a un fragmento.

Mi receta para esto hasta ahora ha sido definir una interfaz, así:

interface BackHandler {
fun onBackPressed(): Boolean
}

Herramienta BackHandler en el Fragmento, si tiene la intención de administrar la prensa trasera:

Manejar de nuevo en un fragmento

Y ahora podemos enviar de vuelta a nuestro fragmento actual mostrado, incluso sin el uso de BackPressDispatcher:

En este caso, podríamos acceder al fragmento actual, preguntarle si piensa manejarlo, y si es así, no pasarlo a actividad, de forma bastante transparente y fácil de entender.

Es posible que se encuentre intentando configurar una vista global, que pertenece a la actividad, mientras navega por sus fragmentos.

En este caso, algunas personas pueden optar por anular Fragment.onStart() y jugar con el negocio allí mismo.

Sin embargo, ¿debería un fragmento conocer realmente las opiniones de la empresa? ¿No sería fantástico si pudiéramos gestionarlo correctamente en el negocio?

Bueno, esto es fácil con Pila simple además. Solo necesitamos poner el ID de activo del título en la clave y administrarlo junto con el cambio en el estado de navegación que ya estamos administrando.

Aquí es donde las cosas se ponen un poco más interesantes. Aunque ya he mencionado esto en dos de mis artículos anteriores:

Vale la pena repetirlo una vez más, ya que aquí es donde entra en juego el Jetpack ViewModel (y en casos simples, el reemplazo de Dagger / Koin).

Pila simple define el concepto de «áreas».

La idea es que a medida que navega hacia adelante y hacia atrás, su estado de navegación puede declarar qué ámbitos espera que existan cada pantalla mientras la navega, y si aún no existe, el Backstack lo creará.

Cada ámbito puede contener «servicios con alcance«, Que tienen un ciclo de vida que se administra automáticamente en función del estado de navegación (y también puede admitir la persistencia y restauración del estado guardado).

Para casos más simples, podemos usar el DefaultServiceProvidere implementar DefaultServiceProvider.HasServices en nuestra clave. Esto nos permitirá hacerlo lookup() servicios del Backstack registrados en una de las áreas anteriores.

Cualquier servicio registrado en GlobalServices también estará disponible, viviendo en el área principal de todas las áreas. Al crear servicios de ámbito asociados con pantallas, puede utilizar serviceBinder.lookup() para heredar servicios de ámbitos previamente asociados.

Con eso, tenemos un archivo FirstScopedModel que hereda el someDao de servicios globales, es capaz de navegar, obtener argumentos e incluso podría manejar el ciclo de vida o la recuperación del estado si se implementa Bundleable es ScopedServices.Registered respectivamente.

Si necesita asociar ámbitos compartidos adicionales entre pantallas que no son directamente el resultado del historial de la pantalla, puede utilizar ScopeKey.Child. Es similar en concepto a los ViewModels de ámbito NavGraph.

Si queremos configurar un historial completamente nuevo (por ejemplo, para enlaces profundos), podemos usar:

backstack.setHistory(
History.of(SomeScreen(), OtherScreen()),
StateChange.FORWARD
)

Si solo queremos saltar al primer elemento, podemos usar:

backstack.jumpToRoot()

Podemos utilizar la herencia de servicios con ámbito, ya que se garantiza la existencia de los servicios de la pantalla anterior.

Por lo tanto, si implementamos una interfaz en un servicio previo esperado, podemos capturarlo y pasarle los resultados directamente.

Si es así, hemos creado una implementación de ScopedServices que delegaría el enlace múltiple al mapa del proveedor de Dagger para crear servicios de ámbito para un ámbito determinado.

Pero esto fue principalmente necesario porque las claves eran globales, aunque lo más probable es que no sea la mejor forma de estructurar el código de navegación entre módulos (ya que se convierte en una dependencia global de todos los módulos, mientras que las claves se separan de la funcionalidad a la que pertenecen. ).

Después de todo, si necesita saber o hacer suposiciones sobre la existencia de otros módulos de los que no depende, entonces está violando el aislamiento de los módulos (a través de la reflexión u otros medios atroces), lo cual no es un enfoque positivo.

La próxima vez, prefiero exponer una interfaz del módulo de funciones y administrar la navegación en el módulo de la aplicación. De esta manera, no se necesitan tales trucos.

Técnicamente sí, aunque todavía no puedo entender las animaciones componibles. Actualizaré este resumen cuando me entere.

Uso de Simple-Stack con Compose

En realidad, esto nos da todo lo anterior. Navegación por pantalla, soporte de osciloscopio (vía backstack.lookup()), y no hace falta decirlo: la restauración del estado mediante la muerte del proceso.

Espero que este artículo haya ayudado a aclarar algunos misterios sobre cómo lo usamos actualmente. Pila simple.

La configuración predeterminada de los fragmentos funciona bien para animaciones simples (la predeterminada es la diapositiva horizontal, aunque es personalizable), y las animaciones generalmente no son tan llamativas en las aplicaciones comerciales. Las transiciones de elementos compartidos, desafortunadamente, necesitarían un poco más de trucos (debido al diseño de FragmentTransaction), aunque hay un ejemplo de esto en el repositorio.

Cuando necesitábamos animaciones más complejas, en realidad optamos por utilizar grupos de vistas compuestos, ya que son más versátiles a este respecto.

Sin embargo, el resultado final es que podemos acceder al archivo backstack a lo largo del proyecto (actividades, fragmentos, vistas, servicios delimitados) según sea necesario y su ciclo de vida es predecible (vive dentro de la actividad mantenida y gestiona la muerte del proceso).

Utilizando Pila simple nos permite abstraer el ciclo de vida de Android hasta tal punto que las acciones de navegación se convierten en una simple llamada a un método. Pasar los resultados se convierte en una llamada a un método. Pasar parámetros entre pantallas se convierte en un parámetro de constructor de una clase. Prueba de tipo es personalizable navegación, con gestión de estado sensible al ciclo de vida.

Puedes ver la esencia de cómo usar Pila simple en este proyecto de muestra, aunque será inquietantemente similar a este artículo.

Espero que esto haya ayudado a explicar cómo funciona, por qué lo estamos usando y tal vez incluso cómo se compara con los enfoques alternativos actuales. Si tiene más preguntas, comente a continuación 🙂

También puede consultar los hilos de discusión, aquí para / r / androiddev, es aquí para / r / android_devs.

Deja una respuesta

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