Crashlytics found an invalid API key - android

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.

Related

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.

Setting app label based on buildvariant

I have an Android with multiple productFlavors. I'd like the android:label for the debug build of, for example flavor1, to say Flavor1 Debug and the release build to say Flavor1.
Without productFlavors this is relatively simple, just create a different string resource in the debug and release source folders but with productFlavors, I don't want to be creating flavor1Debug, flavor2Debug, etc. folders for each flavor with just one string resource in there.
Is there a way to do this ? I'm guessing it requires merging resources somehow but am not sure how.
For extra points, it would be awesome if I could add an overlay to the app icon i.e. merge two images but I realize that might be taking it too far.
I worked out a solution for this. Basically, you use the following method that copies read the build file, modifies the property value, then rewrites the files.
def debugAppRename(variant, labelResource) {
def flavor = variant.productFlavors.get(0)
def buildtype = variant.buildType
// Append buildType name to app label
if(buildtype.debuggable) {
variant.mergeResources << {
def valuesFile = "$buildDir/res/all/${flavor.name}/${buildtype.name}/values/values.xml"
def values = (new XmlParser()).parse(valuesFile)
values.string.each { m->
if (m.#name == labelResource) {
m.value = m.text() + " " + buildtype.name.capitalize()
new XmlNodePrinter(new PrintWriter(new FileWriter(valuesFile)))
.print(values)
}
}
}
}
}
The way to use it is:
applicationVariants.all { variant ->
debugAppRename(variant, 'app_name') // where `app_name` is the string resource you use for the `app:label` property in your AndroidManifest.xml
}
The resulting app will have the buildType name appended to it if it is a debuggable buildType, for e.g. My App Debug, My App Staging, etc. The release build remains unaffected.
I also put it up on a gist.
You can apply it in your build.gradle using the apply from: directive with the raw gist URL or copy the above to your code (personally I find build scripts become too large so I prefer applying from).

Android Gradle replace Package name for a value in manifest

