Como implementamos a navegação do Jetpack em um aplicativo de combate. Yandex.Food Report

Os aplicativos móveis estão cada vez mais usando links diretos. Esses são links que permitem não apenas acessar o aplicativo de fora, mas também acessar uma tela específica. Vladislav Kozhushko, desenvolvedor Android da Yandex.Food, explicou por que implementamos a navegação no Jetpack para implementar links diretos, quais problemas encontramos, como foram resolvidos e o que aconteceu no final.


- Olá pessoal! Meu nome é Vlad. Estou interessado no desenvolvimento do Android desde 2013, trabalho no Yandex.Ed desde o verão passado. Vou falar sobre nossa maneira de introduzir a biblioteca de Componentes de Navegação em um aplicativo de combate.

Tudo começou com o fato de termos a tarefa técnica de refatorar a navegação. Em seguida, os gerentes de produto vieram até nós e disseram que faríamos links profundos, haveria muitos deles, eles levariam a telas diferentes.

E aqui pensamos - a navegação apresentada no Google I / O 2018 é muito adequada para a implementação de tarefas em deepplinks. Decidimos ver o que acontece. Nossos colegas com iOS no Xcode têm um editor gráfico conveniente, no qual podem usar o mouse para vasculhar todo o layout das telas, bem como definir transições entre telas. Agora, também temos essa oportunidade, podemos usar o mouse para definir transições, links diretos para telas e associá-las a fragmentos. Além disso, podemos definir argumentos na tela.



Ou seja, os argumentos devem estar presos em um editor de interface do usuário ou gravados em XML. Além de alternar entre telas, apareceu uma tag de ação, na qual indicamos seu ID e a tela para a qual precisamos ir.



Também indicamos o link profundo com o qual queremos abrir a tela. Nesse caso, existe um parâmetro itemId, se passarmos um parâmetro desse tipo, e ele levará a um fragmento, o valor desse parâmetro será passado aos argumentos do fragmento, e podemos obtê-lo e usá-lo com a chave itemId. A biblioteca também suporta uplinks. Se definirmos o uplink, por exemplo, navdemo.ru/start/{itemId}, não precisaremos mais nos preocupar em registrar esquemas http / https para abrir esses diplinks. A biblioteca fará tudo por nós.

Agora vamos falar sobre os argumentos. Adicionamos dois argumentos, inteiro e booleano, e também fornecemos a eles valores padrão.



Depois disso, ao montar o fragmento, teremos a classe NextFragmentArgs. Possui um construtor com o qual você pode montar e definir os argumentos que precisamos. Também existem getters na classe NextFragmentArgs para obter nossos argumentos. Você pode coletar NextFragmentArgs do pacote configurável e converter o pacote configurável, o que é muito conveniente.



Sobre os principais recursos da biblioteca. Ela tem um conveniente editor de interface do usuário no qual podemos fazer toda a navegação. Temos uma marcação XML legível que aumenta da mesma maneira que o Views. E não temos o mesmo esforço que os desenvolvedores de iOS fizeram quando corrigiram algo no editor gráfico, e muitas coisas mudaram.

Os links profundos podem trabalhar com fragmentos e com Activity, e para isso você não precisa registrar uma grande quantidade de IntentFilter no manifesto. Também oferecemos suporte a uplinks e, com o Android 6, você pode ativar a verificação automática para verificação. Além disso, ao montar projetos, a geração de código ocorre com argumentos para a tela desejada. A navegação suporta gráficos aninhados e navegação aninhada, o que permite decompor logicamente toda a navegação em subcomponentes separados.



Agora vamos falar sobre o nosso caminho, pelo qual passamos, apresentando a biblioteca. Tudo começou com a versão alfa 3. Implementamos tudo, substituímos toda a navegação pelos componentes de navegação, tudo é super, tudo funciona, diplinks abertos, mas surgiram problemas.



O primeiro problema é uma IllegalArgumentException. Apareceu em dois casos: a inconsistência do gráfico, porque houve uma dessincronização da representação do gráfico e dos fragmentos na pilha; por isso, houve uma exceção. O segundo problema são cliques duplos. Quando fizemos o primeiro clique, estamos navegando. Vai para a próxima tela e o estado do gráfico muda. Quando clicamos no segundo clique, o gráfico já está em um novo estado e está tentando fazer a transição antiga, que não existe mais, e temos essa exceção. Nesta versão, diplinks não foram abertos, cujo esquema contém um ponto, por exemplo, project.company. Isso foi decidido nas versões futuras da biblioteca e, na versão estável, tudo funciona bem.

