Las aplicaciones móviles utilizan cada vez más enlaces profundos. Estos son enlaces que le permiten no solo acceder a la aplicación desde el exterior, sino acceder a una pantalla específica. Vladislav Kozhushko, un desarrollador de Android de Yandex.Food, explicó por qué implementamos la navegación desde Jetpack para implementar enlaces profundos, qué problemas encontramos, cómo se resolvieron y qué sucedió al final.
- Hola a todos! Me llamo Vlad He estado interesado en el desarrollo de Android desde 2013, he estado trabajando en Yandex.Ed desde el verano pasado. Te contaré sobre nuestra forma de introducir la biblioteca de Componentes de navegación en una aplicación de combate.
Todo comenzó con el hecho de que teníamos la tarea técnica de refactorizar la navegación. Luego, los gerentes de producto se acercaron a nosotros y nos dijeron que haríamos enlaces profundos, que habría muchos, que conducirían a diferentes pantallas.
Y aquí pensamos: la navegación presentada en Google I / O 2018 es muy adecuada para la implementación de tareas en enlaces profundos. Decidimos ver qué pasa. Nuestros colegas con iOS en Xcode tienen un editor gráfico conveniente en el que pueden usar el mouse para pinchar todo el diseño de las pantallas, así como establecer transiciones entre pantallas. Ahora también tenemos esa oportunidad, podemos usar el mouse para establecer transiciones, enlaces profundos a pantallas y asociarlos con fragmentos. Además, podemos establecer argumentos en la pantalla.

Es decir, los argumentos deben estar atascados en un editor de interfaz de usuario o escritos en XML. Además de cambiar entre pantallas, apareció una etiqueta de acción, en la que indicamos su identificación y la pantalla a la que debemos ir.

También indicamos el enlace profundo con el que queremos abrir la pantalla. En este caso, hay un parámetro itemId, si pasamos un parámetro de este tipo, y dará lugar a un fragmento, entonces el valor de este parámetro se pasará a los argumentos del fragmento, y podemos obtenerlo y usarlo con la clave itemId. La biblioteca también admite enlaces ascendentes. Si configuramos el enlace ascendente, por ejemplo, navdemo.ru/start/{itemId}, ya no tendremos que ocuparnos de registrar esquemas http / https para abrir dichos diplinks. La biblioteca hará todo por nosotros.
Ahora hablemos de los argumentos. Agregamos dos argumentos, entero y booleano, y también les dimos valores predeterminados.

Después de eso, al ensamblar el fragmento, tendremos la clase NextFragmentArgs. Tiene un generador con el que puede ensamblar y establecer los argumentos que necesitamos. También hay captadores en la clase NextFragmentArgs para obtener nuestros argumentos. Puede recopilar NextFragmentArgs del paquete y convertir el paquete, lo cual es muy conveniente.

Sobre las características principales de la biblioteca. Ella tiene un conveniente editor de interfaz de usuario en el que podemos hacer toda la navegación. Obtenemos un marcado XML legible que se infla de la misma manera que las Vistas. Y no tenemos tantos dolores como los desarrolladores de iOS cuando corrigieron algo en el editor gráfico, y muchas cosas cambiaron.
Los enlaces profundos pueden funcionar tanto con fragmentos como con Actividad, y para esto no es necesario registrar una gran cantidad de IntentFilter en el manifiesto. También admitimos enlaces ascendentes, y con Android 6 puede habilitar la verificación automática para la verificación. Además, al ensamblar proyectos, la generación de código se realiza con argumentos en la pantalla deseada. La navegación admite gráficos anidados y navegación anidada, lo que le permite descomponer lógicamente toda la navegación en subcomponentes separados.

Ahora hablaremos sobre nuestro camino, que atravesamos, presentando la biblioteca. Todo comenzó con la versión alfa 3. Implementamos todo, reemplazamos todos los componentes de navegación con componentes de navegación, todo es excelente, todo funciona, los diplinks se abren, pero aparecieron problemas.

