Dynamic App Icon In Android
You’ve probably come across those apps that can pull off a neat trick — changing their app icon, perhaps on your birthday, and then seamlessly switching back to the regular one. It’s the kind of feature that piques your curiosity, making you wonder, ‘How on earth do they do that?’ Well, you’re not alone in your curiosity. Many developers, myself included, have pondered this. It seems like one of those seemingly impossible tasks, but guess what? It’s not! In this article, we’re going to unravel the mystery behind changing Android app icons at runtime. We’ll break it down for you, step by step, and show you that it’s not just doable, but also pretty manageable.
First of all, the app icon is set from the manifest file such like any other app component. Android system reads the manifest file and sets the app icon accordingly. Currently there is no way to change the app icon in runtime. But there is a workaround. That is to use a activity-alias
( If you are not familiar with activity-alias, you can check the official documentation here).
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<application
...
android:icon="YOUR_ICON"
android:roundIcon="YOUR_ICON">
<activity
...
android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity-alias
...
android:icon="YOUR_ICON_2"
android:roundIcon="YOUR_ICON_2"
android:targetActivity=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity-alias>
</application>
</manifest>
As you can see, we have two activities. One is the main activity, and the other is the activity alias. The activity alias is disabled by default. And it has a different icon than the main activity. So when the app is installed, the icon of the main activity will be set. And when we enable the activity alias, the icon of the activity-alias
will be set. So we can change the app icon in runtime by enabling and disabling the activity alias. Now let’s see how to enable and disable the activity alias in runtime. We can do that by using the PackageManager class.
fun Activity.changeIcon() {
packageManager.setComponentEnabledSetting(
ComponentName(
this,
"$packageName.MainActivityAlias"
),
PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
PackageManager.DONT_KILL_APP
)
packageManager.setComponentEnabledSetting(
ComponentName(
this,
"$packageName.MainActivity"
),
PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
PackageManager.DONT_KILL_APP
)
}
As you can see, we are using the setComponentEnabledSetting method of the PackageManager class. We are passing the component name of the activity-alias and the main activity. And we are setting the activity-alias to enabled and the main activity to disabled. So when we call this method, the activity-alias will be enabled and the main activity will be disabled. So the app icon will be changed.
Also, as a software engineer, I do not like this kind of implementation. I would like things to be clean and flexible. Therefore, I believe it is better to update the function above as follows:
fun Activity.changeEnabledComponent(
enabled: String,
disabled: String,
) {
packageManager.setComponentEnabledSetting(
ComponentName(
this,
enabled
),
PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
PackageManager.DONT_KILL_APP
)
packageManager.setComponentEnabledSetting(
ComponentName(
this,
disabled
),
PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
PackageManager.DONT_KILL_APP
)
}
So changing the app icon will be as simple as calling the function with the component names. For instance:
changeEnabledComponent(
enabled = "$packageName.MainActivityAlias",
disabled = "$packageName.MainActivity"
)
Also, as a software engineer, the fact that we are still using hardcodes bothers me a lot. I even want things to be more flexible and more open to change. So I would like to abstract component names a bit more. But here is the challenge, because somehow we need to get the same names that we are using in the manifest file. To solve this issue, instead of using hardcoded strings, we can use BuildConfig with manifestPlaceholders
. In the app-level build.gradle file, we can add the following code: (I assume you are using Kotlin DSL for the build.gradle file.)
private val mainActivity = "YOURPATH.MainActivity"
private val mainActivityAlias = "YOURPATH.MainActivityAlias"
android {
defaultConfig {
...
manifestPlaceholders.apply {
set("main_activity", mainActivity)
set("main_activity_alias", mainActivityAlias)
}
}
buildTypes {
release {
isMinifyEnabled = false
buildConfigField("String", "main_activity", "\"${mainActivity}\"")
buildConfigField("String", "main_activity_alias", "\"${mainActivityAlias}\"")
}
debug {
isDebuggable = true
isMinifyEnabled = false
buildConfigField("String", "main_activity", "\"${mainActivity}\"")
buildConfigField("String", "main_activity_alias", "\"${mainActivityAlias}\"")
}
}
}
Here we are setting the component names to the manifestPlaceholders
and buildConfigField
. So we can access them from the BuildConfig class. Of course, we need to update the manifest file so that it uses the place holders.
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<application ... >
<activity
android:name="${main_activity}"
... >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity-alias
android:name="${main_activity_alias}"
...
android:targetActivity=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity-alias>
</application>
</manifest>
You may now see some errors that says main_activity
and/or main_activity_alias
cannot be found. But you can ignore them, because they will be generated in sync with the build.gradle file. Now we can update our code to use the BuildConfig class.
changeEnabledComponent(
enabled = BuildConfig.main_activity_alias,
disabled = BuildConfig.main_activity
)
Now we have a clean and flexible code. We can change the app icon in runtime by calling the changeEnabledComponent function with the component names. And we can change the component names in the build.gradle file. So we can change the app icon in runtime without changing the code. As a broader sample, take a look at the code below.
val mainActivity = BuildConfig.main_activity
val mainActivityAlias = BuildConfig.main_activity_alias
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
DynamicIconTheme {
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
Screen(
on30Click = {
changeEnabledComponent(
enabled = mainActivityAlias,
disabled = mainActivity
)
},
on60Click = {
changeEnabledComponent(
enabled = mainActivityAlias,
disabled = mainActivity
)
}
)
}
}
}
}
}
You can find the full source code here
Conclusion
In essence, we’ve dismantled the myth that dynamically changing Android app icons at runtime is an unattainable feat. By harnessing the power of activity-alias
in your app’s manifest and skillfully navigating the PackageManager class, we’ve unveiled the path to making it happen. However, the real game-changer lies in our quest for cleaner, more adaptable code, where we’ve made use of placeholders and BuildConfig for ultimate flexibility. Now, you can empower your users to infuse their own unique flair into your app icons, all without getting lost in the code weeds. The key takeaway? This method lets you offer a more personalized user experience, making it particularly valuable for apps seeking that extra touch of user engagement. So, embark on this journey, and let your users leave their mark on your app. Happy coding!
References
https://www.geeksforgeeks.org/how-to-change-app-icon-of-android-programmatically-in-android/
https://developer.android.com/guide/topics/manifest/activity-alias-element
Source code
Love you all.
Stay tune for upcoming blogs.
Take care.