How do I change the AndroidManifest.xml file based on build variants? - android

I have an app with multiple build variants. The variants are used to build versions of the same app for different companies. So, I have several different variants that build different apps:
com.acme.app1
com.schmoe.app2
com.yop.app3
etc...
The build.gradle file handles this very well:
productFlavors {
app1 {
applicationId "com.acme.app1"
}
app2 {
applicationId "com.schmoe.app2"
}
app3 {
applicationId "com.yop.app3"
}
}
Here's my problem. I am integrating Dropbox into my apps. The AndroidManifest.xml file must be changed for each variant to include the appropriate app key (stored in my string file). Dropbox has the following addition to the manifest file:
<activity
android:name="com.dropbox.client2.android.AuthActivity"
android:launchMode="singleTask"
android:configChanges="orientation|keyboard">
<intent-filter>
<!-- Change this to be db- followed by your app key -->
<data android:scheme="db-abcdef" />
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.BROWSABLE"/>
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
Each build variant needs to change the following line:
<data android:scheme="db-abcdef" />
to the appropriate value for each app variant. In other words, I need to replace part of the above string based on the build variant. For instance
App1
<data android:scheme="db-111111" />
App2
<data android:scheme="db-222222" />
App3
<data android:scheme="db-333333" />
The line is the same for each variant up to the text "db-".
What I need is to dynamically replace the variable portion of the string (the x values) "db-xxxxxx" with a string from my string file.
I think this can be done with gradle scripts but I'm a complete newb with gradle. HELP!
If you can help, please be very specific about what goes where since I SUCK at gradle files and scripting. Thanks in advance!

You can save the string in build.gradle too:
productFlavors {
app1 {
applicationId "com.acme.app1"
resValue "string", "my_app_key", "db-abc"
}
app2 {
applicationId "com.schmoe.app2"
resValue "string", "my_app_key", "db-def"
}
app3 {
applicationId "com.yop.app3"
resValue "string", "my_app_key", "db-ghi"
}
}
And then use it like:
<data android:scheme="#string/my_app_key" />

You can do this the same way you would modify the app name, using a string resource. In your manifest, include:
<data android:scheme="#string/db_scheme" />
Next, you add resources folders for the various build types, and in each, place a custom copy of a resource file, say res/values/flavor.xml:
<?xml version="1.0" encoding="utf-8" standalone="no"?>
<resources>
<string name="app_name">Customer 1 App</string>
<string name="db_scheme">db-111111</string>
</resources>
This will allow you to have different API keys, schemes, app-names, icons, etc. for your various flavors.

For people visiting this in 2019: You can use manifestPlaceholders per flavor.
In your case:
productFlavors {
app1 {
applicationId "com.acme.app1"
manifestPlaceholders.scheme = "db-111111"
}
app2 {
applicationId "com.schmoe.app2"
manifestPlaceholders.scheme = "db-222222"
}
app3 {
applicationId "com.yop.app3"
manifestPlaceholders.scheme = "db-333333"
}
}
and then use it in your manifest:
<activity
android:name="com.dropbox.client2.android.AuthActivity"
android:launchMode="singleTask"
android:configChanges="orientation|keyboard">
<intent-filter>
<!-- Change this to be db- followed by your app key -->
<data android:scheme="${scheme}" />
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.BROWSABLE"/>
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>

Yet another way that is somewhat similar to Hibbem's answer:
android {
defaultConfig {
manifestPlaceholders = [schemeName: "default-scheme"]
}
...
app1 {
applicationId "com.acme.app1"
manifestPlaceholders = [schemeName: "db-111111"]
}
...
}
And in your manifest:
<intent-filter>
...
<data android:scheme="${schemeName}" />
...
</intent-filter>
More from the official docs

Related

App does not install from Play Store on devices with Android 12 and above

