Gradle using two property files one for debug and release - android

I am trying to migrate an android project from maven to gradle.
In maven I have two profiles (dev and prod), dev profile uses a dev.properties file to set up some properties and prod uses prod.properties.
I want to be able to tell gradle to use dev.properties for debug build and prod.properties for release build.
More specifically all I need to do is rename the file to constants.properties
How can I achieve this?

Assuming you already have a way to distinguish between dev vs prod, you can rename the file in Groovy with something like this:
def dev = true // set to true or false
def propFile
if (dev) {
propFile = file("dev.properties")
} else {
propFile = file("prod.properties")
}
propFile.renameTo("constants.properties")

Related

How to change Android Studio's default build flavor?

I got a project configured with multiple variants and flavors:
buildTypes {
debug {
}
release {
}
}
flavorDimensions "default"
productFlavors {
mock {
}
alpha {
}
beta {
}
prod {
}
}
Whenever I open the project from another one (so starting Android Studio), it selects the mockDebug variant by default. Often I end up build this one first, then realizing I'm on the wrong variant.
Is there a way to tell Android Studio to defaults to one variant, let's say betaDebug?
Technicals: Android Studio 3.1.4, Gradle wrapper 4.4, Android Gradle 3.1.4.
With Android Studio 3.5+ you can set default falvors:
android {
flavorDimensions "stage", "target"
productFlavors {
develop {
getIsDefault().set(true) // that does the magic
dimension "stage"
...
When using KTS it lookes like this:
android {
flavorDimensions("stage", "target")
productFlavors {
create("develop") {
isDefault = true
dimension("stage")
...
Change the order in which you define them in productFlavors. The IDE always loads the first flavor it finds there as the default.
What actually worked for me is enabling "Android Studio Preferences -> Experimental -> Only sync the active variant". It keeps the selected build variant when reopening AS or when re-syncing, basically solving the original problem.
AS/AGP v4.1.

Gradle: How do I define build type for specific flavors only?

I have been using gradle for creating different build variants for different companies for an Android app.
For example I have build flavors:
Company1
Company2
And then I have build types:
Production
Preview
Development
So this will create 6 build variants:
Company1Production
Company1Preview
Company1Development
Company2Production
Company2Preview
Company2Development
So the question is:
Actually I don't need the development build type for company 2, I only need it for company 1.
Is there a way I can specify only company 1 have the development build type?
I have a lot of companies in my projects, some of the build type just don't make sense for those companies, and it confuses people who want to build the app.
To answer my own question, I have found the documentation on the Gradle Plugin User Guide
Filtering Variants
When you add dimensions and flavors, you can end up with variants that don't make sense. For example you may define a flavor that uses your Web API and a flavor that uses hard-coded fake data, for faster testing. The second flavor is only useful for development, but not in release builds. You can remove this variant using the variantFilter closure, like this:
android {
productFlavors {
realData
fakeData
}
variantFilter { variant ->
def names = variant.flavors*.name
if (names.contains("fakeData") && variant.buildType.name == "release") {
variant.ignore = true
}
}
}
With the configuration above, your project will have only three variants:
realDataDebug
realDataRelease
fakeDataDebug
You can't stop the Android plugin from creating the matrix of all builds, but you can cause the build to fail if it's not valid. For example, if you don't want Flavor2 Debug builds to work, you can stop them like this:
afterEvaluate {
tasks['prepareFlavor2DebugDependencies'].doFirst {
throw new IllegalArgumentException("This project is not valid")
}
}

Crashlytics (Fabric) separate organizations for application variants (build types, product flavors)

This is self-answered question to share my knowledge.
I have a project with multiple product flavors and I want to integrate Fabric using separate organizations for each product flavor.
I tried to integrate Fabric using Android Studio Fabric Plugin. It adds
<meta-data
android:name="io.fabric.ApiKey"
android:value="DEFAULT_ORGANIZATION_API_KEY" />
entry to AndroidManifest.xml of main source set.
I decided to rewrite this entry in application variant specific source sets:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<application>
<meta-data
android:name="io.fabric.ApiKey"
android:value="SECOND_ORGANIZATION_API_KEY"
tools:replace="android:value" />
</application>
</manifest>
Then I discovered that Fabric Gradle plugin generates crashlytics.properties file with fabric api secret (AKA build secret) during build and I should include this file to source control. But this file is overwritten each time I build specific application variant because api secret is unique for each application.
How can I integrate Fabric using separate organizations for each application variant?
During the build fabricGenerateResources task is called and it looks for a file named fabric.properties with following content:
apiSecret=YOUR_BUILD_SECRET
apiKey=YOUR_API_KEY
So all we need is to generate fabric.properties file before this.
I found this solution and slightly modified it to fully support application variants not only build types.
Add this code to android section of build.gradle:
File crashlyticsProperties = new File("${project.projectDir.absolutePath}/fabric.properties")
applicationVariants.all { variant ->
variant.productFlavors.each { flavor ->
def variantSuffix = variant.name.capitalize()
def generatePropertiesTask = task("fabricGenerateProperties${variantSuffix}") << {
Properties properties = new Properties()
properties.put("apiKey", flavor.fabricApiKey)
properties.put("apiSecret", flavor.fabricApiSecret)
properties.store(new FileWriter(crashlyticsProperties), "")
}
def generateResourcesTask = project.tasks.getByName("fabricGenerateResources${variantSuffix}")
generateResourcesTask.dependsOn generatePropertiesTask
generateResourcesTask.doLast {
println "Removing fabric.properties"
crashlyticsProperties.delete()
}
}
}
It iterates over application variants and for each application variant creates task that generates fabric.properties file and task that deletes this file after Fabric Gradle plugin generates application resources.
All you need now is to define product flavor or build type specific fabricApiKey and fabricApiSecret:
productFlavors {
flavor1 {
ext.fabricApiKey = "FLAVOR1_API_KEY"
ext.fabricApiSecret = "FLAVOR1_API_SECRET"
}
}
ext is an ExtraPropertiesExtention object provided by every ExtensionAware object. It allows new properties to be added to existing object. In my case flavor1 is ExtensionAware object and it can be extended with new properties by using ext.someProperty = "value" syntax and later these properties can be used as flavor.someProperty, flavor.fabricApiKey.
Also it's better to include fabric.properties to .gitignore.
And do not forget to remove ext.enableCrashlytics = false from debug build type if you used it to disable Crashlytics during debug. Instead of this you can disable it in Application.onCreate:
Fabric.with(this, new Crashlytics.Builder().core(
new CrashlyticsCore.Builder().disabled(BuildConfig.DEBUG).build()).build());
If you're not opposed to using an application id suffix, you don't need separate organizations. The crashes and answers will be treated as separate apps.
For instance, let's say my application id is io.example
In your build.gradle:
buildTypes {
debug {
applicationIdSuffix ".debug"
}
release {
//options
}
}
After you deploy the debug version to a device or emulator, on the Fabric site you will see two apps:
io.example
io.example.debug
One thing that is nice about this approach is that you can also keep track of other build flavors seprately: io.exmaple.free, io.exmaple.paid, io.example.exterprise, and so on.
A simpler solution, which is also compatible with Gradle 5.x+ is to create separate fabric.properties files for each of the build variants that needs a unique Fabric API key and secret. Create the fabric.properties files as:
#Contains API Secret used to validate your application. Commit to internal source control; avoid making secret public.
apiSecret=YOUR_API_SECRET
apiKey=YOUR_API_KEY
replacing YOUR_API_SECRET with the build variant's API secret and YOUR_API_KEY with the build variant's API key.
Then place each variant's fabric.properties under the project src/variant folder, e.g. app/src/debug or app/src/release. See documentation on build variants for additional details.
At build time, the fabric.properties for the variant being built will be used.

Crashlytics found an invalid API key

When I am trying to build project with value of meta-data tag as a string reference, crashlytics fail with following error:
Crashlytics found an invalid API key: #string/crashlytics.
Check the Crashlytics plugin to make sure that the application has been added successfully!
Contact support#crashlytics.com for assistance.
Doesn't work
<meta-data
android:name="com.crashlytics.ApiKey"
android:value="#string/crashlytics"/>
Works
<meta-data
android:name="com.crashlytics.ApiKey"
android:value="1234567890..."/>
I am want to define different keys inside string.xml for different productFlavors of my android project.
Update
After writing to crashlytics support:
Currently we only are able to evaluate the AndroidManifest.xml at build time so we don't look at any strings resources so we only support a hard coded string. I'll definitely share this with the team that you're interested so we can look into supporting this in a future release.
Edit: The solution accepted is working only if you are using an old version of Crashlytics (I was using v1.1.11). If you are using Fabric SDK you will notice
the tasks of the plugin have changed considerably and the script below
will not work. Also the API secret is not needed anymore, therefore
you can just use the <meta> in the manifest to specify the API key along with a manifest placeholder defined in your flavor:
in build.gradle:
flavor1 {
...
manifestPlaceholders = [crashlyticsApiKey: CRASHLYTICS_API_SECRET_HERE]
...
}
in AndroidManifest.xml:
...
<meta-data
android:name="com.crashlytics.ApiKey"
android:value="${crashlyticsApiKey}" />
...
There is another undocumented way to specify the Crashlytics key as noted here, and it is to use the crashlytics.properties (in the root of your project) to specify that value along with the API secret:
apiKey=YOUR_API_KEY
apiSecret=YOUR_API_SECRET
Unfortuntately this will not allow you to simply specify a different crashlytics.properties for each flavor, because it needs to be in the root of your project in order to be picked correctly by the gradle plugin. That means you need to generate that file dynamically.
The idea is to add the key/secret values in your flavor as custom properties, and generate the crashlytics.properties at buildtime, using the values from the current flavor to fill the file.
The build.gradle inside your android module should look like this:
...
productFlavors {
flavor1 {
...
set("crashlyticsApiKey", CRASHLYTICS_API_KEY_HERE)
set("crashlyticsApiSecret", CRASHLYTICS_API_SECRET_HERE)
...
}
...
}
File crashlyticsProperties = new File("${project.projectDir.absolutePath}/crashlytics.properties")
applicationVariants.all { variant ->
variant.productFlavors.each { flavor ->
def variantSuffix = variant.name.capitalize()
def generateResourcesTask = project.tasks.getByName("crashlyticsGenerateResources${variantSuffix}")
def generatePropertiesTask = task("crashlyticsGenerateProperties${variantSuffix}") << {
Properties properties = new Properties()
println "...copying apiSecret for ${variant.name}"
properties.put("apiSecret", flavor.crashlyticsApiSecret)
println "...copying apiKey for ${variant.name}"
properties.put("apiKey", flavor.crashlyticsApiKey)
properties.store(new FileWriter(crashlyticsProperties), "")
}
generateResourcesTask.dependsOn generatePropertiesTask
def cleanResourcesTask = project.tasks.getByName("crashlyticsCleanupResourcesAfterUpload${variantSuffix}")
cleanResourcesTask.doLast {
println "...removing crashlytics.properties"
crashlyticsProperties.delete()
}
}
}
...
Basically the script hooks in the building process and generates/fills the properties file just before the Crashlytics gradle plugin does its magic.
With Fabric's Crashlytics 2.6.6. I'm able to simply select a Build Variant from the menu (usually located on the left in Android Studio) and run the app. It takes a minute or so to propagate through to the Fabric dashboard, but I didn't have any need for a workaround.

How do you manage multiple environments while developing Android apps?

We're building an Android app that connects to the cloud. We have a test URL for our APIs and a production URL. We connect the app to our local development machines to talk to the database when developing but find ourselves modifying a global API URL to the production URL every time we generate an APk for the Play Store.
Is there a better way to manage environments for Android? Can we also have two versions of the app (development version) and the Play Store version? I am not able to have two versions as both the apps have the same signature. How do we best manage this?
With android studio and gradle its simple now.
inside your app build.gradle edit signing configs
signingConfigs {
debug {
storeFile file("debug.keystore")
storePassword "..."
keyAlias "..."
keyPassword "..."
}
prod {
storeFile file("prod.keystore")
storePassword "..."
keyAlias "..."
keyPassword "..."
}
dev {
storeFile file("dev.keystore")
storePassword "..."
keyAlias "..."
keyPassword "..."
}
}
add buildTypes
buildTypes {
debug {
buildConfigField 'String', 'BASE_URL', '"http://127.0.0.1:8080/"'
......
signingConfig signingConfigs.debug
}
prod {
minifyEnabled true
shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
buildConfigField 'String', 'BASE_URL', '"http://prod.example.com"'
......
signingConfig signingConfigs.prod
}
dev {
buildConfigField 'String', 'BASE_URL', '"http://dev.example.com"'
......
signingConfig signingConfigs.dev
}
}
In your code take base url configured in gradle file by this code.
public final static String BASE_URL = BuildConfig.BASE_URL;
You can also put different KEY or whatever which is build type specific in gradle file and in code it will take according to the build type you are running.
Its even possible to have different package name.
productFlavors {
my_prod {
applicationId "com.example.packtwo"
}
my_dev {
applicationId "com.example.packone"
}
}
In recent gradle config, there are some updates in specifying package name. You have to add flavourDimensions if using productFlavours. See below code with added flavourDimensions
flavorDimensions "pack"
productFlavors {
flavor_dev {
applicationId 'com.example.packtwo'
dimension "pack"
}
flavor_prod {
applicationId 'com.example.packone'
dimension "pack"
}
}
This will give you more details about product flavours and dimensions
https://developer.android.com/studio/build/gradle-plugin-3-0-0-migration.html
Check for more possibilities...
But if you are using different flavors you might have to deal with Manifest merging and all.
This is can be achieved using product flavours.
For achieving this requirement:
First of all, Create 2 files under the app folder of your project say development.props and production.props. Or you can add these 2 files in a package under app folder say config.
Basically, these 2 files contain keys and values. This key is same for both files. But their values are different. These files contain one key say “SERVER_URL” and its value. It would be written like this:
SERVER_URL=”Server_url_value”
In this case, only URL is different. So, I have added only one key-value pair in Props file. You can add more.
Then, create ProductFlavours in the app build.gradle file say development and production. Now, access different props files containing URLs in their correseponding flavours like this:
productFlavors {
development {
getProps('./config/development.props').each { p ->
buildConfigField 'String', p.key, p.value
}
}
production {
getProps('./config/production.props').each { p ->
buildConfigField 'String', p.key, p.value
}
}
}
def getProps(path) {
Properties props = new Properties()
props.load(new FileInputStream(file(path)))
return props
}
Now, For each flavour, there is a build type And this BuildType is added in app build.gradle. For example, Build type is Debug and release. And I have two flavours i.e. development and production. So, gradle task will be created using both flavour and build type like this:
assemble{flavourName}{BuildType}
Now, you need to type these commands only. It would generate required APK with its corresponding URL. Commands are:
./gradlew assembleProductionRelease would generate release build with Production URL.
./gradlew assembleDevelopmentDebug would generate debug build with Development URL.
./gradlew assembleProductionDebug would generate debug build with Production URL.
./gradlew assembleDevelopmentRelease would generate release build with development URL.
Top three gradle task would be very helpful. But the last task would generate Release build with development URL. But this is not recommended. So, we should stop developer to execute this task i.e. ./gradlew assembleDevelopmentRelease
Now To restrict developer to generate release build using Development URL, add this snippet in your app build.gradle file:
android.variantFilter { variant ->
if(variant.buildType.name.equals('release')
&& variant.getFlavors().get(0).name.equals('development')) {
variant.setIgnore(true);
}
}
Now, If we try to execute task i.e. ./gradlew DevelopmentRelease. Gradle would stop generating the build and throw exception and would say: This task assembleDevelopmentRelease is not found in the root project.
Use Ant to build at least the production versions. This way you can set certain config values/flags during building. Let's say you have a config.xml file that contains the URL to the server. You can have different Ant build targets that will change the URL to point to the appropriate server. Check out this tutorial. It explains exactly how that is done.
This I think is considered as the bast practice in case you use android studio with gradle.
You may want to look at this article: http://tulipemoutarde.be/2013/10/06/gradle-build-variants-for-your-android-project.html
Also available in youtube video: https://www.youtube.com/watch?v=7JDEK4wkN5I
This also allows you to have two different package name for the same app.
It uses gradle flavors to achieve exactly what you are looking for and is very easy to implement.
You can try gradle buildType and productFlavor. It will allow you to specifiy different Environment variables like url, versionName, etc And applicationId which will allow you to have dev and prod builds.
For more details http://developer.android.com/tools/building/configuring-gradle.html
I don't know what's the best practice in that case, but I do like this:
You could make your app a LIB and create 2 apps: production app and testing app. Import your lib for those apps and build their manifests (it's almost copy paste of the old one). Then you replace your /res/ files that are different in each app... (you could create a config.xml file that have the URL).

Categories

Resources