El primer problema es una IllegalArgumentException. Apareció en dos casos: la inconsistencia del gráfico, porque hubo una desincronización de la representación del gráfico y los fragmentos en la pila, debido a esto se produjo esta excepción. El segundo problema es hacer doble clic. Cuando hicimos el primer clic, estamos navegando. Pasa a la siguiente pantalla y cambia el estado del gráfico. Cuando hacemos el segundo clic, el gráfico ya está en un nuevo estado y está tratando de hacer la transición anterior, que ya no existe, por lo que obtenemos esa excepción. En esta versión, no se abrieron diplinks, cuyo esquema contiene un punto, por ejemplo, project.company. Esto se decidió en futuras versiones de la biblioteca, y en la versión estable todo funciona bien.
Además, los elementos compartidos no son compatibles. Probablemente haya visto cómo funciona Google Play: hay una lista de aplicaciones, hace clic en la aplicación, se abre la pantalla y se produce una hermosa animación de mover el icono. También tenemos esto en la aplicación en la lista de restaurantes, pero necesitábamos soporte para elementos compartidos. Además, SafeArgs no funcionó para nosotros, por lo que vivimos sin ellos.

Fue fácil arreglar el enlace profundo. Era necesario reemplazar el esquema que estaba en la biblioteca con su propio esquema, que apoya el punto. Con la ayuda de la reflexión, tocamos la clase, cambiamos el valor de la expresión regular y todo funciona.

Para corregir el doble clic, utilizamos el siguiente método. Tenemos funciones de extensión para configurar clics en la navegación. Después de hacer clic en un botón u otro elemento, actualizamos ClickListener y hacemos la navegación para evitar una doble transición. O si tiene RxJava en su proyecto, le recomiendo usar la biblioteca RxBindingsgs de Jake Worton, y con ella puede manejar los eventos de View en un estilo reactivo, utilizando los operadores disponibles para nosotros.

Hablemos de elementos compartidos. Como aparecieron un poco más tarde, decidimos finalizar el navegador y agregarle navegación. Somos programadores, ¿por qué no?

El refinamiento fue el siguiente: heredamos nuestro navegador del navegador, que está en la biblioteca. No todo el código se presenta aquí, pero esta es la parte principal que hemos finalizado. Quiero señalar que antes de hacer la navegación, se verifica el estado del FragmentManager. Si se salvó, entonces perdemos nuestros equipos. En mi opinión, esto es un defecto.
Además, cuando comenzamos una transacción fragmentada, creamos una transacción y configuramos todas nuestras Vistas que necesitan ser revisadas. Pero la pregunta es, ¿qué clase de clase es esta TransitionDestination? Esta es nuestra clase personalizada en la que es posible establecer Vistas. Lo heredamos de Destination y ampliamos la funcionalidad. Establecemos Vistas y nuestro Destino está listo para compartirlas.

La siguiente parte: necesitamos hacer la navegación. Cuando hacemos clic en el botón, buscamos el destino de identificación, sacamos la parte superior del gráfico al que debemos ir. Después de eso, lo transformaremos a nuestro TransitionDestination, en el que tenemos Vistas. A continuación, configuramos todas nuestras Vistas para animar las transiciones, y hacemos la navegación. Todo funciona, todo es super. Pero entonces apareció alpha06.
Esto no significa que hicimos saltos entre versiones. Intentamos actualizar las bibliotecas según sea necesario, pero quizás estos son los cambios más básicos que encontramos.
Hubo problemas con alpha06. Como era una versión alfa de la biblioteca, hubo cambios constantes asociados con los métodos de cambio de nombre, devoluciones de llamada, interfaces, sin mencionar el hecho de que los parámetros se agregaron y eliminaron. Como escribimos nuestro propio navegador, tuvimos que sincronizar el código del navegador de la biblioteca con el nuestro para completar también las correcciones de errores y las nuevas funciones.
Además, en la biblioteca misma, a medida que pasamos de las primeras versiones alfa a las versiones estables, el comportamiento ha cambiado, algunas características se han eliminado. Solía haber un indicador launchDocument, pero nunca se usó, luego se eliminó.

Por ejemplo, hubo un cambio en el que los desarrolladores dijeron que el método navegarUp (), que funciona con DrawerLayout, está en desuso, use otro, en el que los parámetros simplemente se intercambian.


Nuestra próxima gran migración fue en alpha11. Aquí se corrigieron los principales problemas de navegación al trabajar con el gráfico. Finalmente retiramos nuestro controlador dopado y usamos todo lo que estaba fuera de la caja. Los argumentos seguros todavía no funcionaban para nosotros, y estábamos molestos.
Luego se lanzó beta01, y en esta versión prácticamente nada cambió cuando se comportó la navegación, pero apareció el siguiente problema: si hay un cierto número de pantallas abiertas en la aplicación, entonces tenemos que borrar la pila antes de abrir nuestro diploma. Este comportamiento no nos vino bien. Los argumentos seguros todavía no funcionaban.

