adaptive_app_icon
Switch your app's home-screen icon at runtime between pre-bundled variants — with a config-driven codegen system, a type-safe Pigeon bridge, and a drop-in gallery widget for icon selection.
![]()
Android (left) and iOS (right): picking a variant from IconGallery, the iOS system
confirmation alert, and the new icon on the home screen.
Full-resolution video →
| iOS | UIApplication.setAlternateIconName(_:) (iOS 10.3+) |
| Android | <activity-alias> toggling via PackageManager.setComponentEnabledSetting |
Important
This package only switches between icons that are already bundled in the binary at build time. Neither iOS nor Android can download a new icon at runtime — every variant must ship inside the app. Adding a new icon later requires a new build and store submission.
How it works (and its limits)
-
iOS shows an unavoidable system confirmation alert every time the icon changes. This is enforced by the OS and cannot be suppressed. If the user cancels, the switch fails and the gallery selection rolls back automatically.
-
Android enables exactly one
<activity-alias>at a time (the main activity is never toggled). Because Android finishes the current task when the launcher component it was launched through is disabled, a switch can't happen silently mid-session. Choose how it lands withandroidApplyMode:AndroidApplyModeBehavior whenBackgrounded(default)Applied when the app is next backgrounded — the app never visibly closes; the icon is already updated by the time the user is on the home screen. immediatelyApplied now; Android closes the app and the user reopens it. Pair it with IconGallery.confirmChangeto warn the user first.iOS ignores
androidApplyModeentirely.Why no auto-relaunch? Android 10+ blocks background activity starts, so an app cannot reliably relaunch itself after being closed. Prefer
whenBackgrounded(no close at all), or useimmediatelyand tell the user to reopen the app.
Quick start (3 steps)
1. Drop your icon art in assets/ and declare it in pubspec.yaml
One square PNG per icon — 1024×1024 is the recommended master size. Declare them as Flutter assets, then list the variants:
flutter:
assets:
- assets/icons/
adaptive_app_icon:
icons:
# The first entry (or one with `default: true`) is the primary icon.
- name: classic
image: assets/icons/classic.png
label: "Classic"
- name: neon
image: assets/icons/neon.png
label: "Neon"
That is the whole config for the common case — every platform asset is rendered
from image:. name must be lower_snake_case: it becomes an iOS app-icon set
name, an Android component name, and the string you pass to setIcon.
Optional keys
| Key | Scope | Default |
|---|---|---|
label |
per icon | the name |
default |
per icon | first entry in the list |
background |
per icon or top level | #ffffff — composited under the iOS icon, which cannot have an alpha channel |
preview |
per icon | the image — override to show different art in the gallery |
android_asset |
per icon | ic_launcher for the default, ic_launcher_<name> otherwise |
Omit image: on an icon to keep hand-maintained assets instead: supply
ios/Runner/Assets.xcassets/<name>.appiconset and an android_asset resource
yourself, and codegen will wire them up without rendering anything.
The config can also live in a standalone adaptive_app_icon.yaml at the project
root; pubspec.yaml wins if both declare an adaptive_app_icon: section.
2. Run the codegen
dart run adaptive_app_icon:generate_icon_config
This automatically:
- Renders
res/mipmap-*/launcher PNGs at all five densities (mdpi→xxxhdpi), preserving alpha so launchers can mask them. - Renders
ios/Runner/Assets.xcassets/<name>.appiconsetfor each icon, flattened ontobackgroundbecause iOS renders an icon with an alpha channel as a black square. - Points the Xcode asset-catalog build settings
(
ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETSandASSETCATALOG_COMPILER_ALTERNATE_APPICON_NAMES) at those sets on every build configuration, so alternates are bundled without touching Copy Bundle Resources — and Xcode writesCFBundleAlternateIconsinto the builtInfo.plistitself. - Injects
<activity-alias>blocks intoandroid/app/src/main/AndroidManifest.xml(one enabled default, the rest disabled), wrapped inadaptive_app_icon:begin/endmarkers so re-runs stay idempotent. - Generates
lib/app_icons.g.dart— a typedList<AppIconAsset>plus the Android component map and aninitDynamicAppIcon()helper. No raw strings downstream. - Validates loudly: unreadable sources, sources below 1024px, names that
aren't valid platform identifiers, and previews that exist but aren't declared
under
flutter: assets:(a silent placeholder at runtime otherwise).
Re-run it any time you change the config or the art. Useful flags: --dry-run,
--verbose, --no-images (wire up existing assets without re-rendering), and
--project <dir>.
3. Register the config and drop in the gallery
import 'package:adaptive_app_icon/adaptive_app_icon.dart';
import 'app_icons.g.dart'; // generated
void main() {
WidgetsFlutterBinding.ensureInitialized();
initDynamicAppIcon(); // register the generated config
runApp(const MyApp());
}
// ...on a settings screen:
FutureBuilder<String?>(
future: DynamicAppIcon.getCachedIcon(), // avoids a cold-start flash
builder: (context, snapshot) => IconGallery(
icons: DynamicAppIcon.icons,
initialSelectedName: snapshot.data,
onChanged: (icon) => debugPrint('Switched to ${icon.label}'),
),
);
Or call the API directly:
await DynamicAppIcon.setIcon('neon'); // switch
await DynamicAppIcon.setIcon(null); // back to primary
final current = await DynamicAppIcon.getCurrentIcon(); // 'neon' or null
final ok = await DynamicAppIcon.isSupported();
What the codegen handles vs. what you must do manually
| Handled automatically | You must do manually |
|---|---|
| Android density PNGs and iOS app-icon sets, rendered from one source each | Draw the art — one square PNG per icon (the codegen never invents art) |
| Xcode asset-catalog build settings, so alternates bundle without Copy Bundle Resources surgery | Declare the assets folder under flutter: assets: so gallery previews load |
AndroidManifest.xml aliases (incl. moving the launcher off MainActivity) |
|
| Typed Dart config | |
| Source, name, and preview validation |
What lands where
-
iOS. Every icon becomes an app-icon set in
Assets.xcassets: the default takes overAppIcon.appiconset, and each alternate gets<name>.appiconset. Naming the set after the icon is deliberate — the name Xcode registers inCFBundleAlternateIconsis then the same string you pass tosetIcon, so nothing has to be translated at runtime. Sets are written in the single-size (1024) Xcode 14+ format and the asset compiler derives the device sizes.Icons are flattened onto
backgroundbecause iOS renders an icon with an alpha channel as a black square. Setbackground:to match your art; the default is white. -
Android. Each icon is rendered to
res/mipmap-<density>/<android_asset>.pngat all five densities with alpha intact. The codegen turns every icon into an<activity-alias>and removes theLAUNCHERintent-filter from yourMainActivity, so the currently-running activity is never disabled during a switch (doing so crashes the app). Icons only get legacy launcher PNGs today — adaptive icons (mipmap-anydpi-v26/*.xml) are not generated, though you can hand-maintain them and pointandroid_assetat them.
Gallery previews default to the same image: you already declared, so there is
no fourth file to keep in sync. Override preview: only when you want the
in-app thumbnail to differ from the launcher art.
Migrating from the loose-PNG layout
Earlier versions bundled alternates as loose AppIcon-Neon@2x.png files in
ios/Runner/ and needed a manual Copy Bundle Resources step. If you are
coming from that:
- Delete the
ios_asset:keys — codegen now errors on them, since app-icon set names are derived. Addimage:to each icon instead. - Delete the loose
AppIcon-*@2x.png/@3x.pngfiles in Xcode (which also removes them from Copy Bundle Resources). Codegen warns while any remain. - Re-run the codegen. It also strips the
CFBundleIconsblock it used to write intoios/Runner/Info.plist, which now points at files that no longer exist.
Known limitations & review notes
Note
Android notification icons are separate. The small icon shown in the status bar / notifications is set independently of the launcher icon and will not follow an icon switch. Update it via your notification code if needed.
Warning
iOS App Store review. Every bundled icon variant is visible to reviewers at submission time (they appear in the "App Icons" section). Icons cannot be added over-the-air — introducing a new variant later requires a new build and a new review.
Other notes:
- iOS always shows a confirmation alert on switch; there is no API to suppress it.
- iPad. This package writes the iPhone
CFBundleIconsentry. For dedicated iPad alternate icons add aCFBundleIcons~ipadentry with 152/167 px assets. - Android package assumption. Generated component names use your module's
namespace. If yourapplicationIddiffers from yournamespace, verify the alias names resolve on-device.
API reference
DynamicAppIcon
| Member | Description |
|---|---|
initialize({icons, androidComponents}) |
Register the generated config. Call once at startup (via initDynamicAppIcon()). |
setIcon(String? name, {AndroidApplyMode androidApplyMode}) |
Activate name, or the primary icon when null. See the apply-mode table above. |
getCurrentIcon() |
Active icon name, or null for the primary icon. |
isSupported() |
Whether switching is available (platform supports it and >1 icon configured). |
getCachedIcon() |
Last selection from shared_preferences — seed cold-start UI with this. |
icons / defaultIconName |
The registered icon list / the default icon's name. |
Failures throw a PlatformException with code UNSUPPORTED, BAD_ARGS, or
SET_FAILED.
IconGallery
A GridView that renders preview thumbnails, checkmarks the active icon, and
performs an optimistic switch that rolls back if setIcon throws (e.g. the
user cancels iOS's alert). Provide itemBuilder to fully restyle each tile
without forking the widget.
Regenerating the native bridge
The Dart⇄Swift⇄Kotlin bindings are generated by Pigeon from
pigeons/messages.dart:
dart run pigeon --input pigeons/messages.dart
Example
See example/ for a working app with three icon variants, the
gallery wired onto a settings screen, and cold-start state restoration.
Libraries
- adaptive_app_icon
- Runtime app-icon switching for Flutter, with config-driven codegen and a drop-in gallery widget.