Development Mode
--------------------------------------------------------------------------------------------------------------------------------
AndroidManifest.xml (debug)
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<application>
<activity
android:name=".MainActivity"
android:exported="true"
tools:replace="android:exported">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
</application>
</manifest>
AndroidManifest.xml (main)
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<application
android:label="application_logo"
android:name="${applicationName}">
<activity
android:name=".MainActivity"
android:exported="false"
android:launchMode="singleTop"
android:taskAffinity=""
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme" />
</activity>
<activity-alias
android:name=".MainActivityDefault"
android:enabled="true"
android:exported="true"
android:icon="@mipmap/ic_launcher_default"
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=".MainActivityDiwali"
android:enabled="false"
android:exported="true"
android:icon="@mipmap/ic_launcher_diwali"
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=".MainActivityPremium"
android:enabled="false"
android:exported="true"
android:icon="@mipmap/ic_launcher_premium"
android:targetActivity=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity-alias>
<meta-data
android:name="flutterEmbedding"
android:value="2" />
</application>
<queries>
<intent>
<action android:name="android.intent.action.PROCESS_TEXT"/>
<data android:mimeType="text/plain"/>
</intent>
</queries>
</manifest>
Main.dart
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:supabase_flutter/supabase_flutter.dart';
const platform = MethodChannel('com.example.application_logo/icon');
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Supabase.initialize(
url: '',
publishableKey:
'',
);
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(title: 'Icon Switch Demo', home: const IconTestScreen());
}
}
class IconTestScreen extends StatefulWidget {
const IconTestScreen({super.key});
@override
State<IconTestScreen> createState() => _IconTestScreenState();
}
class _IconTestScreenState extends State<IconTestScreen> {
String _status = 'Idle';
String _currentIcon = 'unknown';
@override
void initState() {
super.initState();
_loadIconFromBackend();
}
// Simulates the automatic startup flow: fetch from Supabase,
// then switch icon automatically.
Future<void> _loadIconFromBackend() async {
try {
final data = await Supabase.instance.client
.from('app_config')
.select('value')
.eq('key', 'active_icon')
.single();
final iconName = data['value'] as String;
await _switchIcon(iconName, fromBackend: true);
} catch (e) {
setState(() => _status = 'Backend fetch failed: $e');
}
}
// Manual trigger, useful for testing each icon without
// touching the Supabase dashboard every time.
Future<void> _switchIcon(String iconKey, {bool fromBackend = false}) async {
try {
final result = await platform.invokeMethod('switchIcon', {
'icon': iconKey,
});
setState(() {
_status = fromBackend
? 'Auto-switched from backend: $result'
: 'Manually switched: $result';
_currentIcon = iconKey;
});
} on PlatformException catch (e) {
setState(() => _status = 'Switch failed: ${e.message}');
}
}
Future<void> _checkCurrentIcon() async {
try {
final result = await platform.invokeMethod('getCurrentIcon');
setState(() => _status = 'Currently active icon: $result');
} on PlatformException catch (e) {
setState(() => _status = 'Check failed: ${e.message}');
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('App Icon Switch — Dev Mode')),
body: Padding(
padding: const EdgeInsets.all(20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Text('Status:\n$_status', style: const TextStyle(fontSize: 16)),
const SizedBox(height: 8),
Text('Selected key: $_currentIcon'),
const SizedBox(height: 24),
ElevatedButton(
onPressed: () => _switchIcon('default'),
child: const Text('Switch to Default Icon'),
),
const SizedBox(height: 8),
ElevatedButton(
onPressed: () => _switchIcon('diwali'),
child: const Text('Switch to Diwali Icon'),
),
const SizedBox(height: 8),
ElevatedButton(
onPressed: () => _switchIcon('premium'),
child: const Text('Switch to Premium Icon'),
),
const SizedBox(height: 24),
OutlinedButton(
onPressed: _checkCurrentIcon,
child: const Text('Check Current Active Icon'),
),
const SizedBox(height: 8),
OutlinedButton(
onPressed: _loadIconFromBackend,
child: const Text('Re-fetch From Supabase'),
),
],
),
),
);
}
}
===================================================================================================================================================================================================
Release Mode
--------------------------------------------------------------
AndroidManifest.xml (main)
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<application
android:label="application_logo"
android:name="${applicationName}">
<activity
android:name=".MainActivity"
android:exported="false"
android:launchMode="singleTop"
android:taskAffinity=""
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme" />
</activity>
<activity-alias
android:name=".MainActivityDefault"
android:enabled="true"
android:exported="true"
android:icon="@mipmap/ic_launcher_default"
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=".MainActivityDiwali"
android:enabled="false"
android:exported="true"
android:icon="@mipmap/ic_launcher_diwali"
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=".MainActivityPremium"
android:enabled="false"
android:exported="true"
android:icon="@mipmap/ic_launcher_premium"
android:targetActivity=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity-alias>
<meta-data
android:name="flutterEmbedding"
android:value="2" />
</application>
<queries>
<intent>
<action android:name="android.intent.action.PROCESS_TEXT"/>
<data android:mimeType="text/plain"/>
</intent>
</queries>
</manifest>
MainActivity.kt
--------------------------------------------------------------------------------------------------------------
package com.example.application_logo
import android.content.ComponentName
import android.content.pm.PackageManager
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel
class MainActivity : FlutterActivity() {
private val CHANNEL = "com.example.application_logo/icon"
// Maps friendly icon keys to alias component names.
// packageName is resolved automatically, so this works
// even if you rename your applicationId later.
private val aliasMap: Map<String, String>
get() = mapOf(
"default" to "$packageName.MainActivityDefault",
"diwali" to "$packageName.MainActivityDiwali",
"premium" to "$packageName.MainActivityPremium"
)
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL)
.setMethodCallHandler { call, result ->
when (call.method) {
"switchIcon" -> {
val icon = call.argument<String>("icon") ?: "default"
val success = switchIcon(icon)
if (success) {
result.success("Icon switched to $icon")
} else {
result.error("INVALID_ICON", "Unknown icon key: $icon", null)
}
}
"getCurrentIcon" -> {
result.success(getCurrentEnabledIcon())
}
else -> result.notImplemented()
}
}
}
/**
* Enables the target alias and disables all others.
* Returns false if the icon key is not recognized.
*/
private fun switchIcon(iconKey: String): Boolean {
val map = aliasMap
val targetAlias = map[iconKey] ?: return false
val pm = packageManager
map.values.forEach { alias ->
val state = if (alias == targetAlias)
PackageManager.COMPONENT_ENABLED_STATE_ENABLED
else
PackageManager.COMPONENT_ENABLED_STATE_DISABLED
pm.setComponentEnabledSetting(
ComponentName(this, alias),
state,
PackageManager.DONT_KILL_APP
)
}
return true
}
/**
* Returns which icon key is currently active, useful for
* debugging / confirming state without adb.
*/
private fun getCurrentEnabledIcon(): String {
val pm = packageManager
for ((key, alias) in aliasMap) {
val state = pm.getComponentEnabledSetting(ComponentName(this, alias))
val isEnabled = state == PackageManager.COMPONENT_ENABLED_STATE_ENABLED ||
(state == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT && key == "default")
if (isEnabled) return key
}
return "unknown"
}
}
---------------------------------------------------------------------------------------------------------------
Main.dart
---------------------------------------------------------------------------------------------------------------
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:supabase_flutter/supabase_flutter.dart';
const platform = MethodChannel('com.example.application_logo/icon');
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await Supabase.initialize(
url: 'https://YOUR-PROJECT.supabase.co',
anonKey: 'YOUR-ANON-KEY',
);
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Icon Switch Demo',
home: const IconTestScreen(),
);
}
}
class IconTestScreen extends StatefulWidget {
const IconTestScreen({super.key});
@override
State<IconTestScreen> createState() => _IconTestScreenState();
}
class _IconTestScreenState extends State<IconTestScreen> {
String _status = 'Idle';
String _currentIcon = 'unknown';
@override
void initState() {
super.initState();
_loadIconFromBackend();
}
// Simulates the automatic startup flow: fetch from Supabase,
// then switch icon automatically.
Future<void> _loadIconFromBackend() async {
try {
final data = await Supabase.instance.client
.from('app_config')
.select('value')
.eq('key', 'active_icon')
.single();
final iconName = data['value'] as String;
await _switchIcon(iconName, fromBackend: true);
} catch (e) {
setState(() => _status = 'Backend fetch failed: $e');
}
}
// Manual trigger, useful for testing each icon without
// touching the Supabase dashboard every time.
Future<void> _switchIcon(String iconKey, {bool fromBackend = false}) async {
try {
final result = await platform.invokeMethod('switchIcon', {'icon': iconKey});
setState(() {
_status = fromBackend
? 'Auto-switched from backend: $result'
: 'Manually switched: $result';
_currentIcon = iconKey;
});
} on PlatformException catch (e) {
setState(() => _status = 'Switch failed: ${e.message}');
}
}
Future<void> _checkCurrentIcon() async {
try {
final result = await platform.invokeMethod('getCurrentIcon');
setState(() => _status = 'Currently active icon: $result');
} on PlatformException catch (e) {
setState(() => _status = 'Check failed: ${e.message}');
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('App Icon Switch — Dev Mode')),
body: Padding(
padding: const EdgeInsets.all(20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Text('Status:\n$_status', style: const TextStyle(fontSize: 16)),
const SizedBox(height: 8),
Text('Selected key: $_currentIcon'),
const SizedBox(height: 24),
ElevatedButton(
onPressed: () => _switchIcon('default'),
child: const Text('Switch to Default Icon'),
),
const SizedBox(height: 8),
ElevatedButton(
onPressed: () => _switchIcon('diwali'),
child: const Text('Switch to Diwali Icon'),
),
const SizedBox(height: 8),
ElevatedButton(
onPressed: () => _switchIcon('premium'),
child: const Text('Switch to Premium Icon'),
),
const SizedBox(height: 24),
OutlinedButton(
onPressed: _checkCurrentIcon,
child: const Text('Check Current Active Icon'),
),
const SizedBox(height: 8),
OutlinedButton(
onPressed: _loadIconFromBackend,
child: const Text('Re-fetch From Supabase'),
),
],
),
),
);
}
}
Comments
Post a Comment