Além disso, elementos compartilhados não são suportados. Você provavelmente viu como o Google Play funciona: há uma lista de aplicativos, você clica no aplicativo, sua tela é aberta e ocorre uma bela animação ao mover o ícone. Também temos isso no aplicativo da lista de restaurantes, mas precisávamos de suporte para elementos compartilhados. Além disso, o SafeArgs não funcionou para nós, por isso vivemos sem eles.



Foi fácil corrigir o problema. Foi necessário substituir o esquema que estava na biblioteca por um esquema próprio, que suporte o argumento. Com a ajuda da reflexão, tocamos na classe, alteramos o valor de regex e tudo funciona.



Para corrigir o clique duplo, usamos o seguinte método. Temos funções de extensão para definir cliques na navegação. Depois de clicar em um botão ou outro elemento, atualizamos o ClickListener e fazemos a navegação para evitar uma transição dupla. Ou, se você tiver o RxJava em seu projeto, recomendo o uso da biblioteca RxBindingsgs de Jake Worton e, com ele, você poderá manipular os eventos do View em um estilo reativo, usando os operadores disponíveis.



Vamos falar sobre elementos compartilhados. Como eles apareceram um pouco mais tarde, decidimos finalizar o navegador e adicionar navegação a ele. Somos programadores, por que não?



O refinamento foi o seguinte: herdamos nosso navegador do navegador, que está na biblioteca. Nem todo o código é apresentado aqui, mas esta é a parte principal que finalizamos. Quero observar que, antes de fazer a navegação, o status do FragmentManager é verificado. Se foi salvo, perdemos nossas equipes. Na minha opinião, isso é uma falha.

Além disso, quando iniciamos uma transação de fragmento, criamos uma transação e definimos todas as nossas visualizações que precisam ser vasculhadas. Mas a questão é: que tipo de classe é esse TransitionDestination? Esta é a nossa classe personalizada na qual é possível definir Views. Nós a herdamos do Destino e expandimos a funcionalidade. Definimos vistas e nosso destino está pronto para compartilhá-las.



A próxima parte - precisamos fazer a navegação. Quando clicamos no botão, procuramos o destino da identificação, puxamos a parte superior do gráfico para a qual precisamos ir. Depois disso, vamos transformá-lo em nosso TransitionDestination, no qual temos Views. Em seguida, definimos todas as nossas visualizações para animar transições e fazer a navegação. Tudo funciona, tudo é super. Mas então alpha06 apareceu.

Isso não significa que fizemos saltos entre as versões. Tentamos atualizar as bibliotecas conforme necessário, mas talvez essas sejam as alterações mais básicas que encontramos.

Houve problemas com o alpha06. Como era uma versão alfa da biblioteca, havia alterações constantes associadas a renomear métodos, retornos de chamada, interfaces, sem mencionar o fato de que parâmetros foram adicionados e removidos nos métodos. Como escrevemos nosso próprio navegador, tivemos que sincronizar o código do navegador da biblioteca com o nosso para também preencher correções de bugs e novos recursos.

Além disso, na própria biblioteca, à medida que passamos das versões alfa iniciais para as versões estáveis, o comportamento mudou, alguns recursos foram removidos. Costumava haver um sinalizador launchDocument, mas nunca foi usado e, em seguida, foi removido.



Por exemplo, houve uma alteração na qual os desenvolvedores disseram que o método navigateUp (), que funciona com DrawerLayout, está obsoleto, use outro, no qual os parâmetros são simplesmente trocados.





Nossa próxima grande migração foi na alpha11. Aqui foram corrigidos os principais problemas de navegação ao trabalhar com o gráfico. Finalmente removemos nosso controlador dopado e usamos tudo o que estava fora da caixa. Args seguros ainda não funcionaram para nós, e estávamos chateados.

O beta01 foi lançado e nesta versão praticamente nada mudou durante o comportamento da navegação, mas o seguinte problema apareceu: se um certo número de telas estiver aberto no aplicativo, limparemos a pilha antes de abrir o diplink. Esse comportamento não nos convinha. Args seguros ainda não funcionaram.



