Back to Blog Home

Streamline Builds with Android Manifest Placeholders

Sneh Pandya image

Sneh Pandya -

Streamline Builds with 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, the Android toolchain provides customization by allowing you to specify dynamic information through variable declaration, generally referred to 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 configurations for your Manifest, managing configurations specific to build variants, and more.

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

Sentry

Android Crash Reporting and Performance Monitoring

Resolve Android crashes faster with deep insights and automated workflows.

READ MORE

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 that 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 an 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, it can get cumbersome as the build variants and flavors grow in your Android app.

For example, your app module has multiple flavors that 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 configurations for your app, including separate Manifest files. In a verbose manner, this would result in the below files being generated:

  • 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 that is simple, it has its own problems:

  • Management of multiple Manifest files gets trickier with the growing number of flavors

  • The code becomes error-prone since the complexity and number of iterations increase with each file and flavor

  • Updating any configuration in Manifest requires replication in all the remaining Manifest files, which can be 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 managed only one file for all your configurations and flavors specified in Gradle.

Once AndroidManifest.xml files are ready, the Android toolchain uses 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 a Manifest merging tool that matches elements between multiple Manifest files logically and takes the 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 for specific details and sources is on the right. Different priorities are set to different Manifest files, which are denoted by different colors on the left, and 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 parent 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, keeping all the elements as is and adding 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 to be 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 trees performed by the 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 as 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 application 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 application 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 essential part of any Android application since it contains all the necessary configurations to run and navigate through your app and what kind of data, permissions, and external dependencies are allowed.

In this article, you saw how you can customize elements in the Manifest file 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.

Share

Share on Twitter
Share on Bluesky
Share on HackerNews
Share on LinkedIn

Published

Sentry Sign Up CTA

Code breaks, fix it faster

Sign up for Sentry and monitor your application in minutes.

Try Sentry Free

Topics

Guest Posts

New product releases and exclusive demos

Listen to the Syntax Podcast

Of course we sponsor a developer podcast. Check it out on your favorite listening platform.

Listen To Syntax
© 2024 • Sentry is a registered Trademark of Functional Software, Inc.