Android: Creating Dynamic Product Flavors and Signing Configs
When working on an Android project, which is a
platform for creating applications for viewing video content, it is became
necessary to dynamically configure product flavors with the transfer of
information about signing configs to an external file.
Initial data
There is an
Android project, which is a platform for creating applications for viewing
video content. The code base is common for all applications, the differences
are in the settings of the REST API parameters and the settings for the
appearance of the application (banners, colors, fonts, etc.). Three flavor
dimension were used in the project:
- market: google or amazon. Because
applications are distributed both on Google Play and in the Amazon Marketplace,
there is a need to share some functionality depending on the distribution
place. For example: Amazon prohibits the use of In-App Purchases mechanism from
Google and requires the implementation of its mechanism. - endpoint: “pro” or
“staging”. Specific configurations for production and staging
versions. - site: the actual dimension for a particular
application. Set applicationId and signingConfig.
The problems we encountered
When
creating a new application, it was necessary to add Product Flavor:
application1 { dimension 'site' applicationId 'com.damsols.application1' signingConfig signingConfigs.application1 }
Also, it was
necessary to add the appropriate Signing Config:
application1 { storeFile file("path_to_keystore1.jks") storePassword "password1" keyAlias "application1" keyPassword "password1" }
Problems:
- five lines to add one application, differing only in
applicationId and signingConfig. When the number of applications became more
than 50, the build.gradle file began to contain more than 500 lines of
application information. - storage in plain-text information about keystore for
signing applicat
Removing Certificate
Information
The first step was to transfer the certificate
information to a separate json file. For example, information is also stored in
plain-text, but nothing prevents you from storing the file in encrypted form
(we use GPG) and decrypting it directly during application build. The JSON file
has the following structure:
{ "signingConfigs":[ { "configName":"application1", "storeFile":"application1.jks", "storePassword":"password1", "keyAlias":"application1", "keyPassword":"password1" }, { "configName":"application2", "storeFile":"application2.jks", "storePassword":"password2", "keyAlias":"application2", "keyPassword":"password2" }, { "configName":"application3", "storeFile":"application3.jks", "storePassword":"password3", "keyAlias":"application3", "keyPassword":"password3" }, ] }
Simplify Product Flavors
Sections
To reduce the number of lines required to
describe Product Flavor with dimension = “site”, an array was created
with the necessary information to describe a specific application, and all
Product Flavors with dimension = “site” were deleted.
It was
… productFlavors { pro { dimension 'endpoint' } staging { dimension 'endpoint' } google { dimension 'market' } amazon { dimension 'market' } application1 { dimension 'site' applicationId "com.damsols.application1" signingConfig signingConfigs.application1 } application2 { dimension 'site' applicationId "com.damsols.application2" signingConfig signingConfigs.application2 } application3 { dimension 'site' applicationId "com.damsols.application3" signingConfig signingConfigs.application3 } } } ...
It became
... productFlavors { pro { dimension 'endpoint' } staging { dimension 'endpoint' } google { dimension 'market' } amazon { dimension 'market' } } def applicationDefinitions = [ ['name': 'application1', 'applicationId': 'com.damsols.application1'], ['name': 'application2', 'applicationId': 'com.damsols.application2'], ['name': 'application3', 'applicationId': 'com.damsols.application3'] ] } ...
Dynamic creation of
Product Flavors
The last step was to dynamically create product
flavors and signing configs using an external JSON file with certificate
information from the applicationDefinitions array.
def applicationDefinitions = [ ['name': 'application1', 'applicationId': 'com.damsols.application1'], ['name': 'application2', 'applicationId': 'com.damsols.application2'], ['name': 'application3', 'applicationId': 'com.damsols.application3'] ] def signKeysFile = file('signkeys/signkeys.json') def signKeys = new JsonSlurper().parseText(signKeysFile.text) def configs = signKeys.signingConfigs def signingConfigsMap = [:] configs.each { config -> signingConfigsMap[config.configName] = config } applicationDefinitions.each { applicationDefinition -> def signingConfig = signingConfigsMap[applicationDefinition['name']] android.productFlavors.create(applicationDefinition['name'], { flavor -> flavor.dimension = 'site' flavor.applicationId = applicationDefinition['applicationId'] flavor.signingConfig = android.signingConfigs.create(applicationDefinition['name']) flavor.signingConfig.storeFile = file(signingConfig.storeFile) flavor.signingConfig.storePassword = signingConfig.storePassword flavor.signingConfig.keyAlias = signingConfig.keyAlias flavor.signingConfig.keyPassword = signingConfig.keyPassword }) }
To add
reading from encrypted storage, you need to replace the section
def signKeysFile = file('signkeys/signkeys.json') def signKeys = new JsonSlurper().parseText(signKeysFile.text) def configs = signKeys.signingConfigs
to read from an encrypted file.
Related Posts
1 Comment
Leave a Reply Cancel reply
Service
Categories
- DEVELOPMENT (104)
- DEVOPS (53)
- FRAMEWORKS (27)
- IT (25)
- QA (14)
- SECURITY (14)
- SOFTWARE (13)
- UI/UX (6)
- Uncategorized (8)
Wow, fantastic blog layout! How long have you been blogging for?
you make blogging look easy. The overall look of your web site is
excellent, as well as the content!