Escrevemos um problema no Google, no qual nos disseram que todas as regras foram originalmente concebidas, e no próprio código isso ocorreu porque, antes de mudar para o deepplink, retornamos ao fragmento raiz usando o ID do gráfico que está na raiz. E também no método setPopUpTo () o sinalizador true é passado, dizendo que, retornando a essa tela, também precisamos removê-lo da pilha.



Decidimos devolver nosso navegador dopado e corrigir o que achamos errado.



Aqui está, o problema original que causou a limpeza da pilha. Resolvemos da seguinte maneira. Verificamos se startDestination é zero, a tela inicial, então a usaremos, pegamos o identificador do gráfico como identificador. Se nosso ID de startDestination não for igual a zero, pegaremos esse ID no gráfico, graças ao qual não podemos limpar a pilha e abrir o diplink sobre o conteúdo que temos. Ou, como opção, você pode simplesmente remover o pop-up true nas opções de navegação. Em teoria, tudo deve funcionar também.

E, finalmente, uma versão estável sai. Ficamos contentes, pensamos que estava tudo bem, mas na versão estável, o comportamento, em geral, não mudou. Eles acabaram de finalizar. Finalmente conseguimos argumentos de segurança funcionando, então começamos a adicionar ativamente argumentos em nossas telas e usá-los em qualquer lugar do código. Também descobrimos que a navegação não funciona com DialogFragments. Como tínhamos DialogFragments, queríamos transferir todos eles para um gráfico, em XML, e descrever as transições entre eles. Mas não tivemos sucesso.



Abertura dupla. Também tivemos um problema que nos assombrou desde a primeira versão - a dupla abertura do Activity durante um início a frio do aplicativo.



Isso acontece da seguinte maneira. Existe um método maravilhoso de lidar com deeplink que podemos chamar de nosso código, por exemplo, quando em atividade entramos em newIntent () para interceptar um link de profundidade. Aqui, o sinalizador ACTIVITY_NEW_TASK chega ao Intent quando o aplicativo é iniciado usando o deeplink, para que ocorra o seguinte: uma nova atividade é iniciada e, se houver uma atividade atual, ela é eliminada. Portanto, se iniciarmos o aplicativo, a tela branca iniciará primeiro, depois desaparecerá, outra tela aparecerá e elas ficarão muito bonitas.

Como resultado, ao apresentar esta biblioteca, obtivemos as seguintes vantagens.



Temos documentação para nossa navegação, além de uma representação gráfica das transições entre telas, e se uma pessoa nos procura em um projeto, ela rapidamente entende isso olhando para o gráfico, abrindo sua apresentação no Studio.

Temos SingleActivity. Todas as telas são feitas em fragmentos, todos os diplinks levam a fragmentos, e acho que isso é conveniente.

O resultado foi uma simples vinculação do deeplink com fragmentos, basta adicionar a tag deeplink ao fragmento e a biblioteca faz tudo por nós. Também dividimos nossa navegação em subgráficos aninhados, fizemos navegação aninhada. Essas são coisas diferentes. Apenas um gráfico aninhado, de fato, é incluído dentro do gráfico e a navegação aninhada ocorre quando um navegador separado é usado para percorrer as telas.

Também alteramos o gráfico dinamicamente no código, podemos adicionar vértices, podemos excluir vértices, podemos mudar a tela inicial - tudo funciona.

Quase esquecemos como trabalhar com o FragmentManager, já que toda a lógica de trabalhar com ele é encapsulada dentro da biblioteca, e a biblioteca faz toda a mágica para nós. A biblioteca também funciona com o DrawerLayout, se você tiver um fragmento raiz definido, a própria biblioteca desenhará um hambúrguer e, ao passar para as próximas telas, desenhará uma seta e fará isso de maneira animada ao retornar do penúltimo fragmento.

Também transferimos todos os argumentos, a maioria deles, para o SafeArgs, e tudo é criado quando criamos o projeto. Além disso, lidamos com todos os problemas que nos atormentavam e modificamos a biblioteca para atender às nossas necessidades.

O SafeArgs pode gerar código Kotlin, um plugin separado é usado para isso.



Além disso, a biblioteca tem desvantagens. A primeira é que a limpeza da pilha foi entregue na versão estável. Não sei por que isso foi feito, talvez seja tão conveniente para alguém, mas no caso de nosso aplicativo, gostaríamos de abrir links de profundidade sobre o conteúdo que temos.

