android { compileSdkVersion 31 defaultConfig { minSdkVersion 23 targetSdkVersion 31 applicationId "com.example.applicationname" versionCode 1 versionName "1.0" resConfigs "en" multiDexEnabled true }

Migrating Apps To Android 12

Android Jun 28, 2022

This article is completely based on our recent experience migrating Halodoc Apps to Android 12. To include the new capabilities of Android 12, we planned this migration.

About Android 12

Android is the world’s most popular and most used smartphone operating system. It's running on nearly 3 billion smartphones.

Google releases a software update every year to its Android mobile operating system. Last year Google rolled out Android 11 and now Google has released the stable version of Android 12 (API 31).

Make sure compatibility with Android 12

It's important to test the functionality of your existing app against Android 12 to provide a great experience for users updating to the latest version of Android. Some platform changes can affect the way your app behaves, so it's important to test early and thoroughly and make any needed adjustments to your app.

You can usually adjust your app and publish an update without needing to change the app's targetSdkVersion. Similarly, you should not need to use new APIs or change the app's compileSdkVersion, although this can depend on the way your app is built and the platform functionality it's using.

Migration Journey

1. Update Target API Level:

The first step in the migration process is to update the target API level. If you recently updated or created your app, you will already be at target API 30 (Android 11).

However, you just need to open up your app-level build.gradle and set the targetSdkVersion as well as the compileSdkVersion to 31.

2. Safer component exporting

Now that we have updated the build.gradle, all the new Android 12 restrictions are applied to our app.

With an app targeting API 31, you need to set the android:exported tag for any activity, service, or broadcast receiver that uses intent filters.

You should only set it  true if you need the respective component to be accessible from outside your application.

Note: On your launcher activity the tag needs to be set to true, otherwise the system won’t be able to start your app.

If you leave out this tag on the mentioned components, your app won’t be installable on Android 12 devices and will show this error:

Manifest merger failed : android:exported needs to be explicitly specified for <activity>. Apps targeting Android 12 and higher are required to specify an explicit value for 'android:exported' when the corresponding component has an intent filter defined.
See for details.

3. Scheduled Exact Alarm Permission

After leaving the configuration adaptions behind, we started migrating the first component that affected the first feature of our app.

We implemented the option for users to add reminders that get triggered via AlarmManager at exact points in time.

Because Android 12 introduced the new SCHEDULED_EXACT_ALARM permission, we can no longer out of the box set an alarm at an exact time. But don’t worry, the migration process isn’t that hard.

Check if your app meets the acceptable use cases for this permission. Because our app needs to remind the user about an affirmation at a specific time, the following use case is met.

“Your app allows users to schedule precisely-timed actions, such as notifications for tasks and events.”

To set alarms at an exact time with target API 31, we just have to declare the mentioned permission inside our AndroidManifest.xml file.

If your app is using Alarm Manager, then use this new Permission to set alarms:

From Android12​ onwards, this permission allows applications to use exact alarm APIs. If we are setting any kind of alarm, we need to add this permission inside our App manifest.


4. Pending intent mutability

In the next restriction, not only was the alarm schedule restricted, but also the notification.

With the introduction of Android 12, you now have to set either the flag PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_MUTABLE if your app minSdkVersion is set to Android 6.0 Marshmallow (SDK API 23) or above.

If your app's minSdkVersion is below SDK 23, you need to perform a runtime check-on Build.VERSION.SDK_INT to make sure the respective device has the required SDK number.

If the device is below that SDK level, skip the usage of mutability flags.

The rule of thumb for setting mutability flags is to always use the PendingIntent.FLAG_IMMUTABLE flag, except for some special cases.

Use cases for the PendingIntent.FLAG_MUTABLE flag can be taken from the documentation.

One use case in our app was the PendingIntent I used for scheduling the alarms with the AlarmManager. Following the documentation, the usage of the PendingIntent.FLAG_MUTABLE flag, in this case, enables the system to determine if a repeatable alarm has been triggered multiple times and notify an app accordingly.


Now that we have discussed the theoretical part, we can take a look at the actual code adaptions we have to do.

  • If you have no other flags to set, an PendingIntent​ initialization now simply looks like the following:
  • If you are in a situation like mine and you already have other flags set to your PendingIntent, you can simply combine the existing flag with the mutability flag like the following code snippet shows:

5. Use of WorkManager

Furthermore, if you are using the Android WorkManager, make sure to update the library dependency to at least the following version:

If you try to schedule work with the, WorkManager you will otherwise experience an IllegalArgumentException error ​with the following exception message:

PACKAGE_NAME: Targeting S+ (version 31 and above) requires that one of FLAG_IMMUTABLE or FLAG_MUTABLE be specified when creating a PendingIntent.

Strongly consider using FLAG_IMMUTABLE, only use FLAG_MUTABLE if some functionality depends on the PendingIntent being mutable, e.g. if it needs to be used with inline replies or bubbles.

6. Unsafe Intent Launches:

The next point which affected our app was the new “unsafe intent launches” restrictions.

With targeting Android 12, we are now being asked to make sure we don’t use unsafe intent launches and to be more specific, be more aware of the usage of intents.

Besides that, the documentation states that you should watch out for the overuse of putExtras(Intent) or putExtras(Bundle) calls. Also, watch out for bundles that are too large. The upper bound of the possible buffer storage is only 1MB.

  • Only reference large files. Never try to pass a whole, Bitmap for example, to an Intent. You will run the risk of Transaction Too Large Exception.
  • Also, make sure the exported flag of the respective components is set to false for internal Intent launches.
  • The third point is the avoidance of using nested Intents. By that term, we understand passing a Intent to another Intent as an extra, which, on the other hand, calls startActivity().

To fix this problem, the usage of PendingIntents is recommended.

Detecting Unsafe Intent Launches:

We started by initializing a VmPolicy with the help of the StrictMode VmPolicy Builder.

By overriding the onCreate() function of your Application class and adding the VmPolicy with detectUnsafeIntentLaunch() set, as the following code snippet shows. We could look for possible unsafe intent launches that would then be printed out to the console.

7. Upgrade existing splash screen implementation:

Starting in Android 12, the system always applies the new Android system default splash screen on cold and warm starts for all apps. By default, this system's default splash screen is constructed using your app’s launcher icon element and the window Background of your theme (if it's a single color).

