Implementing Dynamic App Icons in Flutter: A Step-by-Step Guide
Nowadays, almost every application includes premium features, providing additional benefits for users. For premium users, it's a great idea to change the app icon, as many apps like Zomato, Swiggy etc. do.
However, changing the app icon dynamically requires some effort. For Android or iOS applications, we simply update the respective native files. But for cross-platform frameworks like Flutter, we need to work on both Android and iOS to reflect the changes.
Now, let's discuss a step-by-step guide to adding dynamic icons in Flutter.
Android
Step 1:
For now, I would like to add an icon for a Premium User.
First, we need to add a new activity-alias for the Premium activity to display the new app icon for the premium user.
We need to add a new icons set for the premium app icon in your folder android->app->src->main->res.
You can also add a separate round icon if required.
<activity-alias android:name=".PremiumActivity"
android:enabled="false"
android:exported="true"
android:icon="@mipmap/ic_launcher_premium"
android:roundIcon="@mipmap/ic_launcher_premium_round"
android:targetActivity=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity-alias>
Note: Add all your app deeplinks inside the activity alias, just as you have them inside the main activity, to ensure that deeplinks work correctly.
Step 2:
Now, let's create the plugin to change the icon.
class PremiumIconPlugin(private val context: Context) {
private lateinit var channel: MethodChannel
private val packageName = context.packageName
fun initWith(binaryMessenger: BinaryMessenger) {
channel = MethodChannel(binaryMessenger, "premium_icon")
channel.setMethodCallHandler { call, result ->
when (call.method) {
"updateToPremiumIcon" -> {
updateToPremiumIcon()
result.success(true)
}
"updateToDefaultIcon" -> {
updateToDefaultIcon()
result.success(true)
}
else -> result.notImplemented()
}
}
}
private fun updateToPremiumIcon() {
changeIcon("PremiumActivity", "MainActivity")
}
private fun updateToDefaultIcon() {
changeIcon("MainActivity", "PremiumActivity")
}
private fun changeIcon(newActivity: String, oldActivity: String) {
val packageManager = context.packageManager
val newComponentName = ComponentName(context, "$packageName.$newActivity")
val oldComponentName = ComponentName(context, "$packageName.$oldActivity")
// Enable the new activity alias
packageManager.setComponentEnabledSetting(
newComponentName,
PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
PackageManager.DONT_KILL_APP
)
// Disable the old activity alias
packageManager.setComponentEnabledSetting(
oldComponentName,
PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
PackageManager.DONT_KILL_APP
)
}
}
And initialize the plugin in MainActivity.
That's all we need to do for Android.
iOS
Step 1:
Add the premium app icon set in the Assets.xcassets.
Navigate to Assets -> New iOS App Icon -> add all the icon variations.
Next, open Info.plist [open as source code] and add CFBundleIcons as shown below:
<key>CFBundleIcons</key>
<dict>
<key>CFBundlePrimaryIcon</key>
<dict>
<key>CFBundleIconFiles</key>
<array/>
<key>UIPrerenderedIcon</key>
<false/>
</dict>
<key>CFBundleAlternateIcons</key>
<dict>
<key>PremiumAppIcon</key>
<dict>
<key>CFBundleIconFiles</key>
<array>
<string>PremiumAppIcon</string>
</array>
<key>UIPrerenderedIcon</key>
<true/>
</dict>
</dict>
</dict>
You need to add your alternate icons [premium, festival, event etc..] in CFBundleAlternateIcons.
Ensure the string name matches the app icon name in the Assets.
In this case, I used PremiumAppIcon, so the string is PremiumAppIcon.
Step 2:
Now, let's create the Swift Plugin.
In Swift, we use the setAlternateIconName() method to change the app icon, which simplifies our task. Here's the plugin code:
public class PremiumIconPlugin: NSObject, FlutterPlugin {
public static func register(with registrar: FlutterPluginRegistrar) {
let channel = FlutterMethodChannel(name: "premium_icon", binaryMessenger: registrar.messenger())
let instance = PremiumIconPlugin()
registrar.addMethodCallDelegate(instance, channel: channel)
}
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
switch call.method {
case "updateToPremiumIcon":
updateToPremiumIcon(result: result)
case "updateToDefaultIcon":
updateToDefaultIcon(result: result)
default:
result(FlutterMethodNotImplemented)
}
}
private func updateToPremiumIcon(result: @escaping FlutterResult) {
setAlternateIconName(iconName: "PremiumAppIcon", result: result)
}
private func updateToDefaultIcon(result: @escaping FlutterResult) {
setAlternateIconName(iconName: nil, result: result) // Passing nil to reset to default
}
private func setAlternateIconName(iconName: String?, result: @escaping FlutterResult) {
if UIApplication.shared.supportsAlternateIcons {
UIApplication.shared.setAlternateIconName(iconName) { error in
DispatchQueue.main.async {
if let error = error {
print("Error changing app icon: \(error.localizedDescription)")
// Report the error back to Flutter
result(FlutterError(code: "Icon Change Error", message: "Failed to change icon: \(error.localizedDescription)", details: nil))
} else {
// Success, return true
result(true)
}
}
}
} else {
DispatchQueue.main.async {
// If alternate icons are not supported, report it back to Flutter
result(FlutterError(code: "Unsupported", message: "Alternate icons not supported", details: nil))
}
}
}
}
Make sure the method channel name is consistent in both Android and iOS, as we use this method channel name in Flutter.
Use the same method names for both Android and iOS to maintain consistency.
To change the app icon to the default, pass nil to the setAlternateIconName method. To change to a specific icon, provide the IconName.
Note: There is an important setting to update for dynamic app icons: Open XCode -> Runner -> TARGETS -> Select your Runner -> General -> App Icons and Launch Screen -> Select the check box with name include all app icon assets. Don't forget to initialize the plugin in
AppDelegate.swift.
With these steps, we have successfully created the plugins for both Android and iOS.
Final Part: Setting Up in Flutter
Let's create a premium_icon_changer.dart file and add the necessary methods to change the app icon.
class PremiumIconChanger {
static const MethodChannel _channel = MethodChannel('premium_icon');
static Future<void> updateToDefaultIcon() async {
try {
await _channel.invokeMethod('updateToDefaultIcon');
} on PlatformException catch (e) { //add if required
print("Failed to change icon: ${e.message}");
} on MissingPluginException catch (e) { //add if required
print("Failed to change icon: ${e.message}");
}
}
static Future<void> updateToPremiumIcon() async {
try {
await _channel.invokeMethod('updateToPremiumIcon');
} on PlatformException catch (e) { //add if required
print("Failed to change icon: ${e.message}");
} on MissingPluginException catch (e) { //add if required
print("Failed to change icon: ${e.message}");
}
}
}
Explanation:
-
Method Channel: We define a
MethodChannelnamed'premium_icon'to communicate with the platform-specific code. -
Update to Default Icon: The
updateToDefaultIconmethod invokes theupdateToDefaultIconmethod on the native side. If an error occurs, it prints the error message. -
Update to Premium Icon: The
updateToPremiumIconmethod invokes theupdateToPremiumIconmethod on the native side. Similarly, it prints any error messages that occur.
Now, our class is complete. We just need to use these methods to update the app icon according to our requirements. For instance, you might call updateToPremiumIcon when a user purchases a premium subscription and updateToDefaultIcon when the premium subscription expires or is canceled.
Conclusion
Dynamic app icons can make your app more engaging by showing updates or special features. This guide covered how to set up dynamic icons for both Android and iOS and how to manage them from your Flutter app. By following these steps, you can provide a more personalized experience for your users.