Prefácio
Desde 2012 distante, nos espaços abertos de Habr, lembro-me do comentário:
... se não houver nenhum caso desde o início, o dispositivo permanecerá
inacabado, vai cair poeira em uma prateleira ...
O tópico está longe de ser um componente de hardware. Analisando meu problema, convenci-me da correção desse julgamento e tentei colocar as coisas em ordem na minha prateleira empoeirada.
Recentemente, fui abordado por um cliente que me pediu para adicionar suporte para vários serviços em seu projeto. A tarefa era que eu precisava conectar o serviço "A" e, antes de colocar o aplicativo em produção, executar esse serviço em um ambiente de teste. Decidi analisar minhas decisões anteriores e ... fiquei horrorizado.
Para vários tipos de montagens, usei vários arquivos de configuração com a descrição de variáveis de ambiente. Mas o problema era que apenas para encaminhar o valor para o código real, era necessário escrever o mesmo código para cada tipo.
O problema
O Google nos permite encaminhar valores personalizados para cada
montagem .
android { //... buildTypes { release { buildConfigField("String", "HOST_URL", "\"prod.com\"") } debug { buildConfigField("String", "HOST_URL", "\"debug.com\"") } } }
Após analisar o script build.gradle , as ferramentas do Android obterão todos os valores de buildConfigFileds de buildTypes e productFlavors e gerarão arquivos BuildConfig para cada tipo de montagem:
public final class BuildConfig {
Não há problema à primeira vista. Especialmente quando não existem muitos sabores e valores personalizados em seu aplicativo. No meu projeto, havia> 20 e 3 ambientes (interno / alfa / produção). Obviamente, havia apenas um problema para mim - livrar-me do clichê.
Um problema igualmente importante é que os valores das variáveis de ambiente não devem ser refletidos no seu projeto. Mesmo no arquivo de configuração. Você deve verificar sua configuração do build.gradle via VCS. Mas você não deve registrar suas chaves diretamente, para isso, você precisa de um mecanismo de terceiros (por exemplo, um arquivo, os serviços do seu IC). Na minha prática, havia vários projetos nos quais, para liberar a montagem de produção, não tinha acesso aos valores de algumas bibliotecas. Isso já é um problema comercial e é do seu interesse não gerar custos desnecessários. Você não deve usar chaves destinadas à produção durante a depuração ou teste interno.
A maneira de resolver o problema
Em um dos projetos antigos, para armazenar os valores das variáveis de ambiente, usamos arquivos .properties simples que forneciam acesso aos campos por meio da chave clássica: mapa de valores. Essa abordagem não resolve o problema de ligação. Mas resolve o problema da entrega de dados, que deve ser aplicado. Além disso, podemos usar arquivos .properties como base para um certo tipo de contrato de fornecimento de dados.
Se voltarmos um pouco, temos uma etapa intermediária: do buildConfigField ao campo da classe BuildConfig . Mas quem faz isso? Tudo é bastante brega, o plugin gradle que você conecta absolutamente em todos os projetos do Android é responsável por isso.
apply plugin: "com.android.application"
É ele quem é responsável pelo fato de que, após analisar seu arquivo build.gradle , a classe BuildConfig será gerada para cada tipo com seu próprio conjunto de campos. Dessa forma, posso escrever meu próprio medicamento que expandirá os recursos do com.android.application e salvará
me desta dor de cabeça.
A solução para o problema é a seguinte: forneça um contrato,
que descreverá todas as chaves e valores para todas as montagens.
Expanda os arquivos de configuração em subtipos. Dê tudo ao plugin.

Solução
Acima, descobrimos a estrutura da solução, a única coisa que resta a fazer é trazer tudo à vida. Parece que uma solução trivial e um problema podem ser resolvidos com uma extensão de arquivo de compilação simples. Inicialmente, eu fiz isso.
Revelar solução ```groovy class Constants { // Environments properties path pattern, store your config files in each folders of pattern static final CONFIG_PROPERTY_PATTERN = "config/%s/config.properties" } android.buildTypes.all { buildType -> buildConfigFields(buildType, buildType.name) } android.applicationVariants.all { appVariant -> buildConfigFields(appVariant, appVariant.flavorName) } private def buildConfigFields(Object variant, String variantName) { def properties = getProperties(variantName) properties.each { key, value -> variant.buildConfigField( parseValueType(value), toConfigKey(key), value ) } } // Convert config property key to java constant style private def toConfigKey(String key) { return key.replaceAll("(\\.)|(-)", "_") .toUpperCase() } // Parse configuration value type private def parseValueType(String value) { if (value == null) { throw new NullPointerException("Missing configuration value") } if (value =~ "[0-9]*L" ) { return "Long" } if (value.isInteger()) { return "Integer" } if (value.isFloat()) { return "Float" } if ("true" == value.toLowerCase() || "false" == value.toLowerCase()) { return "Boolean" } return "String" } private def getProperties(String variantName) { def propertiesPath = String.format( Constants.CONFIG_PROPERTY_PATTERN, variantName ) def propertiesFile = rootProject.file(propertiesPath) def properties = new Properties() // Do nothing, when configuration file doesn't exists if (propertiesFile.exists()) { properties.load(new FileInputStream(propertiesFile)) } return properties } ```
E aqui imediatamente surgiram essas dificuldades nas quais eu não havia pensado - um regimento empoeirado. Decidi "vender" minha decisão a meus colegas. Eu preparei uma doca, lancei o assunto para discussão e ... percebi que somos todos pessoas, e programadores são pessoas preguiçosas. Ninguém quer incorporar um pedaço de código desconhecido para ele no projeto, ele precisa ser estudado, lido para sempre? E se ele não estiver trabalhando? E se ele estiver fazendo outra coisa errada? Isso é legal, mas eu não o conheço e não está claro como trabalhar com ele. E um script já foi transferido para o Kotlin por um longo tempo, e eu não consigo sair de canais, e assim por diante.
O mais interessante é que todos esses julgamentos já vieram de mim, porque Percebi que essa integração da solução não combina comigo. Além disso, notei alguns pontos que realmente gostaria de melhorar. Depois de implementar a solução no projeto A, eu gostaria de apoiá-la no projeto B. Há apenas uma saída: você precisa escrever um plug-in.
E que problemas o plug-in e sua entrega remota ao usuário resolverão?
- o problema de um programador preguiçoso. Estamos com preguiça de nos aprofundarmos na raiz do problema e em possíveis maneiras de resolvê-lo. É muito mais fácil pegar algo que já foi feito antes de você e usá-lo.
- suporte. Inclui suporte ao código, seu desenvolvimento e expansão de oportunidades. Ao resolver meu problema, resolvi apenas os ambientes probrosvarivnyh apenas no código, esquecendo completamente a possibilidade de encaminhamento para recursos.
- qualidade do código. Há uma opinião de que alguns desenvolvedores nem olham para o código-fonte aberto que não é coberto por testes. É 2019 e podemos conectar serviços facilmente para rastrear a qualidade do código https://sonarcloud.io ou https://codecov.io/
- configuração A extensão do arquivo de compilação me obriga a examinar esse código e fazer alterações manualmente. No meu caso, nem sempre preciso usar a configuração para buildTypes ou productFlavors , quero uma coisa ou todas de uma vez.
- limpeza de prateleira empoeirada. Finalmente limpei um deles e fui capaz de aprofundar essa decisão do meu quartinho.
Não vou entrar em detalhes e problemas ao escrever o plug-in, ele está puxando um novo tópico. Você pode tomar uma decisão com sua integração, propor sua ideia ou dar sua contribuição
aqui