Préface
De loin en 2012, dans les grands espaces de Habr, je me souviens du commentaire:
... s'il n'y a pas de cas depuis le tout début, alors l'appareil restera
inachevé, tombera de la poussière sur une étagère ...
Le sujet est loin d'être un composant matériel. Analysant mon problème, je suis devenu convaincu de l'exactitude de ce jugement et j'ai essayé de mettre les choses en ordre sur mon étagère poussiéreuse.
Récemment, j'ai été approché par un client qui m'a demandé d'ajouter un support pour plusieurs services à son projet. La tâche était que je devais connecter le service «A» et, avant de mettre l'application en production, exécuter ce service sur un environnement de test. J'ai décidé d'analyser mes décisions précédentes et ... j'ai été horrifié.
Pour différents types d'assemblages, j'ai utilisé divers fichiers de configuration avec la description des variables d'environnement. Mais le problème était que juste pour transmettre la valeur dans le vrai code, il était nécessaire d'écrire le même code pour chaque type.
Le problème
Google nous donne la possibilité de transmettre des valeurs personnalisées pour chaque
assemblage .
android { //... buildTypes { release { buildConfigField("String", "HOST_URL", "\"prod.com\"") } debug { buildConfigField("String", "HOST_URL", "\"debug.com\"") } } }
Après avoir analysé le script build.gradle , les outils Android prendront toutes les valeurs de buildConfigFileds des buildTypes et productFlavors et généreront des fichiers BuildConfig pour chaque type d'assemblage:
public final class BuildConfig {
Aucun problème à première vue. Surtout quand il n'y a pas autant de saveurs et de valeurs personnalisées dans votre application. Dans mon projet, il y avait> 20 et 3 environnements (interne / alpha / production). De toute évidence, il n'y avait qu'un seul problème pour moi - se débarrasser du passe-partout.
Un problème tout aussi important est que les valeurs des variables d'environnement ne doivent pas être reflétées dans votre projet. Même dans le fichier de configuration. Vous devez vérifier votre configuration build.gradle via VCS. Mais vous ne devez pas enregistrer vos clés directement, pour cela, vous avez besoin d'un mécanisme tiers (par exemple, un fichier, les services de votre CI). Dans ma pratique, il y avait plusieurs projets pour lesquels pour l'assemblage de production de versions, je n'avais pas accès aux valeurs de certaines bibliothèques. C'est déjà un problème commercial et dans son intérêt de ne pas faire de coûts inutiles. Vous ne devez pas utiliser de clés destinées à la production lors du débogage ou des tests internes.
La façon de résoudre le problème
Dans l'un des anciens projets, pour stocker les valeurs des variables d'environnement, nous avons utilisé des fichiers .properties simples qui permettaient d'accéder aux champs via la clé classique: mappage de valeurs. Cette approche ne résout pas le problème de liaison. Mais cela résout le problème de la livraison des données, qui devrait être appliqué. De plus, nous pouvons prendre des fichiers .properties comme base pour un certain type de contrat de fourniture de données.
Si nous remontons un peu, nous avons une étape intermédiaire: de buildConfigField au champ de la classe BuildConfig . Mais qui fait ça? Tout est assez ringard, le plugin gradle que vous connectez absolument dans tous les projets Android en est responsable.
apply plugin: "com.android.application"
C'est lui qui est responsable du fait qu'après avoir analysé votre fichier build.gradle , la classe BuildConfig sera générée pour chaque saveur avec son propre ensemble de champs. De cette façon, je peux écrire mon propre médicament qui étendra les capacités de com.android.application et économisera
moi de ce mal de tête.
La solution est la suivante: proposer un contrat,
qui décrira toutes les clés et valeurs de tous les assemblys.
Développez les fichiers de configuration en sous-types. Donnez tout au plugin.

Solution
Ci-dessus, nous avons compris la structure de la solution, la seule chose qui reste à faire est de tout donner vie. Il semblerait qu'une solution triviale et un problème peuvent être résolus par une simple extension de fichier de construction. Au départ, je l'ai fait.
Révéler la solution ```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 } ```
Et là surgirent aussitôt ces difficultés auxquelles je n'avais pas pensé - un régiment poussiéreux. J'ai décidé de "vendre" ma décision à mes collègues. J'ai préparé un dock, lancé la discussion et ... J'ai réalisé que nous sommes tous des gens et que les programmeurs sont des gens paresseux. Personne ne veut intégrer un morceau de code qui lui est inconnu dans le projet, faut-il l'étudier, le lire pour de bon? Et s'il ne travaille pas? Et s'il fait autre chose de mal? C'est groovy, mais je ne le connais pas et on ne sait pas comment travailler avec lui. Et un script s'est déjà déplacé sur Kotlin depuis longtemps, et je ne peux pas faire de portage depuis des grooves, et ainsi de suite.
La chose la plus intéressante est que tous ces jugements sont déjà venus de moi, car J'ai réalisé qu'une telle intégration de la solution ne me convenait pas. De plus, j'ai remarqué quelques points que j'aimerais vraiment améliorer. Ayant implémenté la solution dans le projet A, je voudrais la soutenir dans le projet B. Il n'y a qu'une seule issue - vous devez écrire un plug-in.
Et quels problèmes le plug-in et sa livraison à distance à l'utilisateur résoudront-ils?
- le problème d'un programmeur paresseux. Nous sommes trop paresseux pour fouiller la racine du problème et les moyens possibles de le résoudre. Il nous est beaucoup plus facile de prendre quelque chose qui a déjà été fait avant vous et de l'utiliser.
- soutien. Il comprend le support du code, son développement et l'expansion des opportunités. Lors de la résolution de mon problème, je n'ai résolu que les environnements probrosvarivnyh uniquement dans le code, oubliant complètement la possibilité de transfert vers les ressources.
- qualité du code. Il y a une opinion que certains développeurs ne regardent même pas le code open source qui n'est pas couvert par les tests. Nous sommes en 2019 et nous pouvons facilement connecter des services pour suivre la qualité du code https://sonarcloud.io ou https://codecov.io/
- configuration. L'extension de fichier de build m'oblige à examiner ce code et à apporter des modifications manuellement. Dans mon cas, je n'ai pas toujours besoin d'utiliser la configuration pour buildTypes ou productFlavors , je veux une chose ou toutes à la fois.
- nettoyage des étagères poussiéreuses. J'ai finalement nettoyé l'un d'eux et j'ai pu faire avancer cette décision de ma chambre.
Je n'entrerai pas dans les détails et les problèmes lors de l'écriture du plugin, il tire sur un nouveau sujet. Vous pouvez prendre une décision avec son intégration, proposer votre idée ou apporter votre contribution
ici .