Como parte de nosso trabalho para modernizar as orientações sobre arquitetura de apps, queremos experimentar diferentes padrões de IU para ver o que funciona melhor, encontrar semelhanças e diferenças entre as alternativas e consolidar nossas descobertas como práticas recomendadas.
Para facilitar ao máximo a aplicação de nossas descobertas, precisávamos de uma amostra com um caso de negócios familiar e que não fosse muito complicada. E quem é que não conhece os apps TODO ? Escolhemos o Architecture Blueprints! O Blueprints sempre serviu como um playground experimental para escolhas de arquitetura. E ele é ótimo para isso!
Os padrões com os quais queremos fazer experiências são claramente afetados pelas diferentes APIs disponíveis atualmente. E a novidade são as APIs de estado do Jetpack Compose! Como o Compose funciona perfeitamente com qualquer padrão de fluxo de dados unidirecional , vamos utilizá-lo para renderizar a IU e fazer uma comparação justa.
Esta postagem do blog conta como a equipe migrou o Architecture Blueprints para o Jetpack Compose. Como o LiveData também é considerado uma alternativa em nossos experimentos, deixamos a amostra no estado em que se encontrava no momento da migração. Nessa refatoração, as classes ViewModel e a camada de dados permaneceram inalteradas.
⚠️ A arquitetura usada nesta base de código baseada no LiveData não segue totalmente as práticas recomendadas de arquitetura mais recentes. Em particular, o LiveData não deve ser usado nas camadas de dados ou de domínios. Devem-se usar fluxos e corrotinas.
Agora que o contexto já ficou claro, vamos detalhar como abordamos a refatoração do Blueprints para o Jetpack Compose. Confira o código completo na ramificação dev-compose.
Antes de realizar qualquer codificação real, a equipe criou um plano de migração para garantir que todos concordassem com as mudanças propostas. O objetivo final era que o Blueprints fosse um aplicativo de atividade única, com telas como funções que podem ser compostas e usando a biblioteca Compose Navigation recomendada para a movimentação entre as telas.
Felizmente, o Blueprints já era um app de atividade única que usava o Jetpack Navigation para a movimentação entre diferentes telas implementadas com fragmentos. Para migrar para o Compose, seguimos as orientações de interoperabilidade do Navigation, que recomendam que os apps híbridos usem o componente Navigation baseado em fragmentos para conter telas baseadas em visualização, telas do Compose e telas que usam tanto as visualizações quanto o Compose. Infelizmente, não é possível combinar os destinos dos fragmentos e do Compose no mesmo gráfico do Navigation.
O objetivo de uma migração gradual é facilitar as revisões de código e manter um produto em condições de entrega ao longo de toda a migração. O plano de migração envolvia três passos:
E foi isso o que fizemos! 🧑💻 Depois ⏩ de duas semanas, migramos as telas Statistics (PR), Add/Edit task (PR), Task detail (PR) e Tasks (PR) e mesclamos a PR final que migrou a lógica Activity e Navigation para o Compose, incluindo a remoção das dependências do sistema de visualização não utilizadas.
Durante a migração, encontramos algumas peculiaridades específicas do Compose que valem destaque:
Assim que você começa a adicionar o Compose ao app, os testes que declaram IUs do Compose precisam usar APIs de teste do Compose.
Para os testes de IU no nível de tela, em vez de usar a API launchFragmentInContainer<FragmentType>, usamos a API createAndroidComposeRule<ComponentActivity>, que permite capturar recursos de strings nos testes. Esses testes são executados no Espresso e no Robolectric. Como o Compose já tem suporte para tudo isso, nenhuma mudança adicional foi necessária. Por exemplo, é possível comparar o código em AddEditTaskFragmentTest que foi migrado para AddEditTaskScreenTest. Observe que, se você usar o ComponentActivity, precisará contar com o artefato androidx.compose.ui:ui-test-manifest.
Nos testes completos ou de integração, também não tivemos nenhum problema. Graças à interoperabilidade do Espresso e do Compose, usamos as declarações do Espresso para verificar as visualizações e as APIs do Compose para verificar a IU do Compose. Aqui, podemos ver a aparência de AppNavigationTest em um ponto durante a migração para o Compose.
Tivemos problemas com o modo como os eventos do ViewModel eram manipulados no Blueprints. O Blueprints implementava uma solução de wrapper de evento para enviar comandos do ViewModel para a IU. No entanto, isso não funciona no Compose. Nossas orientações recentes recomendam a modelagem desses "eventos" como estado, e foi isso o que fizemos durante a migração.
Se observarmos o caso de uso do evento de exibição de mensagens na tela , substituímos o tipo Event<Int>do LiveData por "Int?". Isso também modela o cenário no qual não há mensagens a serem exibidas para o usuário. Nesse caso de uso em particular, o ViewModel também exige uma confirmação da IU sempre que a mensagem é exibida. Veja a diferença entre as duas implementações no seguinte código:
Embora isso possa parecer um trabalho adicional, à primeira vista, é uma garantia de que a mensagem seja exibida na tela.
No código da IU, a forma de assegurar que o evento seja manipulado apenas uma vez é fazer uma chamada para event.getContentIfNotHandled(). Essa abordagem funciona mais ou menos nos fragmentos, mas falha totalmente no Compose. Como as recomposições podem ocorrer a qualquer momento no Compose, o wrapper de evento não é uma solução válida. Se o evento for processado e a função for recomposta (algo que aconteceu bastante durante o teste dessa abordagem), o snackbar será cancelado, e o usuário poderá perder a mensagem. Esse é um problema de UX inaceitável! A solução wrapper de evento não deve ser usada em apps do Compose.
Veja o snippet de código a seguir com o antes (wrapper de evento) e o depois (evento como estado) do código. Como a exibição de mensagens na tela envolve a lógica de IU e as funções de tela que podem ser compostas estavam se tornando mais complexas, usamos uma classe detentora de estado simples para gerenciar essa complexidade (veja, por exemplo, AddEditTaskState).
Durante a refatoração, pode ser tentador migrar tudo para o Compose. Embora não haja nenhum problema nisso, você não deve sacrificar a experiência do usuário nem a precisão do app. A finalidade de fazer uma migração gradual é que o app esteja sempre em condições de entrega.
Isso aconteceu conosco durante a migração de algumas telas para o Compose. Não queríamos fazer um monte de migrações ao mesmo tempo, então migramos algumas das telas para o Compose antes da migração do wrapper de evento. Em vez de manipular o wrapper de evento no Compose e entregar uma experiência insatisfatória, continuamos manipulando essas mensagens no fragmento, enquanto o restante do código da tela estava no Compose. Veja, por exemplo, o estado de TasksFragment durante a migração.
Nem tudo correu tão bem quanto parecia. 🫤 Embora a conversão de conteúdo de fragmentos para o Compose seja direta, a migração dos fragmentos do Navigation para o Navigation Compose exigiu um pouco mais de tempo e raciocínio.
É necessário expandir e melhorar as orientações quanto a diferentes aspectos que tornarão a migração para o Compose mais simples no futuro. Esse trabalho provocou várias conversas, e esperamos ter novas orientações sobre isso em breve! 🎊
Por ser iniciante em Navigation ✋ e a pessoa que lidou com a migração para o Navigation Compose, enfrentei os seguintes desafios:
Em geral, a migração de fragmentos do Navigation para o Navigation Compose foi bem divertida! O mais engraçado é que gastamos mais tempo aguardando revisões de pares do que fazendo a migração do projeto em si! Criar o plano de migração e sincronizar todo mundo com certeza ajudou a definir as expectativas logo no começo e alertar os pares sobre futuras revisões demoradas.
Esperamos que você tenha gostado de ler sobre nossa abordagem da migração para o Compose, e queremos compartilhar em breve mais informações sobre os experimentos e as melhorias que faremos no Architecture Blueprints.
Caso tenha interesse em ver o Blueprints com o código do Compose, confira a ramificação dev-compose. E, caso deseje ver todas as PRs da migração gradual, esta é a lista:
One of the things I love about house of hazards is the surprise factor. You never know who's going to stumble next!
Postar um comentário
Um comentário :
One of the things I love about house of hazards is the surprise factor. You never know who's going to stumble next!
Postar um comentário