flutter_dynamic_icon
A flutter plugin for dynamically changing app icon and app icon batch number in the mobile platform. Supports
only iOS
(with version >
10.3
).
Usage
To use this plugin, add
flutter_dynamic_icon
as a
dependency in your pubspec.yaml file
.
Getting Started
Check out the
example
directory for a sample app using
flutter_dynamic_icon
.
iOS Integration
Index
-
2x
-
120px x 120px
-
3x
-
180px x 180px
To integrate your plugin into the iOS part of your app, follow these steps
-
First let us put a few images for app icons, they are
-
These icons shouldn't be kept in
Assets.xcassets
folder, but outside. When copying to Xcode, you can select 'create folder references' or 'create groups', if not you will get and error when uploading the build to the AppStore saying: (Thanks to @nohli for this observation)
TMS-90032: Invalid Image Path - - No image found at the path referenced under key 'CFBundleAlternateIcons':...
Here is my directory structure:
-
Next, we need to setup the
Info.plist
-
Add
Icon files (iOS 5)
to the Information Property List
-
Add
CFBundleAlternateIcons
as a dictionary, it is used for alternative icons
-
Set 3 dictionaries under
CFBundleAlternateIcons
, they are correspond to
teamfortress
,
photos
, and
chills
-
For each dictionary, two properties —
UIPrerenderedIcon
and
CFBundleIconFiles
need to be configured
-
If the sub-property
UINewsstandIcon
is showing under
Icon files (iOS 5)
and you don't plan on using it (it is intended for use with Newstand features), erase it or the app will get rejected upon submission on the App Store
Note that if you need it work for iPads, You need to add these icon declarations in
CFBundleIcons~ipad
as well.
See here
for more details.
Here is my
Info.plist
after adding Alternate Icons
Screenshot
Raw
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIcons</key>
<key>CFBundleAlternateIcons</key>
<key>chills</key>
<key>CFBundleIconFiles</key>
<array>
<string>chills</string>
</array>
<key>UIPrerenderedIcon</key>
<false/>
</dict>
<key>photos</key>
<key>CFBundleIconFiles</key>
<array>
<string>photos</string>
</array>
<key>UIPrerenderedIcon</key>
<false/>
</dict>
<key>teamfortress</key>
<key>CFBundleIconFiles</key>
<array>
<string>teamfortress</string>
</array>
<key>UIPrerenderedIcon</key>
<false/>
</dict>
</dict>
<key>CFBundlePrimaryIcon</key>
<key>CFBundleIconFiles</key>
<array>
<string>chills</string>
</array>
<key>UIPrerenderedIcon</key>
<false/>
</dict>
</dict>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>flutter_dynamic_icon_example</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>$(FLUTTER_BUILD_NAME)</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>$(FLUTTER_BUILD_NUMBER)</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
<string>Main</string>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UIViewControllerBasedStatusBarAppearance</key>
<false/>
</dict>
</plist>
Now, you can call
FlutterDynamicIcon.setAlternateIconName
with the
CFBundleAlternateIcons
key as the argument to set that icon.
Dart/Flutter Integration
From your Dart code, you need to import the plugin and use it's static methods:
import 'package:flutter_dynamic_icon/flutter_dynamic_icon.dart';
try {
if (await FlutterDynamicIcon.supportsAlternateIcons) {
await FlutterDynamicIcon.setAlternateIconName("photos");
print("App icon change successful");
return;
} on PlatformException {} catch (e) {}
print("Failed to change app icon");
// set batch number
try {
await FlutterDynamicIcon.setApplicationIconBadgeNumber(9399);
} on PlatformException {} catch (e) {}
// gets currently set batch number
int batchNumber = FlutterDynamicIcon.getApplicationIconBadgeNumber();
Check out the
example
app for more details
Screenrecord
Showing App Icon change
Showing Batch number on app icon change in SpringBoard
Reference
This was made possible because this blog. I borrowed a lot of words from this blog.
https://medium.com/ios-os-x-development/dynamically-change-the-app-icon-7d4bece820d2
Use this package as a library
Depend on it
Run this command:
With Flutter:
$ flutter pub add flutter_dynamic_icon
This will add a line like this to your package's pubspec.yaml (and run an implicit
flutter pub get
):
dependencies:
flutter_dynamic_icon: ^2.1.0
Alternatively, your editor might support
flutter pub get
. Check the docs for your editor to learn more.
Import it
Now in your Dart code, you can use:
import 'package:flutter_dynamic_icon/flutter_dynamic_icon.dart';
example/lib/main.dart
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_dynamic_icon/flutter_dynamic_icon.dart';
void main() => runApp(MaterialApp(home: MyApp()));
class MyApp extends StatefulWidget {
@override
_MyAppState createState() => _MyAppState();
class _MyAppState extends State<MyApp> {
int batchIconNumber = 0;
String currentIconName = "?";
bool loading = false;
bool showAlert = true;
TextEditingController controller = TextEditingController();
@override
void initState() {
super.initState();
FlutterDynamicIcon.getApplicationIconBadgeNumber().then((v) {
setState(() {
batchIconNumber = v;
FlutterDynamicIcon.getAlternateIconName().then((v) {
setState(() {
currentIconName = v ?? "`primary`";
@override
Widget build(BuildContext context) {
GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey();
return Scaffold(
key: _scaffoldKey,
appBar: AppBar(
title: const Text('Dynamic App Icon'),
body: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 28),
child: ListView(
children: <Widget>[
Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
"Current batch number: $batchIconNumber",
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.headline4,
TextField(
controller: controller,
inputFormatters: <TextInputFormatter>[
FilteringTextInputFormatter.allow(RegExp("\\d+")),
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText: "Set Batch Icon Number",
suffixIcon: loading
? Padding(
padding: const EdgeInsets.all(16.0),
child: CircularProgressIndicator(
// strokeWidth: 2,
: IconButton(
icon: Icon(Icons.send),
onPressed: () async {
setState(() {
loading = true;
try {
await FlutterDynamicIcon
.setApplicationIconBadgeNumber(
int.parse(controller.text));
batchIconNumber = await FlutterDynamicIcon
.getApplicationIconBadgeNumber();
if (this.mounted) {
ScaffoldMessenger.of(context)
.hideCurrentSnackBar();
ScaffoldMessenger.of(context)
.showSnackBar(SnackBar(
content:
Text("Successfully changed batch number"),
} on PlatformException {
if (this.mounted) {
ScaffoldMessenger.of(context)
.hideCurrentSnackBar();
ScaffoldMessenger.of(context)
.showSnackBar(SnackBar(
content: Text("Failed to change batch number"),
} catch (e) {
if (this.mounted) {
ScaffoldMessenger.of(context)
.hideCurrentSnackBar();
ScaffoldMessenger.of(context)
.showSnackBar(SnackBar(
content: Text("Failed to change batch number"),
setState(() {
loading = false;
SizedBox(
height: 28,
Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
"Current Icon Name: $currentIconName",
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.headline4,
SwitchListTile(
title: Text("Show Alert"),
subtitle: Text(
"Disable alert at your own risk as it uses a private/undocumented API"),
value: showAlert,
onChanged: (value) {
setState(() {
showAlert = value;
OutlinedButton.icon(
icon: Icon(Icons.ac_unit),
label: Text("Team Fortress"),
onPressed: () async {
try {
print(await FlutterDynamicIcon.supportsAlternateIcons);
if (await FlutterDynamicIcon.supportsAlternateIcons) {
await FlutterDynamicIcon.setAlternateIconName(
"teamfortress",
showAlert: showAlert);
ScaffoldMessenger.of(context).hideCurrentSnackBar();
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text("App icon change successful"),
FlutterDynamicIcon.getAlternateIconName().then((v) {
setState(() {
currentIconName = v ?? "`primary`";
return;
} on PlatformException {
} catch (e) {}
ScaffoldMessenger.of(context).hideCurrentSnackBar();
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text("Failed to change app icon"),
OutlinedButton.icon(
icon: Icon(Icons.ac_unit),
label: Text("Photos"),
onPressed: () async {
try {
if (await FlutterDynamicIcon.supportsAlternateIcons) {
await FlutterDynamicIcon.setAlternateIconName("photos",
showAlert: showAlert);
ScaffoldMessenger.of(context).hideCurrentSnackBar();
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text("App icon change successful"),
FlutterDynamicIcon.getAlternateIconName().then((v) {
setState(() {
currentIconName = v ?? "`primary`";
return;
} on PlatformException {
} catch (e) {}
ScaffoldMessenger.of(context).hideCurrentSnackBar();
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text("Failed to change app icon"),
OutlinedButton.icon(
icon: Icon(Icons.ac_unit),
label: Text("Chills"),
onPressed: () async {
try {
if (await FlutterDynamicIcon.supportsAlternateIcons) {
await FlutterDynamicIcon.setAlternateIconName("chills",
showAlert: showAlert);
ScaffoldMessenger.of(context).hideCurrentSnackBar();
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text("App icon change successful"),
FlutterDynamicIcon.getAlternateIconName().then((v) {
setState(() {
currentIconName = v ?? "`primary`";
return;
} on PlatformException {
} catch (e) {}
ScaffoldMessenger.of(context).hideCurrentSnackBar();
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text("Failed to change app icon"),
SizedBox(
height: 28,
OutlinedButton.icon(
icon: Icon(Icons.restore_outlined),
label: Text("Restore Icon"),
onPressed: () async {
try {
if (await FlutterDynamicIcon.supportsAlternateIcons) {
await FlutterDynamicIcon.setAlternateIconName(null,
showAlert: showAlert);
ScaffoldMessenger.of(context).hideCurrentSnackBar();
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text("App icon restore successful"),
FlutterDynamicIcon.getAlternateIconName().then((v) {
setState(() {
currentIconName = v ?? "`primary`";
return;
} on PlatformException {
} catch (e) {}
ScaffoldMessenger.of(context).hideCurrentSnackBar();
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text("Failed to change app icon"),