I am using Gradle with Product flavors where I set a different package name for each one.
productFlavors {
appone {
packageName "com.dg.app1"
}
apptwo {
packageName "com.dg.app2"
}
appthree {
packageName "com.dg.app3"
}
appfour {
packageName "com.dg.app4"
}
}
I need to be able to replace the package name inside the manifest for each corresponding app.
My manifest has this:
<receiver android:name="com.parse.GcmBroadcastReceiver"
android:permission="com.google.android.c2dm.permission.SEND">
<intent-filter>
<action android:name="com.google.android.c2dm.intent.RECEIVE" />
<action android:name="com.google.android.c2dm.intent.REGISTRATION" />
<category android:name="com.dg.example" />
</intent-filter>
</receiver>
So I need to replace com.dg.example for each app flavor's package name. What is the best way to do this?
Gradle Plugin v0.12 and higher:
Use ${applicationId} instead of ${packageName}.
Gradle Plugin v0.11 and higher:
As of v0.11, you no longer need to specify not to use the old manifest merger.
Gradle Plugin v0.10 and higher:
Assuming you're using version 0.10 or higher, this is now officially supported:
buildscript {
repositories {
mavenCentral()
}
dependencies {
// Make sure this is at least 0.10.+
classpath 'com.android.tools.build:gradle:0.10.+'
}
}
As of v0.10, you'll also have to manually enable the new manifest merger, although I'd expect that requirement to go away in a version or two whenever the new merger becomes the default:
android {
useOldManifestMerger false
}
Then, just use ${packageName} anywhere in AndroidManifest.xml that you would normally hardcode the package name. For example:
<category android:name="my.package.name"/>
would become
<category android:name="${packageName}"/>
Gradle Plugin v0.9 and below:
So, referencing this post, it appears this is not yet officially supported through Gradle. A simple workaround is the following:
Replace the package name with a custom tag (e.g. <category android:name="my.package.name"/> becomes <category android:name="_PACKAGENAME_"/>
Add the following to your build.gradle, under the android scope:
applicationVariants.all { variant ->
// After processing the manifest, replace all instances of your tag
// with the variant's actual package name.
variant.processManifest << {
def manifestOutFile = variant.processManifest.manifestOutputFile
def newFileContents = manifestOutFile.getText('UTF-8').replace("_PACKAGENAME_", variant.packageName)
manifestOutFile.write(newFileContents, 'UTF-8')
}
}
To do something like this, I use buildTypes in my gradle file but I am pretty sure this will work with flavours as well. For me I am trying to set the label field in the activities.
I have a strings xml file for each of my buildTypes.
Then I have a sourceSet for each buildType which includes the correct strings file.
Then in the manifest I do not use a hard coded string but rather "#string/my_var" which will pull the correct string depending on how the sourceSets are defined.
This google+ post and related gist may help.
Something else to do is to put a AndroidManifest.xml file into the src/flavour which only contains the bits which are relevant to each flavour. Then take those bits out of the main manifest file. At build time the Manifest files will be merged into one file. You can see the result all of the merged manifests in build/manifests.
I had the same problem and implemented a placeholder replace method in Gradle. It does exactly what you'd expect but also takes care about packageNameSuffix attributes so you can have debug and release as well as any other custom builds on the same device.
applicationVariants.all { variant ->
def flavor = variant.productFlavors.get(0)
def buildType = variant.buildType
variant.processManifest.doLast {
println '################# Adding Package Names to Manifest #######################'
replaceInManifest(variant,
'PACKAGE_NAME',
[flavor.packageName, buildType.packageNameSuffix].findAll().join()) // ignores null
}
}
def replaceInManifest(variant, fromString, toString) {
def flavor = variant.productFlavors.get(0)
def buildtype = variant.buildType
def manifestFile = "$buildDir/manifests/${flavor.name}/${buildtype.name}/AndroidManifest.xml"
def updatedContent = new File(manifestFile).getText('UTF-8').replaceAll(fromString, toString)
new File(manifestFile).write(updatedContent, 'UTF-8')
}
I have it up on a gist too if you want to see if it evolves later.
I found to be a more elegant approach than the multiple resources and XML parsing approaches.
Option Gradle:
Use grade attributes API. Some thing like this
manifest.attributes(["attr1":"value1", "attr2":"value2"])
Option 1
How about converting your project to Android - library project, and making extra project for each company. Than you can edit the Manifest file as you wish.
Option 2
Write a batch file.

Gradle using two property files one for debug and release

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")

Edit an AndroidManifest when compiling to remove API-key

I'm currently working on an Android project, and learning how to use git.
I'm blocked because of a problem with git : I have my Google Maps api key declared in my android-manifest file :
<meta-data
android:name="com.google.android.maps.v2.API_KEY"
android:value="HEREISMYKEY"/>
Now, I'd like to push my code in github, but I can't push my AndroidManifest, because it contains my api key (which is supposed to remain secret).
I'd like to know if there is a way to modify it before every push, or maybe modify it each time I compile my application?
Thank you for your help !
Create a new *.xml file in your res/values (call it api-keys.xml or something similar).
Change your manifest to point to this string:
<meta-data
android:name="com.google.android.maps.v2.API_KEY"
android:value="#string/GoogleMapsKey"/>
When you first push your changes to the public, put in a dummy key.
Then edit the file with your real API key.
Then add the file to your .gitignore file so it doesn't get pushed again.
I suggest to use Gradle to dynamically load the API key for different build types and load a dummy key if none is found. This has the advantage that also a CI can build your application without the need of a real API key, but every new developer in your team has to set up the API key.
create a properties file google-maps-api.properties in your root gradle folder. Ignore the file by adding this line gradle/google-maps-api.properties to your .gitignore and put in the following 2 properties:
googleMapsApiKeyDebug=[YOUR_DEBUG_GOOGLE_MAPS_API_KEY_HERE]
googleMapsApiKeyRelease=[YOUR_RELEASE_GOOGLE_MAPS_API_KEY_HERE]
Next create the following google-maps-api-config.gradle file in your root gradle folder. This Gradle file will load the property file you created in step 1.
ext {
googleMapsApiConfig = new GoogleMapsApiConfigLogic(project)
}
/*
Before you run your application, you need a Google Maps API key.
Using these values:
Package name:
at.ict4d.covid19map
SHA-1 certificate fingerprint:
DE:4A:F0:B1:F5:F4:00:88:B6:E0:03:3E:31:1F:11:88:3F:43:0D:8B
Follow the directions here:
https://developers.google.com/maps/documentation/android/start#get-key
Once you have your key (it starts with "AIza") create a file with the name "google-maps-api.properties" in the gradle folder:
gradle/google-maps-api.properties
Put in 2 properties:
googleMapsApiKeyDebug:[THE KEY YOU JUST CREATED]
googleMapsApiKeyRelease:[YOUR RELEASE GOOGLE MAPS KEY] (if you are just developing and not planning to publish then fill in "YOUR_RELEASE_KEY_HERE")
"gradle/google-maps-api.properties" example:
googleMapsApiKeyDebug=AIzaFJKLJKsdLFDDsjlkdfDFJKLdjkf
googleMapsApiKeyRelease=YOUR_RELEASE_KEY_HERE
Sync your project and build.
*/
class GoogleMapsApiConfigLogic {
public final keyGoogleMapsApiKeyDebug = "googleMapsApiKeyDebug"
public final keyGoogleMapsApiKeyRelease = "googleMapsApiKeyRelease"
private final configGoogleMapsApiPropsFileName = "gradle/google-maps-api.properties"
public final props
private final project
GoogleMapsApiConfigLogic(project) {
this.project = project
this.props = loadGoogleMapsApiProperties()
}
def loadGoogleMapsApiProperties() {
Properties propertiesObj
if ((propertiesObj = loadFromProperties("$project.rootDir/$configGoogleMapsApiPropsFileName")) != null) {
project.logger.quiet("google-maps-api-config: use local properties file for Google Maps API")
return propertiesObj
} else {
propertiesObj = new Properties()
propertiesObj.put(keyGoogleMapsApiKeyDebug, "YOUR_KEY_HERE")
propertiesObj.put(keyGoogleMapsApiKeyRelease, "YOUR_KEY_HERE")
}
project.logger.quiet("google-maps-api-config: no API key found for Google Maps")
return propertiesObj
}
def loadFromProperties(fileName) {
Properties props = new Properties()
File propFile = new File(fileName)
if (propFile.exists()) {
props.load(new FileInputStream(propFile))
if (props.get(keyGoogleMapsApiKeyDebug) != null &&
props.get(keyGoogleMapsApiKeyRelease) != null) {
project.logger.quiet("google-maps-api-config: use $fileName for Google Maps API")
return props
}
} else {
project.logger.quiet("google-maps-api-config: $propFile does not exist for Google Maps API")
}
return null
}
}
Add the the following to your app/build.gradle file:
plugins {
id 'com.android.application'
id 'kotlin-android'
id 'kotlin-android-extensions'
// ...
}
apply from: "$rootDir/gradle/google-maps-api-config.gradle" // handles Google Maps API key
android {
// ...
buildTypes {
release {
// other config
resValue "string", "google_maps_key", "\"${googleMapsApiConfig.props.getProperty(googleMapsApiConfig.keyGoogleMapsApiKeyRelease)}\""
}
debug {
// other config
resValue "string", "google_maps_key", "\"${googleMapsApiConfig.props.getProperty(googleMapsApiConfig.keyGoogleMapsApiKeyDebug)}\""
}
}
This will create a String Resource with the key google_maps_key and the value of your API key. You can delete any other API key references, e.g. Android Studio generate the following files: app/src/debug/res/values/google_maps_api.xml and app/src/release/res/values/google_maps_api.xml - you can delete them.
Add the Google Maps API Key to your manifest:
<application>
<meta-data
android:name="com.google.android.geo.API_KEY"
android:value="#string/google_maps_key" />
The way I have approached similar issues in the past is by using specific branches in my git repo for pubic pushes.
Say you have a local master branch with your keys in the manifest. When you are ready to push to github (or wherever else) you can make a new "release" branch with no history. You can see a bit more about that here: How to push new branch without history git branch --orphan release. Once you do that remove all private information, commit all files and push only that branch to github git push origin release.
The issue with this is you will not have a commit history, maybe someone else can come up with something better.
If you create Google Maps Activity from Android Studio as a template it will add needed plugin in your build.gradle file, and then you can put your API key in local.properties file and use it as variable in Manifest file as for example: "${API_KEY}"
For more information visit https://developers.google.com/maps/documentation/places/android-sdk/secrets-gradle-plugin

Categories

Resources