Share on Twitter
Share on Facebook
Share on HackerNews

Android Manifest Placeholders

Android Manifest file is essential for any Android app, which contains specific information about your app, Android build tools, Google Play, device permissions, app launch information, operating system config and more. Every Android app must have an AndroidManifest.xml file in the directory structure.

Android Manifest usually contains pre-defined or static information which is then used to run the app. However, Android toolchain provides customization by allowing you to specify dynamic information through variable declaration, generally referred as Android Manifest placeholders. These variables are specified under the manifestPlaceholders block in your build.gradle file.

The variables are defined in key-value pairs and can be used for various purposes such as specifying custom configuration for your Manifest, managing configuration specific to build variant and more.

In this article, you will learn how to make use of Android Manifest placeholders to supercharge your Android app development.

Injecting Variables into AndroidManifest.xml

Let’s begin with the simplest case. When you need to specify a property such as a URL for your app’s network communication in your Manifest, you need to specify it in your build.gradle as below:

// build.gradle
android {
  defaultConfig {
    manifestPlaceholders.baseUrl = "api.demoserver.com"
  }
}

Here, the variable name is baseUrl whereas the assigned string is the value which will be received by AndroidManifest.xml once declared as below:

// AndroidManifest.xml
<intent-filter ...> 
  <data android:scheme="https" android:host="${baseUrl}" ... />
</intent-filter>

Similarly, other examples can include properties such as applicationId, screenOrientation as well as frequently used elements in the AndroidManifest.xml:

// build.gradle
android {
  defaultConfig {
    applicationId "example.manifest.app"
  }
  buildTypes {
    release {
      manifestPlaceholders.screenOrientation = "portrait"
    }
    debug {
      manifestPlaceholders.screenOrientation = "unspecified"
    }
  }
}

Which can be used in your Manifest as below:

// AndroidManifest.xml
<intent-filter ... >
  <action android:name="${applicationId}" />
</intent-filter>
<uses-sdk tools:overrideLibrary="${applicationId}.home, ${applicationId}.util" />
<activity
  android:name=".MainActivity"
  android:screenOrientation="${screenOrientation}" />

Alternatively, you can specify in the form of key-value pairs at once by assigning manifestPlaceholders as array like this:

// build.gradle
android {
  defaultConfig {
    manifestPlaceholders = [baseUrl: "dev.demoserver.com",
                                screenOrientation: "portrait",
                               ]
  }
}

Managing Multiple Manifests

While managing separate Manifest files for each build variant sounds easy and straightforward, it can get cumbersome as the build variants and flavors grow in your Android app.

For example, your app module has multiple flavors which are needed for different build variants. The app.gradle file will look like this:

// build.gradle
android {
  buildTypes {
    release { ... }
    debug { ... }
  }
  flavorDimensions "ui", "paidui", "trialui", "referralui"
  productFlavors {
    paid {
      dimension "ui"
    }
    free {
      dimension "paidui"
    }
    trial {
      dimension "trialui"
    }
    referral {
      dimension "referralui"
    }
  }
}

Here, ui and proui are flavor dimensions which are respectively assigned to flavors paid and free. This allows you to generate and assign separate source-set configuration for your app, including separate Manifest files. In a verbose manner, this would result in files generation as below:

  • app/src/paidDebug/AndroidManifest.xml
  • app/src/paidRelease/AndroidManifest.xml
  • app/src/freeDebug/AndroidManifest.xml
  • app/src/freeRelease/AndroidManifest.xml
  • app/src/trialDebug/AndroidManifest.xml
  • app/src/trialRelease/AndroidManifest.xml
  • app/src/referralDebug/AndroidManifest.xml
  • app/src/referralRelease/AndroidManifest.xml

While using multiple Manifest files sounds like an idea which is simple, it has its own problems:

  • Management of multiple Manifest files get trickier with growing number of flavors
  • The code becomes error-prone since the complexity and number of iteration increases with each file and flavor
  • Updating any configuration in Manifest requires replication in all the remaining Manifest files which can get troublesome
  • File comparison, error identification, Manifest merging and differentiating files from each other becomes extremely difficult

To solve this, you can inject variables into your AndroidManifest.xml file, which will make sure that you have to manage only one file for all your configurations and flavors specified in Gradle.

Once AndroidManifest.xml files are ready, Android toolchain makes use of Manifest merge policies to merge all the manifest files into a single source. Let us understand how the Android Manifest merging process works.

Merging Multiple Manifests

Android toolchain includes Manifest merging tool which matches elements between multiple Manifest files logically and takes necessary steps to generate a final merged Manifest file. This tool matches elements with two different approaches:

  • Key element matching, e.g. ”android:name” declared inside <action> tag
  • XML tag matching, e.g. <supports-screen> tag

Understanding the merging process becomes crucial since multiple files can introduce conflicts and errors. You can check the merged Manifest file preview in Android Studio by navigating to AndroidManifest.xml file and then Merged Manifest Viewer at the bottom of the editor.

The Merged Manifest view shows you the final resulting output of the merged Manifest files on the left whereas the information to specific details and sources on the right. Different priority is set to different Manifest files, which are denoted by different colors on the left as well as a color legend is also provided on the right.

When any two or more Manifest files include the same XML element, then the merging tool performs merging based on three predefined merge policies:

Merge

This is the standard strategy, which combines all non-conflicting elements and properties in the same tag. In the next step, child elements are merged according to their respective merge policy - if any elements or properties conflict with each other, then merge rule markers are used to merge such elements or properties together.

Merge Children Only