I faced the following problem with my application - it can not be installed from Play Store on devices with Android 12 and above (error dialog from Play Store app at the bottom of the post).
The problem only appeared recently, presumably from the version 31.9.13-21 [0] [PR] 467268234 of Google Play Store.
Some other facts:
Installing application from Android Studio using option "APK from app bundle" in run configuration works fine.
Installing universal APK from Google Play Console works fine.
Installing APK using bundletool works fine.
Removing Play Store updates fixes the ptoblem. Updates can be removed from Play Store app settings.
Logcat contains the following error when installing from Play Store: INSTALL_FAILED_INVALID_APK: Full install must include a base package. It seems that this error is thrown by Android system's class, more specifically here (google source). As far as I understand, this means that invalid set of apks is generated from bundle, without main (or base apk).
App module's build.gradle.kts snippet (without dependencies):
plugins {
id("com.android.application")
kotlin("android")
id("kotlin-parcelize")
id("com.google.gms.google-services")
id("com.google.firebase.crashlytics")
id("google-play-publisher")
id("appcenter")
id("com.huawei.agconnect")
id("app-gallery-publisher")
}
android {
compileSdk = 31
defaultConfig {
applicationId = "com.some.example"
minSdk = 24
targetSdk = 30
versionCode = Versions.versionCode
versionName = Versions.versionName
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
ndk {
abiFilters.addAll(setOf("armeabi-v7a", "x86", "arm64-v8a", "x86_64"))
}
}
compileOptions {
sourceCompatibility(JavaVersion.VERSION_11)
targetCompatibility(JavaVersion.VERSION_11)
}
kotlinOptions {
jvmTarget = "11"
}
buildTypes {
getByName("release") {
isShrinkResources = true
isMinifyEnabled = true
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
proguardFiles(*fileTree("$rootDir/proguard").files.toTypedArray())
signingConfig = signingConfigs.getByName("release")
}
getByName("debug") {
signingConfig = signingConfigs.getByName("debug")
}
}
packagingOptions {
exclude("META-INF/LICENSE.md")
exclude("META-INF/LICENSE-notice.md")
}
buildFeatures {
viewBinding = true
}
bundle {
language {
enableSplit = false
}
}
}
App module's AndroidManifest.xml:
<?xml version="1.0" encoding="utf-8"?>
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.some.example">
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission
android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
tools:node="remove"
tools:ignore="ScopedStorage"/>
<application
android:name="AppName"
android:allowBackup="false"
android:icon="#mipmap/ic_launcher"
android:label="#string/app_name"
android:networkSecurityConfig="#xml/network_security_config"
android:roundIcon="#mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="#style/AppTheme"
tools:replace="android:allowBackup">
<activity
android:name="SomeActivityName"
android:screenOrientation="portrait">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:scheme="somescheme"/>
</intent-filter>
<intent-filter android:autoVerify="true" tools:targetApi="m">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:scheme="https"
android:host="some.host"
android:pathPrefix="/prefix" />
<data
android:scheme="https"
android:host="some.host"
android:path="/path/"/>
</intent-filter>
</activity>
</application>
</manifest>
Update you compileSdk and targetSdk to 33 in your build.gradle.kts file.

Opening two instances of two different flavors but same code base

I am experiencing the following situation:
Application A is in the background, then I launch application B from the relevant icon, but A reopens.
Both flavors share most of the same code.
The problem is, both A and B opening the same MainActivity file. the MainActivity located in the main package and not inside the flavors packages. I want each flavor to open a new task for itself.
this is the current XML files:
A:
<activity
android:name="com.example.activities.MainActivity"
android:label="#string/app_name"
android:launchMode="singleTask">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
B:
<activity
android:name="com.example.activities.MainActivity"
android:label="#string/app_name"
android:launchMode="singleTask">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
and build.grade:
defaultConfig {
applicationId "com.example"
...
}
flavorDimensions "example"
productFlavors {
a{
dimension "example"
applicationIdSuffix ".a"
versionCode ...
versionName ...
//flavor consts
...
}
b{
dimension "example"
applicationIdSuffix ".b"
versionCode ...
versionName ...
//flavor consts
...
}
}
I was trying to switch the launchMode into a standard but it didn't do any good.
Thanks.
I had another field that wasn't mentioned:
android:taskAffinity="..."
both flavors used the same task Affinity application class, so I cut it into two separated Applications classes.
If you taskAffinity references your applicationId, it will only open as one instance. Try:
android:taskAffinity="${applicationId}"

How to add different values of string resource for different flavors?

I'm an iOS developer who is trying to make some small changes on android side but with no clue of how to achieve them. My goal is to implement Facebook SDK inside the app, official documentation says:
In /app/res/values/strings.xml add new string element:
<string name="facebook_app_id">Facebook App ID</string>
Then add in AndroidManifest.xml file new meta-data like below:
<application android:label="#string/app_name" ...>
...
<meta-data android:name="com.facebook.sdk.ApplicationId" android:value="#string/facebook_app_id"/>
...
</application>
All is understood but my problem is that I have few product flavors based on which I build at the end stand alone apps, for each I have different Facebook App ID. I would appreciate any kind of help. Thanks!
You can create multiple build types or product flavors inside the app-level build.gradle file and then define different strings for each build variant like this
buildTypes {
prod{
resValue "string", "facebook_app_id", "your app id"
}
dev {
resValue "string", "facebook_app_id", "your app id"
}
}
Now you can access this string inside manifest
<meta-data android:name="com.facebook.sdk.ApplicationId" android:value="#string/facebook_app_id"/>
There are many ways. But as you already define multiple flavour in build.gradle in app level then you can define the string over there like this
android {
buildTypes {
flavour1 {
buildConfigField "String", "facebook_app_id", "\"first facebook id 1\""
}
flavour2 {
buildConfigField "String", "facebook_app_id", "\"first facebook id 2\""
}
}
}
It automatically initialise the facebook_app_id to the build when you are creating the build of the flavour.
Happy Coding :)
In build.gradle :
productFlavors {
appDev {
applicationId 'com.android.dev'
}
appTest {
applicationId 'com.android.test'
}
}
flavorDimensions "default"
Inside src folder:
create folder with flavors name
-src
-appDev(folder)
-java(folder)
-res(folder)
-values(folder)
-appTest(folder)
-java(folder)
-res(folder)
-values(folder)
inside values folder you can create strings.xml each folder act as different flavor
you need to pass your facebook app_id in the string files.
<string name="facebook_app_id">35166120881***</string>
<string name="fb_login_protocol_scheme">fb35166120881****</string>
...
<activity android:name="com.facebook.FacebookActivity"
android:configChanges=
"keyboard|keyboardHidden|screenLayout|screenSize|orientation"
android:label="#string/app_name" />
<activity
android:name="com.facebook.CustomTabActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="#string/fb_login_protocol_scheme" />
</intent-filter>
</activity>
...
</application>
Important this
<meta-data android:name="com.facebook.sdk.ApplicationId"
android:value="#string/facebook_app_id"/>

