đ How I Built IcoraâââA Small Experiment That Turned Into a Lesson on Dynamic Android Icons

Changing the launcher icon dynamically is one of those common questions in the Android community:
âCan I update my app icon at runtime?â
The short answer: Yesâââbut only if the icons are pre-packaged.
Android doesnât allow replacing launcher icons with downloaded images, but it does allow switching between multiple predefined icons using activity-alias.
To explore this idea and help developers learn the correct implementation, I built Icora, a lightweight Android demo app that switches app icons instantly using Jetpack Compose + activity aliases.
This blog walks through the full implementation.
đŻ Why Icora Exists
A lot of developers try to replace launcher icons using Bitmaps, downloads, or runtime assetsâââbut Android blocks those approaches.
The proper solution is:
- Prepackage multiple icons
- Assign each one to an <activity-alias>
- Enable only one alias at a time
- Disable all others
Icora demonstrates this cleanly with Compose buttons for each icon.
𧊠How Dynamic Icons Actually Work
Android launchers show app icons based on:
android:icon=""
android:roundIcon=""
These must reference resources inside the APK.
Instead of modifying these at runtime, Icora defines many launchable aliases, each with a different icon:
MainActivityAliasA â icon A
MainActivityAliasB â icon B
MainActivityAliasC â icon C
...
All aliases point to the same activity (MainActivity), but only one is enabled at a time.
đ Manifest Setup (activity-alias)
Below is the essential part of the manifest:
<activity
android:name=".MainActivity"
android:exported="true" />
<activity-alias
android:name=".MainActivityAliasA"
android:enabled="true"
android:exported="true"
android:icon="@mipmap/ic_launcher_a"
android:roundIcon="@mipmap/ic_launcher_a_round"
android:targetActivity=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity-alias>
<activity-alias
android:name=".MainActivityAliasB"
android:enabled="false"
android:exported="true"
android:icon="@mipmap/ic_launcher_b"
android:roundIcon="@mipmap/ic_launcher_b_round"
android:targetActivity=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity-alias>
Repeat for C, D, E, FâŚ
đ§ Switching Icons Using PackageManager
The heart of the project is this Kotlin function:
fun setAppIcon(context: Context, aliasClassName: String) {
val pm = context.packageManager
val packageName = context.packageName
val aliases = listOf(
"$packageName.MainActivityAliasA",
"$packageName.MainActivityAliasB",
"$packageName.MainActivityAliasC",
"$packageName.MainActivityAliasD",
"$packageName.MainActivityAliasE",
"$packageName.MainActivityAliasF",
"$packageName.MainActivityAliasDefault",
)
for (alias in aliases) {
val component = ComponentName(packageName, alias)
val state = if (alias == "$packageName.$aliasClassName") {
PackageManager.COMPONENT_ENABLED_STATE_ENABLED
} else {
PackageManager.COMPONENT_ENABLED_STATE_DISABLED
}
pm.setComponentEnabledSetting(
component,
state,
PackageManager.DONT_KILL_APP
)
}
}This function:
- Enables the selected alias
- Disables all others
- Ensures only one launcher icon is visible
đ¨ UI Implementation with Jetpack Compose
Icora uses a very simple Compose UI to trigger icon changes:
OptIn(ExperimentalFoundationApi::class, DelicateCoroutinesApi::class)
@Composable
fun FiveButtonGrid() {
val buttons = listOf("A", "B", "C", "D", "E","F")
val context = LocalContext.current
LazyVerticalGrid(
columns = GridCells.Fixed(2),
modifier = Modifier
.fillMaxSize()
.padding(16.dp),
horizontalArrangement = Arrangement.spacedBy(12.dp),
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
items(buttons.size) { index ->
Button(
onClick = {
val alias = when(index) {
0-> "MainActivityAliasA"
1-> "MainActivityAliasB"
2-> "MainActivityAliasC"
3-> "MainActivityAliasD"
4-> "MainActivityAliasE"
5-> "MainActivityAliasF"
else -> {
"MainActivityAliasDefault"
}
}
CustomAppIcon.setAppIcon(context,alias)
},
modifier = Modifier
.fillMaxWidth()
.height(50.dp)
) {
Text(text = buttons[index])
}
}
}
}
Each button enables one alias â instantly switching the icon.
đ How Fast Does the Icon Update?
Depending on the launcher:
- Pixel Launcher: 1â2Â seconds
- Samsung One UI: 2â5Â seconds
- Xiaomi MIUI: may require refreshing home screen
đ§Ş Common Issues &Â Fixes
âď¸ Multiple icons appearing?
Make sure:
- Only one alias has android:enabled="true" initially
- The main <activity> does NOT have the LAUNCHER intent-filter
âď¸ Icon not updating?
- Launcher is cachingâââwait a moment
- Try dragging the app icon on home screen
- Some OEM launchers refresh slower
đĽ Try Icora or Fork the Project
Feel free to explore, fork, and experiment with Icora.
Itâs a great starting point for:
- Theming apps
- Seasonal icon changes
- Premium icon packs
- Customizable launcher appearance
đ Final Thoughts
Dynamic icon switching on Android is a powerful featureâââbut often misunderstood. Icora demonstrates the proper, stable, and officially supported way to do it.
If youâre building a customizable Android experience, this technique will open up a lot of possibilities.
đŚ Full Source Code
Explore the complete implementation of Icora. https://github.com/ajithvgiri/icora