androidreact-nativenpmicons

in React Native, How to change Android app icon "dynamically"?


I looked up on the internet and found some packages like this which doesn't support React Native Android and this which not well documented...

Are there any other modules for React Native Android for that? Or is there any way i can program app to change the icon dynamically.

For example, when dark theme for system, icon changes to dark theme. Or like a calendar icon changes showing current date on icon. Or clock showing hands movement as seconds goes by, etc.


Solution

  • There are few steps to enable dynamic icon change on Android. Seems a lot of work, but actually very easy.

    1. Put all your icons on mipmap folder.

    2. AndroidManifest.xml (Create activity alias for each icon)

    Under <application>, Main <activity> should not contain intent-filter & android:exported should be set to true. this will make MainActivity available always without showing shortcut icon.

    <activity
        android:name=".MainActivity"
        android:label="Test App"
        android:configChanges="keyboard|keyboardHidden|orientation|screenSize|uiMode"
        android:launchMode="singleTask"
        android:windowSoftInputMode="adjustResize"
        android:exported="true"
    >
    </activity>
    

    3. Now, for each icon you want to display, create <activity-alias>. Set android:enabled to true for your defailt icon. All enabled icons will be displayed on desktop. So make sure you enable only one activity alias.

    <activity-alias
        android:label="Test App :: Default"
        android:icon="@mipmap/icon1"
        android:name="First"
        android:enabled="true"
        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:label="Test App :: Special"
    android:icon="@mipmap/icon2"
    android:name="Second"
    android:enabled="false"
    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:label="Test App :: Very Special"
        android:icon="@mipmap/icon3"
        android:name="Third"
        android:enabled="false"
        android:targetActivity=".MainActivity">
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />
            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
    </activity-alias>
    

    4. Create a Java Classes

    Create empty class for each of your activity alias.

    package com.your.package;
    
    class First {
    }
    

    5. Create IconChanger class

    @ReactMethod allows accessing it from react-native. You can change com.your.package to BuildConfig.APPLICATION_ID to avoid writing your package name multiple times.

    package your.package.name;
    import android.os.Bundle;
    import com.facebook.react.ReactActivity;
    import com.facebook.react.bridge.NativeModule;
    import com.facebook.react.bridge.ReactApplicationContext;
    import com.facebook.react.bridge.ReactContext;
    import com.facebook.react.bridge.ReactContextBaseJavaModule;
    import com.facebook.react.bridge.ReactMethod;
    import java.util.Map;
    import java.util.HashMap;
    import android.content.pm.PackageManager;
    import android.content.ComponentName;
    import com.facebook.react.bridge.Promise;
    import android.os.Bundle;  
    import android.widget.Toast; 
    
    public class IconChanger extends ReactContextBaseJavaModule {
    
        private final ReactApplicationContext reactContext;
    
        public IconChanger(ReactApplicationContext reactContext) {
            super(reactContext);
            this.reactContext = reactContext;
        }
    
        @Override
        public String getName() {
            return "IconChanger";
        }
    
        @ReactMethod
        public void changeIcon(String enableIntent, String disableIntent, Promise response) {
            try {
                PackageManager packageManager = this.reactContext.getPackageManager();
                int action;
                String activeIntent="com.your.package."+enableIntent;
    
                Toast.makeText( this.reactContext,"Enabling "+enableIntent,Toast.LENGTH_SHORT).show();
    
                packageManager.setComponentEnabledSetting(
                    new ComponentName("com.your.package", activeIntent), 
                    PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
                    PackageManager.DONT_KILL_APP
                );
    
                if(!disableIntent.equals(null) && !disableIntent.equals(enableIntent)){
                    activeIntent="com.your.package.."+disableIntent;
                    Toast.makeText( this.reactContext,"Disabling "+disableIntent,Toast.LENGTH_SHORT).show();
    
                    packageManager.setComponentEnabledSetting(
                        new ComponentName("com.your.package.", activeIntent), 
                        PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
                        PackageManager.DONT_KILL_APP
                    );
                }
                response.resolve(enableIntent);
            } catch (Exception e) {
                response.reject("Error", e);
            }
        }
    }
    

    6. Register it as NativeModule

    Create CustomPackages.java

    package com.your.package;
    import com.facebook.react.ReactPackage;
    import com.facebook.react.bridge.NativeModule;
    import com.facebook.react.bridge.ReactApplicationContext;
    import com.facebook.react.uimanager.ViewManager;
    
    import java.util.ArrayList;
    import java.util.Collections;
    import java.util.List;
    
    public class CustomPackages implements ReactPackage {
    
        @Override
        public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
            return Collections.emptyList();
        }
    
        @Override
        public List<NativeModule> createNativeModules(
                ReactApplicationContext reactContext) {
            List<NativeModule> modules = new ArrayList<>();
    
            modules.add(new IconChanger(reactContext));
    
            return modules;
        }
    }
    

    7. Now add this package to your MainApplication.java

    @Override
    protected List<ReactPackage> getPackages() {
        @SuppressWarnings("UnnecessaryLocalVariable")
        List<ReactPackage> packages = new PackageList(this).getPackages();
        packages.add(new CustomPackages());
        return packages;
    }
    

    8. Access IconChanger from react-native

    import { NativeModules } from 'react-native';
    const { IconChanger } = NativeModules;
    IconChanger.changeIcon(newIcon, oldIcon);
    

    [NOTES]