Skip to content

Commit c842378

Browse files
committed
feat: fix UI blocking and add performance options to getInstalledApps
- Run MethodChannel handlers on background thread via makeBackgroundTaskQueue() - Add includeIcon parameter to skip expensive icon loading - Add includeSystemApps parameter to filter system apps - Fix versionCode Long to Int type conversion - Bump minimum SDK to Dart 3.0 and Flutter 3.10
1 parent 672cb16 commit c842378

File tree

5 files changed

+98
-52
lines changed

5 files changed

+98
-52
lines changed

CHANGELOG.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,19 @@
1+
## 1.7.0
2+
3+
### Performance
4+
- **Fixed UI blocking**: `getInstalledApps()` no longer freezes the UI. Method channel handlers now run on a background thread using Flutter's `makeBackgroundTaskQueue()`.
5+
6+
### New Features
7+
- Added `includeIcon` parameter to `getInstalledApps()` - set to `false` for significantly faster performance when icons aren't needed.
8+
- Added `includeSystemApps` parameter to `getInstalledApps()` - set to `false` to only retrieve user-installed apps.
9+
10+
### Bug Fixes
11+
- Fixed `versionCode` type mismatch that could cause issues on Android API 28+.
12+
13+
### Breaking Changes
14+
- Minimum Dart SDK version is now 3.0.0
15+
- Minimum Flutter version is now 3.10.0
16+
117
### 1.6.0
218

319
- Updated Android dependencies

android/src/main/kotlin/dev/yashgarg/appcheck/AppcheckPlugin.kt