In this policy, the merge tool gives precedence to the attributes and tags of parents elements to the highest priority manifest, while the child elements from all the Manifest files are merged according to the merge policy.

Keep

This policy applies to all the children which keeps all the elements as is and adds them to the parent element with the same declaration in the merged Manifest file. This is a relatively less used strategy as it only allows elements with multiple declarations being accepted for the same parent.

Manifest Merge Conflicts and Resolution

If conflict(s) appear while merging multiple Manifest files, they are displayed under the Merging Errors section on the right side of the editor. This section also helps you with hints or recommendations on how to resolve the conflict using the merge rules we discussed above.

For advanced level configuration and Manifest merging, it becomes critical to ensure that the correct elements are merged when the final merged result of AndroidManifest.xml is generated. To verify the same, you can find the log file which contains all the merging steps and decision tree performed by Android toolchain to merge multiple Manifest files for you.

You can find the log files under build/outputs/logs directory, where the file name appears something like manifest-merger-*buildvariant*-report.txt. This file gets updated every time Manifest is updated and Manifest merging takes place.

Build Your Own Manifest

You’ve gathered all the important pieces to understand how Android Manifest placeholders can be useful for your Android apps. Let’s look at one such example below:

<intent-filter>
  <action android:name="android.intent.action.VIEW" />
  <category android:name="android.intent.category.BROWSABLE"/>
  <category android:name="android.intent.category.DEFAULT" />
  <!-- custom intent filter definition for deep links -->
  ${deepLinks_manifestPlaceholder}
</intent-filter>

Here, ${deepLinks_manifestPlaceholder} will be replaced with a specific deep link definition. This way, you can generate deep links to accept data and intent from multiple domains, subdomains and schemes in order to navigate your users to a specific screen as well as perform any actions on the basis of data provided via deeplink.

Manage Your Sentry.io Configuration

When setting up your Android app, Sentry will supply you with a DSN, or Data Source Name, a unique value. It seems like a standard URL, but it’s actually a representation of the Sentry SDKs’ configuration. The protocol, public key, server IP, and project identity are only a few of the components.

The default DSN configuration looks like this in the Manifest:

<application>
  <meta-data android:name="io.sentry.dsn" android:value="https://1093t8yhqfw8h819@x11.ingest.sentry.io/123123" />
  <!-- Set tracesSampleRate to 1.0 to capture 100% of transactions for performance monitoring.
   We recommend adjusting this value in production. -->
  <meta-data android:name="io.sentry.traces.sample-rate" android:value="1.0" />
</application>

Here, it is important to note that, the meta-data value specified for io.sentry.traces.sample-rate specifies the performance monitoring scale for Sentry to capture the transactions. Value 1.0 equals 100% of transactions being captured. This can vary based on different environments, such as during development v/s in production.

Also, capturing transactions requires you to declare performance monitoring as well as Activity’s instrumentation. The Activity’s instrumentation is declared as below:

<meta-data android:name="io.sentry.traces.activity.enable" android:value="true" />
<meta-data android:name="io.sentry.traces.activity.auto-finish.enable" android:value="true" />

Here’s how you can replace it with Manifest placeholders to achieve seamless performance monitoring for multiple environments:

The build.gradle file would look like this:

android {
  buildTypes {
    release { 
      manifestPlaceholders.sentryTraceSampleRate = "0.6"
      manifestPlaceholders.sentryActivityTrace = true
      manifestPlaceholders.sentryAutoFinishActivityTrace = true
    }
    debug {
      manifestPlaceholders.sentryTraceSampleRate = "1.0"
      manifestPlaceholders.sentryActivityTrace = false
      manifestPlaceholders.sentryAutoFinishActivityTrace = false
    }
  }
}

With the above build.gradle, you can modify your AndroidManifest.xml to support placeholders as below:

// AndroidManifest.xml
<application>
  <meta-data android:name="io.sentry.dsn" android:value="https://1093t8yhqfw8h819@x11.ingest.sentry.io/123123" />
  <!-- Set tracesSampleRate to 1.0 to capture 100% of transactions for performance monitoring.
      We recommend adjusting this value in production. -->
  <meta-data android:name="io.sentry.traces.sample-rate" android:value="${sentryTraceSampleRate}" />
  <meta-data android:name="io.sentry.traces.activity.enable" android:value="${sentryActivityTrace}" />
  <meta-data android:name="io.sentry.traces.activity.auto-finish.enable" android:value="${sentryAutoFinishActivityTrace}" />
</application>

This way, you can also declare properties in your build.gradle and access them in AndroidManifest.xml with the help of manifest placeholders.

Conclusion

AndroidManifest.xml is an important part of any Android application, since it contains all the necessary configuration to run and navigate through your app as well as what kind of data, permissions and external dependencies are allowed.

In this article, you saw how you can customize elements in the Manifest file in order to provide variables so that different customizations can work well alongside each other, avoiding messy code as well as avoiding managing multiple Manifest files. This versatile approach not only speeds up your development time, but also gives you more control and power to manage your apps while entertaining a wide variety of customizations specific to build flavors and requirements.

Your code is broken. Let's Fix it.
Get Started

More from the Sentry blog

ChangelogDashboardsDiscoverDogfooding ChroniclesEcosystemError MonitoringEventsGuest PostsMoonlightingOpen SourcePerformance MonitoringRelease HealthSDK UpdatesSentry

Do you like corporate newsletters?

Neither do we. Sign up anyway.

© 2022 • Sentry is a registered Trademark
of Functional Software, Inc.