Escribimos un problema en Google, al que nos dijeron que todas las reglas fueron concebidas originalmente, y en el código en sí mismo esto se debió a que antes de cambiar al enlace profundo, volvimos al fragmento raíz utilizando la identificación del gráfico que se encuentra en la raíz. Y también en el método setPopUpTo () se pasa el indicador verdadero, que dice que al regresar a esta pantalla, también debemos soltarlo de la pila.

Decidimos devolver nuestro navegador dopado y arreglar lo que creemos que está mal.

Aquí está, el problema original que causó que la pila se borrara. Lo resolvimos de la siguiente manera. Verificamos si startDestination es igual a cero, la pantalla inicial, luego la usaremos, tomaremos como identificador el identificador del gráfico. Si nuestra ID de inicio de Destino no es igual a cero, tomaremos esta ID del gráfico, gracias a lo cual no podremos borrar la pila y abrir el diploma en la parte superior del contenido que tenemos. O, como opción, simplemente puede eliminar la ventana emergente true en las opciones de navegación. En teoría, todo debería funcionar también.
Y finalmente, sale una versión estable. Nos alegramos, pensamos que todo estaba bien, pero en la versión estable, el comportamiento, en general, no cambió. Simplemente lo hicieron finalizado. Finalmente conseguimos que los argumentos seguros funcionaran, así que comenzamos a agregar argumentos activamente en nuestras pantallas y a usarlos en todas partes del código. También descubrimos que la navegación no funciona con DialogFragments. Como teníamos DialogFragments, queríamos transferirlos a un gráfico, en XML, y describir las transiciones entre ellos. Pero no tuvimos éxito.

Doble apertura. También tuvimos un problema que nos persiguió desde la primera versión: la doble apertura de Activity durante un inicio en frío de la aplicación.

Sucede de la siguiente manera. Hay un método maravilloso de enlace profundo al que podemos llamar desde nuestro código, por ejemplo, cuando estamos en actividad nos ponemos en NewIntent () para interceptar un enlace profundo. Aquí, el indicador ACTIVITY_NEW_TASK llega a Intent cuando la aplicación se inicia utilizando el enlace profundo, por lo que sucede lo siguiente: se inicia una nueva Actividad y, si hay una Actividad actual, se elimina. Por lo tanto, si iniciamos la aplicación, la pantalla blanca comienza primero, luego desaparece, aparece otra pantalla y se ven muy hermosas.
Como resultado, al presentar esta biblioteca, obtuvimos las siguientes ventajas.

Tenemos documentación para nuestra navegación, así como una representación gráfica de las transiciones entre pantallas, y si una persona viene a nosotros en un proyecto, lo comprende rápidamente al mirar el gráfico y abrir su presentación en el Estudio.
Tenemos SingleActivity. Todas las pantallas están hechas en fragmentos, todos los diplinks conducen a fragmentos, y creo que esto es conveniente.
El resultado fue una simple vinculación del enlace profundo con fragmentos, solo agregue la etiqueta de enlace profundo al fragmento, y la biblioteca hace todo por nosotros. También dividimos nuestra navegación en subgrafías anidadas, hicimos navegación anidada. Estas son cosas diferentes. Solo un gráfico anidado, de hecho, se incluye dentro del gráfico, y la navegación anidada es cuando se usa un navegador separado para caminar alrededor de las pantallas.
También cambiamos el gráfico dinámicamente en el código, podemos agregar vértices, podemos eliminar vértices, podemos cambiar la pantalla de inicio, todo funciona.
Casi olvidamos cómo trabajar con el FragmentManager, ya que toda la lógica de trabajar con él está encapsulada dentro de la biblioteca, y la biblioteca hace toda la magia por nosotros. La biblioteca también funciona con DrawerLayout, si tiene un conjunto de fragmentos raíz, la biblioteca misma dibujará una hamburguesa y, al pasar a las siguientes pantallas, dibujará una flecha y lo hará animadamente cuando regrese del penúltimo fragmento.
También transferimos todos los argumentos, la mayoría de ellos, a SafeArgs, y todo se crea cuando construimos el proyecto. Además, nos enfrentamos a todos los problemas que nos atormentaban y modificamos la biblioteca para adaptarla a nuestras necesidades.
SafeArgs puede generar código Kotlin, se utiliza un complemento separado para esto.