By default, all apps on Android 12 will show a splash screen with the app icon at the center. If you use an Activity or Fragment to show a splash screen, the default Android 12 splash screen would come first, then your custom splash screen would follow. Now you have two splash screens that will likely look completely different. More inconsistency in design and more intrusive to your users. Now you need to add support for the Android 12 splash screen so it has a more consistent look and is less intrusive to your users.

The API supports Animated Icons via AnimatedVectorDrawable or AnimationDrawable.

With older API levels, we already have backwards compatibility.

  • SplashScreen compat library

You can use the SplashScreen API directly, but we strongly recommend using the Androidx SplashScreen compat library instead. The compat library uses the SplashScreen API, enables backward compatibility, and creates a consistent look and feel for splash screen display across all Android versions. This guide is written using the compat library.

If you choose to migrate using the SplashScreen API directly, your splash screen appears the same as before on Android 11, but starting with Android 12, your splash screen will look and feel like it's in Android 12.

Steps to migrate your splash screen:

In the build.gradle file, change your compileSdkVersion and include the SplashScreen compat library in dependencies.

  • Create a theme with a parent Theme.SplashScreen, and set the values of postSplashScreenThemethe theme that the Activity ​should use and windowSplashScreenAnimatedIcon to a drawable or animated drawable. The other attributes are optional.
  • If you want to add a background color underneath your icon, you can use the Theme.SplashScreen.IconBackground theme and set the windowSplashScreenIconBackground attribute.
  • In the manifest, replace the theme of the starting activity with the theme you created in the previous step.
  • Call installSplashScreen in the starting activity before calling super.onCreate().
  • You can also use installSplashScreen to modify the animation or to keep the splash screen on screen for longer.
For more details on customizing the animation, see Keep the splash screen on-screen for longer periods and customise the animation for dismissing the splash screen.


In this article, we've shared our key experiences and lessons learned from the migration process of Halodoc apps.

Note that we just talked about the restrictions that affected our applications. There are other restrictions you will encounter when migrating to your specific app.

Just to name a few you might stumble upon, you could face some restrictions regarding location permission limitations (the user can now decide if he wants to allow fine or course location permission) or the adaptions to Bluetooth permissions.

If you want to take a look at other restrictions that might affect your app, check out the official documentation on Android 12 behavior changes.

Join us

Scalability, reliability and maintainability are the three pillars that govern what we build at Halodoc Tech. We are actively looking for engineers at all levels and  if solving hard problems with challenging requirements is your forte, please reach out to us with your resumé at

About Halodoc

Halodoc is the number 1 all around Healthcare application in Indonesia. Our mission is to simplify and bring quality healthcare across Indonesia, from Sabang to Merauke. We connect 20,000+ doctors with patients in need through our Tele-consultation service. We partner with 3500+ pharmacies in 100+ cities to bring medicine to your doorstep. We've also partnered with Indonesia's largest lab provider to provide lab home services, and to top it off we have recently launched a premium appointment service that partners with 500+ hospitals that allow patients to book a doctor appointment inside our application. We are extremely fortunate to be trusted by our investors, such as the Bill & Melinda Gates Foundation, Singtel, UOB Ventures, Allianz, GoJek, Astra, Temasek and many more. We recently closed our Series C round and In total have raised around USD$180 million for our mission. Our team works tirelessly to make sure that we create the best healthcare solution personalised for all of our patient's needs, and are continuously on a path to simplify healthcare for Indonesia.

Pawan kushwaha

I am an Android developer with almost 8+ years of experience whose goal is to work in a dynamic professional environment with a solid organization and utilize my creative skills.