Lines changed: 39 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import io.flutter.embedding.engine.plugins.FlutterPlugin
1212
import io.flutter.plugin.common.MethodCall
1313
import io.flutter.plugin.common.MethodChannel
1414
import io.flutter.plugin.common.MethodChannel.*
15+
import io.flutter.plugin.common.StandardMethodCodec
1516
import kotlin.collections.*
1617
import android.os.Build.VERSION.SDK_INT
1718
import android.os.Build.VERSION_CODES.P
@@ -27,7 +28,14 @@ class AppcheckPlugin : FlutterPlugin, MethodCallHandler {
2728
private lateinit var context: Context
2829

2930
override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
30-
channel = MethodChannel(flutterPluginBinding.binaryMessenger, "dev.yashgarg/appcheck")
31+
// Use background task queue to prevent blocking the UI thread
32+
val taskQueue = flutterPluginBinding.binaryMessenger.makeBackgroundTaskQueue()
33+
channel = MethodChannel(
34+
flutterPluginBinding.binaryMessenger,
35+
"dev.yashgarg/appcheck",
36+
StandardMethodCodec.INSTANCE,
37+
taskQueue
38+
)
3139
channel.setMethodCallHandler(this)
3240
context = flutterPluginBinding.applicationContext
3341
}
@@ -39,7 +47,11 @@ class AppcheckPlugin : FlutterPlugin, MethodCallHandler {
3947
uriSchema = call.argument<String>("uri").toString()
4048
checkAvailability(uriSchema, result)
4149
}
42-
"getInstalledApps" -> result.success(installedApps)
50+
"getInstalledApps" -> {
51+
val includeIcon = call.argument<Boolean>("includeIcon") ?: true
52+
val includeSystemApps = call.argument<Boolean>("includeSystemApps") ?: true
53+
result.success(getInstalledApps(includeIcon, includeSystemApps))
54+
}
4355
"isAppEnabled" -> {
4456
uriSchema = call.argument<String>("uri").toString()
4557
isAppEnabled(uriSchema, result)
@@ -61,22 +73,25 @@ class AppcheckPlugin : FlutterPlugin, MethodCallHandler {
6173
result.error("400", "App not found $uri", null)
6274
}
6375

64-
private val installedApps: MutableList<Map<String, Any>>
65-
get() {
66-
val packageManager: PackageManager = context.packageManager
67-
val packages = if (SDK_INT >= TIRAMISU) {
68-
packageManager.getInstalledPackages(PackageManager.PackageInfoFlags.of(0L))
69-
} else {
70-
@Suppress("DEPRECATION")
71-
packageManager.getInstalledPackages(0)
72-
}
73-
val installedApps: MutableList<Map<String, Any>> = ArrayList(packages.size)
74-
for (pkg in packages) {
75-
val map = convertPackageInfoToJson(pkg)
76-
installedApps.add(map)
77-
}
78-
return installedApps
76+
private fun getInstalledApps(includeIcon: Boolean, includeSystemApps: Boolean): MutableList<Map<String, Any>> {
77+
val packageManager: PackageManager = context.packageManager
78+
val packages = if (SDK_INT >= TIRAMISU) {
79+
packageManager.getInstalledPackages(PackageManager.PackageInfoFlags.of(0L))
80+
} else {
81+
@Suppress("DEPRECATION")
82+
packageManager.getInstalledPackages(0)
83+
}
84+
val installedApps: MutableList<Map<String, Any>> = ArrayList(packages.size)
85+
for (pkg in packages) {
86+
val appInfo = pkg.applicationInfo
87+
val isSystemApp = appInfo != null && (appInfo.flags and ApplicationInfo.FLAG_SYSTEM) != 0
88+
if (!includeSystemApps && isSystemApp) continue
89+
90+
val map = convertPackageInfoToJson(pkg, includeIcon)
91+
installedApps.add(map)
7992
}
93+
return installedApps
94+
}
8095

8196
private fun getAppPackageInfo(uri: String): PackageInfo? {
8297
val pm = context.packageManager
@@ -93,14 +108,15 @@ class AppcheckPlugin : FlutterPlugin, MethodCallHandler {
93108
return null
94109
}
95110

96-
private fun convertPackageInfoToJson(info: PackageInfo): Map<String, Any> {
111+
private fun convertPackageInfoToJson(info: PackageInfo, includeIcon: Boolean = true): Map<String, Any> {
97112
val app: MutableMap<String, Any> = HashMap()
98113

99-
val appInfo = info.applicationInfo;
114+
val appInfo = info.applicationInfo
100115
if (appInfo != null) {
101-
app["app_name"] =
102-
appInfo.loadLabel(context.packageManager).toString()
103-
app["icon"] = DrawableUtil.drawableToByteArray(appInfo.loadIcon(context.packageManager))
116+
app["app_name"] = appInfo.loadLabel(context.packageManager).toString()
117+
if (includeIcon) {
118+
app["icon"] = DrawableUtil.drawableToByteArray(appInfo.loadIcon(context.packageManager))
119+
}
104120
app["system_app"] = (appInfo.flags and ApplicationInfo.FLAG_SYSTEM) != 0
105121
} else {
106122
app["app_name"] = "N/A"
@@ -109,7 +125,7 @@ class AppcheckPlugin : FlutterPlugin, MethodCallHandler {
109125

110126
app["package_name"] = info.packageName
111127
app["version_name"] = info.versionName.toString()
112-
app["version_code"] = getVersionCode(info)
128+
app["version_code"] = getVersionCode(info).toInt()
113129

114130
return app
115131
}

example/lib/main.dart

Lines changed: 19 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ class _AppCheckExampleState extends State<AppCheckExample> {
3535
List<AppInfo>? apps = [];
3636
try {
3737
if (Platform.isAndroid) {
38-
apps = await appCheck.getInstalledApps();
38+
apps = await appCheck.getInstalledApps(includeSystemApps: false);
3939
apps?.sort(
4040
(a, b) =>
4141
a.appName!.toLowerCase().compareTo(b.appName!.toLowerCase()),
@@ -70,25 +70,24 @@ class _AppCheckExampleState extends State<AppCheckExample> {
7070
debugShowCheckedModeBanner: false,
7171
home: Scaffold(
7272
appBar: AppBar(title: const Text('AppCheck Example')),
73-
body:
74-
installedApps.isNotEmpty
75-
? ListView.builder(
76-
itemCount: installedApps.length,
77-
itemBuilder: (context, index) {
78-
final app = installedApps[index];
79-
return ListTile(
80-
title: Text(app.appName ?? app.packageName),
81-
subtitle: Text(
82-
(app.isSystemApp ?? false) ? 'System App' : 'User App',
83-
),
84-
trailing: IconButton(
85-
icon: const Icon(Icons.open_in_new),
86-
onPressed: () => _launchApp(app),
87-
),
88-
);
89-
},
90-
)
91-
: const Center(child: Text('No installed apps found!')),
73+
body: installedApps.isNotEmpty
74+
? ListView.builder(
75+
itemCount: installedApps.length,
76+
itemBuilder: (context, index) {
77+
final app = installedApps[index];
78+
return ListTile(
79+
title: Text(app.appName ?? app.packageName),
80+
subtitle: Text(
81+
(app.isSystemApp ?? false) ? 'System App' : 'User App',
82+
),
83+
trailing: IconButton(
84+
icon: const Icon(Icons.open_in_new),
85+
onPressed: () => _launchApp(app),
86+
),
87+
);
88+
},
89+
)
90+
: const Center(child: Text('No installed apps found!')),
9291
),
9392
);
9493
}

lib/src/appcheck.dart

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ class AppCheck {
77
final MethodChannel _channel;
88

99
AppCheck({MethodChannel? channel})
10-
: _channel = channel ?? const MethodChannel('dev.yashgarg/appcheck');
10+
: _channel = channel ?? const MethodChannel('dev.yashgarg/appcheck');
1111

1212
/// Check if an app is available with the given [uri] scheme.
1313
///
@@ -25,8 +25,10 @@ class AppCheck {
2525

2626
return AppInfo.fromMap(app);
2727
} else if (Platform.isIOS) {
28-
bool appAvailable =
29-
await _channel.invokeMethod("checkAvailability", args);
28+
bool appAvailable = await _channel.invokeMethod(
29+
"checkAvailability",
30+
args,
31+
);
3032

3133
if (!appAvailable) {
3234
throw PlatformException(code: "", message: "App not found $uri");
@@ -55,9 +57,22 @@ class AppCheck {
5557
/// Get the list of all installed apps, where
5658
/// each app has a form like [checkAvailability()].
5759
///
60+
/// [includeIcon] - Whether to include app icons (default: true).
61+
/// Set to false for faster performance when icons aren't needed.
62+
///
63+
/// [includeSystemApps] - Whether to include system apps (default: true).
64+
/// Set to false to only get user-installed apps.
65+
///
5866
/// Returns a list of [AppInfo] containing all installed apps data, else returns [null]
59-
Future<List<AppInfo>?> getInstalledApps() async {
60-
List<dynamic>? apps = await _channel.invokeMethod("getInstalledApps");
67+
Future<List<AppInfo>?> getInstalledApps({
68+
bool includeIcon = true,
69+
bool includeSystemApps = true,
70+
}) async {
71+
final args = <String, dynamic>{
72+
'includeIcon': includeIcon,
73+
'includeSystemApps': includeSystemApps,
74+
};
75+
List<dynamic>? apps = await _channel.invokeMethod("getInstalledApps", args);
6176
if (apps != null) {
6277
List<AppInfo> list = [];
6378
for (var app in apps) {

pubspec.yaml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
name: appcheck
22
description: Flutter plugin that allows you to check if an app is installed/enabled, launch an app and get the list of installed apps.
3-
version: 1.6.0
3+
version: 1.7.0
44
homepage: https://github.com/Yash-Garg/appcheck
55

66
environment:
7-
sdk: ">=2.12.0 <4.0.0"
8-
flutter: ">=3.0.0"
7+
sdk: ">=3.0.0 <4.0.0"
8+
flutter: ">=3.10.0"
99

1010
dependencies:
1111
flutter:
@@ -14,7 +14,7 @@ dependencies:
1414
dev_dependencies:
1515
flutter_test:
1616
sdk: flutter
17-
flutter_lints: ^6.0.0
17+
flutter_lints: ^5.0.0
1818

1919
flutter:
2020
plugin:

0 commit comments

Comments
 (0)