manifestPlaceholders value is not string

In my AndroidManifest.xml file I have the following meta-data tag which should be populated dynamically:
<meta-data android:name="com.facebook.sdk.ApplicationId" android:value="${FACEBOOK_APP_ID}"/>
My gradle file looks like this:
manifestPlaceholders = [
GOOGLE_PROJECT_ID: "A888844613784",
FACEBOOK_APP_ID: "888570042741264"
]
After "Build & Assemble" the FACEBOOK_APP_ID in the manifest file looks like this:
<meta-data
android:name="com.facebook.sdk.ApplicationId"
android:value="1481023616.000000" />
Unfortunately this is not a String, but a float value. That's not correct or what I want.
I know there is another way to define the FACEBOOK_APP_ID in the string.xml file. But since I have lots of flavors it would be nice and easy to maintain if we put all flavors-related parameters in the build.gradle file instead of the strings.xml files.
Does anyone know how to avoid the string to float conversion?
You can use the following code to add a String value to your string resources from a build.gradle file:
resValue 'string', 'FACEBOOK_APP_ID', 'facebook_application_id'
But I'm not sure if the AndroidManifest.xml file does not support flavor specific strings (I can remember you will get a warning if you try, but I'm not sure).
You could also try to add a null-terminator to your FACEBOOK_APP_ID manifestPlaceholder as suggested in this answer:
FACEBOOK_APP_ID: "888570042741264\0"
Edit:
The code null-terminator method seems not to be working when used directly from the build.gradle file, it does however work when using the null-terminator inside the AndroidManifest.xml file:
<meta-data android:name="com.facebook.sdk.ApplicationId" android:value="${FACEBOOK_APP_ID}\0"/>
I use manifestPlaceholders and force gradle to recognize the string by escaping the quotes like so:
manifestPlaceholders = [facebook_app_id:"\"9999000099990000\"",
fb_login_protocol_scheme:"fb9999000099990000"]
You don't need to it for the second param since it has the string fb in front.
This is working for me (if you do not use flavors, just put resValue into defaultConfig):
productFlavors {
production {
resValue 'string', 'facebookAppId', '22222222222'
resValue 'string', 'facebookSchemeId', 'fb22222222222'
}
development {
resValue 'string', 'facebookAppId', '11111111111'
resValue 'string', 'facebookSchemeId', 'fb11111111111'
}
}
Then in manifest:
<meta-data
android:name="com.facebook.sdk.ApplicationId"
android:value="#string/facebookAppId" />
<activity
android:name="com.facebook.CustomTabActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="#string/facebookSchemeId" />
</intent-filter>
</activity>
I just face a similar issue where my facebook values were treated as a number and garbled.
If I make sure to wrap them in " then it works as expected and they are treated as strings.
manifestPlaceholders += [FACEBOOK_APP_ID: "\"" + rootProject.facebookAppId + "\"", FACEBOOK_CLIENT_TOKEN: "\"" + rootProject.facebookClientToken + "\""]

Android Parse, notifications and buildTypes

I am currently using parse to send and receive push notifications on an Android app.
Everything was fine until i added an application id suffix to my build.gradle for the debug build type:
defaultConfig {
applicationId "com.example.myapp"
...
}
buildTypes {
debug {
applicationIdSuffix ".debug"
...
}
release {
...
}
}
Now I can see in Parse that new installations are registered with the applicationId field : com.example.myapp.debug, so into the manifest file I used the following piece of code :
<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="${applicationId}" />
</intent-filter>
</receiver>
Doing this i thought the registration would be dynamic according to the build type.
Actually it doesn't work : installations registered with the .debug suffix have no GCMSenderId nor DeviceToken, and so i can't receive any notification. I am obviously missing a point, or I didn't understand how the registration works, but i can't figure how to make it ok.
Does Parse use the applicationId to register the application, or the real package name (used for R) ?
Did anyone manage to handle parse notifications with different buildTypes (applicationId suffix) ?
Ok, I finally figured it out. I forgot to add the dynamic applicationId in the permission declaration...
<permission
android:name="${applicationId}.permission.C2D_MESSAGE"
android:protectionLevel="signature" />
<uses-permission android:name="${applicationId}.permission.C2D_MESSAGE" />
Problem solved.

Categories

Resources