Os próprios fragmentos são criados pelo fragmento do navegador e são criados através da reflexão. Eu não acho que isso seja uma vantagem na implementação. A condição para deeplink não é suportada. Seu aplicativo pode ter telas secretas disponíveis apenas para usuários autorizados. E, para abrir os deepplinks por condição, é necessário escrever muletas para isso, pois não é possível definir um processador de deepplink no qual teríamos controle e dizer o que fazer, abra a tela pelo deeplink ou abra outra tela.

Além disso, os comandos de transição são perdidos, tudo porque o fragmento do navegador verifica o estado se está salvo ou não, e se está salvo, simplesmente não fazemos nada.

Também tivemos que modificar a biblioteca com um arquivo, isso não é uma vantagem. E outra desvantagem significativa - não temos a oportunidade de abrir uma cadeia de telas em frente ao deepplink. No caso de nossa aplicação, gostaríamos de fazer um link profundo para a cesta, antes da qual abrimos todas as telas anteriores: restaurantes, um restaurante específico e somente depois a cesta.

Além disso, para trabalhar com a navegação, você deve ter uma instância View. Para obter um navegador, você precisa acessar a Visualização - a própria biblioteca de navegação entrará em contato com os pais dessa Visualização - e tentar encontrar o navegador nela. Se ela encontrar, a tela vai para a tela que precisamos.

Mas surge a pergunta: vale a pena usar a biblioteca em batalha? Eu direi que sim se:



Se precisamos obter um resultado rápido. A biblioteca é implementada muito rapidamente, pode ser inserida no projeto em cerca de 20 minutos, todas as transições são pressionadas com o mouse, tudo isso é conveniente. Também é escrito rapidamente em XML, rapidamente montado e funciona rapidamente. Tudo é super.

Se você precisar ter muitas telas no aplicativo que funcionem com links de profundidade, e não há condições sob as quais elas devem ser abertas.

Nenhuma atividade única. Aqui, quero dizer não apenas uma atividade. Podemos navegar nos fragmentos com uma atividade e em uma mistura de fragmentos e atividade, apenas no gráfico você também pode descrever a atividade. Para isso, um provedor é usado dentro do navegador, no qual existem duas implementações de navegação. Uma na Activity, que cria as Intents para ir para as Atividades desejadas, a segunda implementação é um navegador de fragmentos que trabalha internamente com um gerenciador de fragmentos.

Se você não possui uma lógica complexa para alternar entre telas. Cada aplicativo é único à sua maneira e, se houver uma lógica complexa para abrir telas, essa biblioteca não é para você.

Você está pronto para entrar na fonte e modificá-la como nós. Isso é realmente muito interessante.



Eu direi não se o aplicativo tiver lógica de navegação complicada, se você precisar abrir uma cadeia de telas antes do deepplink, se precisar de condições difíceis para abrir as telas através do deepplink. Em princípio, com o exemplo de autorização, isso pode ser feito, a tela que você precisa simplesmente se abre e, além disso, é a tela de autorização. Se o usuário não efetuar login, você será despejado na pilha na tela anterior, antes da qual a tela secreta foi aberta. Tal decisão de muleta.

Perder equipes é fundamental para você. O mesmo Cicerone pode salvar comandos no buffer e, quando o navegador se torna disponível, ele os executa.

Se você não deseja concluir o código, isso não significa que você, como desenvolvedor, não deseja fazer algo. Suponha que você tenha um prazo, os gerentes de produto lhe dizem que você precisa cortar recursos, uma empresa deseja implementar recursos. Você precisa de uma solução pronta para uso que funcione imediatamente. Componentes de navegação não é sobre esse caso.

Mais uma coisa crítica - os DialogFragments não são suportados. Um navegador que trabalharia com eles poderia ser adicionado, mas por algum motivo eles não foram adicionados. O problema é que eles mesmos se abrem como fragmentos comuns. A caixa de seleção isShowing não está definida e, portanto, o ciclo de vida de DialogFragments para criar uma caixa de diálogo não é executado. De fato, DialogFragment abre como um fragmento regular, a tela inteira sem criar uma janela.

Isso é tudo para mim. Cavar a fonte, isso é realmente interessante e emocionante. Aqui estão materiais úteis para quem estiver interessado em trabalhar com a biblioteca de navegação. Obrigado pela atenção.

Source: https://habr.com/ru/post/pt455479/


All Articles