Como fazer dois aplicativos de um. Experiência Júnior Tinkoff

Olá, meu nome é Andrey e estou trabalhando nos aplicativos Tinkoff e Tinkoff Junior para a plataforma Android. Quero falar sobre como coletamos dois aplicativos semelhantes em uma base de código.


— , ̆ 14 . , (, ), , , (, ).


.


No início do projeto, consideramos várias opções para sua implementação e tomamos várias decisões. Imediatamente ficou claro que os dois aplicativos (Tinkoff e Tinkoff Junior) teriam uma parte significativa do código comum. Não queríamos usar o aplicativo antigo e copiar as correções de bugs e a nova funcionalidade comum. Para trabalhar com duas aplicações ao mesmo tempo, consideramos três opções: Gradle Flavors, Git Submodules, Gradle Modules.


Sabores Gradle


Muitos de nossos desenvolvedores já tentaram usar Flavors, além de podermos usar flavours multidimensionais para usar com flavours existentes.
No entanto, os sabores têm uma falha fatal. O Android Studio considera o código apenas o código do sabor ativo - ou seja, o que está na pasta principal e na pasta de sabor. O restante do código é considerado texto junto com os comentários. Isso impõe restrições a algumas ferramentas do estúdio: pesquisa de uso de código, refatoração e outras.


Sub-módulos Git


Outra opção para implementar nossa idéia é usar os submódulos do git: transfira o código comum para um repositório separado e conecte-o como um submódulo a dois repositórios com o código para uma aplicação específica.


Essa abordagem aumenta a complexidade de trabalhar com o código fonte do projeto. Além disso, os desenvolvedores ainda teriam que trabalhar com os três repositórios para fazer edições ao alterar a API do módulo comum.


Arquitetura multi-módulo


A opção final é mudar para a arquitetura de vários módulos. Essa abordagem está livre das desvantagens que os outros dois têm. No entanto, a transição para uma arquitetura de vários módulos requer refatoração demorada.


No momento em que começamos a trabalhar no Tinkoff Junior, tínhamos dois módulos: um pequeno módulo de API que descreve como trabalhar com o servidor e um grande módulo de aplicativo monolítico, no qual a maior parte do código do projeto estava concentrada.


desenhodesenho
Como resultado, queríamos obter dois módulos de aplicativos: adulto e júnior e algum módulo principal comum. Identificamos duas opções:


  • Colocando código comum no módulo comum comum. Essa abordagem é "mais correta", mas leva mais tempo. Estimamos os volumes de reutilização de código em aproximadamente 80%.
    desenho
  • Converta o módulo do aplicativo em uma biblioteca e conecte-a aos módulos adulto e júnior thin. Essa opção é mais rápida, mas trará código ao Tinkoff Junior que nunca será executado.
    desenho

Tínhamos tempo de reserva e decidimos iniciar o desenvolvimento de acordo com a primeira opção (o módulo comum ) com a condição de mudar para a opção rápida quando o tempo de refatoração se esgotar.
No final, isso aconteceu: transferimos parte do projeto para o módulo comum e, em seguida, transformamos o módulo de aplicativo restante em uma biblioteca. Como resultado, agora temos a seguinte estrutura de projeto:


desenho

Temos módulos com recursos, o que nos permite distinguir entre um código "adulto", geral ou "infantil". No entanto, o módulo do aplicativo ainda é grande o suficiente e agora cerca de metade do projeto está armazenada lá.


Transformando o aplicativo em uma biblioteca


A documentação possui instruções simples para transformar um aplicativo em uma biblioteca. Ele contém quatro pontos simples e, ao que parece, nenhuma dificuldade deve ser:


  1. Abra o arquivo build.gradle módulo
  2. Remova applicationId da configuração do módulo
  3. No início do arquivo, substitua o apply plugin: 'com.android.application' por apply plugin: 'com.android.library'
  4. Salve as alterações e sincronize o projeto no Android Studio ( Arquivo> Sincronizar projeto com arquivos Gradle )

No entanto, a conversão levou vários dias e o diff resultante ficou assim:


  • 183 arquivos alterados
  • 1601 inserções (+)
  • Exclusões de 1920 (-)

O que deu errado?

Primeiro de tudo, nas bibliotecas, os identificadores de recursos não são constantes . Nas bibliotecas, como nos aplicativos, um arquivo R.java é gerado com uma lista de identificadores de recursos. E nas bibliotecas, os valores do identificador não são constantes. Java não permite ativar valores não constantes e todos os comutadores devem ser substituídos por if-else.


 // Application int id = view.getId(); switch(id) { case R.id.button1: action1(); break; case R.id.button2: action2(); break; } // Library int id = view.getId(); if (id == R.id.button1) { action1(); } else if (id == R.id.button2) { action2(); } 

Em seguida, encontramos uma colisão de pacotes.
Suponha que você tenha uma biblioteca com package = com.example e o aplicativo com package = com.example.app depende dessa biblioteca. Em seguida, a classe com.example.R será gerada na biblioteca e com.example.app.R , respectivamente , no aplicativo. Agora vamos criar a atividade com.example.MainActivity no aplicativo, na qual tentaremos acessar a classe R. Sem importação explícita, a biblioteca usará a classe R, que não especifica os recursos do aplicativo, mas apenas os recursos da biblioteca. No entanto, o Android Studio não destaca o erro e, quando você tenta mudar de código para um recurso, tudo ficará bem.


Adaga


Usamos o Dagger como uma estrutura para injeção de dependência.
Em cada módulo que contém atividades, fragmentos e serviços, temos interfaces regulares que descrevem métodos de injeção para essas entidades. Nos módulos de aplicação ( adulto e junor ), as interfaces do componente punhal herdam dessas interfaces. Nos módulos, trazemos os componentes para as interfaces necessárias para este módulo.


Multibindings


O desenvolvimento do nosso projeto é bastante simplificado pelo uso de multibindings.
Em um dos módulos comuns, definimos uma interface. Em cada módulo de aplicação ( adulto , júnior ), descrevemos a implementação dessa interface. Usando a anotação @Binds , @Binds ao punhal que toda vez em vez de uma interface é necessário injetar sua implementação específica para um aplicativo infantil ou adulto. Também coletamos frequentemente uma coleção de implementações de interface (Conjunto ou Mapa), e essas implementações são descritas em diferentes módulos de aplicativo.


Sabores


Para propósitos diferentes, coletamos várias opções de aplicativos. Os sabores descritos no módulo base também devem ser descritos nos módulos dependentes. Além disso, para o Android Studio funcionar corretamente, é necessário que opções de montagem compatíveis sejam selecionadas em todos os módulos do projeto.


Conclusões


Em pouco tempo, implementamos um novo aplicativo. Agora, enviamos a nova funcionalidade em dois aplicativos, escrevendo-a uma vez.


Ao mesmo tempo, passamos um tempo refatorando, reduzindo simultaneamente a dívida técnica e mudamos para uma arquitetura de vários módulos. No caminho, encontramos restrições do Android SDK e do Android Studio, que gerenciamos com sucesso.

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


All Articles