Además, la biblioteca tiene desventajas. El primero es que la limpieza de la pila se entregó a la versión estable. No sé por qué se hizo esto, tal vez sea tan conveniente para alguien, pero en el caso de nuestra aplicación, nos gustaría abrir enlaces profundos sobre el contenido que tenemos.
Los fragmentos en sí mismos son creados por el fragmento del navegador, y se crean a través de la reflexión. No creo que esto sea una ventaja en la implementación. La condición para el enlace profundo no es compatible. Su aplicación puede tener pantallas secretas que están disponibles solo para usuarios autorizados. Y para abrir los enlaces profundos por condición, debe escribir muletas para esto, ya que no puede configurar un procesador de enlaces profundos en el que tengamos control y decimos qué hacer, abrir la pantalla mediante el enlace profundo o abrir otra pantalla.
Además, los comandos de transición se pierden, todo porque el fragmento del navegador verifica el estado de si está guardado o no, y si está guardado, simplemente no hacemos nada.
También tuvimos que modificar la biblioteca con un archivo, esto no es una ventaja. Y otro inconveniente importante: no tenemos la oportunidad de abrir una cadena de pantallas frente al enlace profundo. En el caso de nuestra aplicación, nos gustaría hacer un enlace profundo a la canasta, antes de lo cual abrimos todas las pantallas anteriores: restaurantes, un restaurante específico, y solo después de eso la canasta.
Además, para trabajar con la navegación, debe tener una instancia de Vista. Para obtener un navegador, debe dirigirse a la Vista (la biblioteca de navegación se contactará con los padres de esta Vista) e intentar encontrar el navegador en ella. Si lo encuentra, entonces la pantalla va a la pantalla que necesitamos.
Pero surge la pregunta: ¿vale la pena usar la biblioteca en la batalla? Diré que sí si:

Si necesitamos obtener un resultado rápido. La biblioteca se implementa muy rápidamente, se puede insertar en el proyecto en aproximadamente 20 minutos, todas las transiciones se tocan con el mouse, todo esto es conveniente. También se escribe rápidamente en XML, se ensambla rápidamente y funciona rápidamente. Todo es super
Si necesita tener muchas pantallas en la aplicación que funcionen con enlaces profundos, y no hay condiciones bajo las cuales deberían abrirse.
No hay una sola actividad. Aquí, me refiero no solo a una sola actividad. Podemos navegar tanto en fragmentos con una Actividad como en una mezcla de fragmentos y Actividad, solo en el gráfico también puede describir Actividad. Para esto, se utiliza un proveedor dentro del navegador, en el que hay dos implementaciones de navegación. Uno en Actividad, que crea Intentos para ir a las Actividades deseadas, la segunda implementación es un navegador de fragmentos que trabaja internamente con un administrador de fragmentos.
Si no tiene una lógica compleja para cambiar entre pantallas. Cada aplicación es única a su manera, y si existe una lógica compleja para abrir pantallas, esta biblioteca no es para usted.
Está listo para ingresar a la fuente y modificarla como lo hacemos nosotros. Esto es realmente muy interesante.

Diré que no si la aplicación tiene una lógica de navegación complicada, si necesita abrir una cadena de pantallas antes del enlace profundo, si necesita condiciones difíciles para abrir las pantallas a través del enlace profundo. En principio, con el ejemplo de autorización, esto se puede hacer, la pantalla que necesita simplemente se abre, y encima está la pantalla de autorización. Si el usuario no inicia sesión, se lo arroja a la pila a la pantalla anterior, antes de la cual se abrió la pantalla secreta. Tal decisión de muleta.
Perder equipos es fundamental para ti. El mismo Cicerone puede guardar comandos en el búfer, y cuando el navegador está disponible, los ejecuta.
Si no desea finalizar el código, esto no significa que usted, como desarrollador, no quiera hacer algo. Supongamos que tiene una fecha límite, los gerentes de producto le dicen que necesita cortar funciones, una empresa quiere implementar funciones. Necesita una solución llave en mano que funcione de inmediato. Navigation Components no se trata de este caso.
Una cosa más crítica: los DialogFragments no son compatibles. Se podría agregar un navegador que trabajaría con ellos, pero por alguna razón no se agregaron. El problema es que ellos mismos se abren como fragmentos ordinarios. La casilla de verificación para isShowing no está establecida y, en consecuencia, no se ejecuta el ciclo de vida de DialogFragments para crear un diálogo. De hecho, DialogFragment se abre como un fragmento regular, toda la pantalla sin crear una ventana.
Eso es todo para mí. Cavar la fuente, esto es realmente interesante y emocionante. Aquí hay
materiales útiles para aquellos que estén interesados en trabajar con la biblioteca de navegación. Gracias por su atencion