Retrieving Installed Applications List via Android PackageManager in Kotlin

I’m working on an Android app that needs to show all installed applications on the device. My target is Android 15 with SDK 35. I want to create a list that shows both system apps and user installed apps using LazyColumn in Jetpack Compose.

The main challenges I’m facing are:

  • How to properly use PackageManager to get the app list
  • What properties are available for each app (like app name, package identifier, etc)
  • Getting this data into a usable format for my Composable

Here’s what I have so far:

@Composable
fun InstalledAppsScreen(modifier: Modifier = Modifier) {
    val currentContext = LocalContext.current
    val appManager = currentContext.packageManager // Is this correct?
    
    // Display apps using LazyColumn
    // Need help getting the actual list
}

I’ve already added the necessary permissions in my manifest:

<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    <queries>
        <package android:name="android.permission.QUERY_ALL_PACKAGES"/>
    </queries>
</manifest>

Can someone show me the proper way to implement PackageManager for this use case? I’m particularly confused about the Context usage and how to extract the app information I need.

your context usage looks good, but you’re likely to run into memory leaks. try moving that packagemanager call into a viewmodel instead of having it in the composable. also, the permission syntax in your manifest is wrong - the queries tag isn’t meant for permissions!

Been down this road before on a device management app. Both answers got the permission issue right, but here’s what actually worked in production.

Skip getInstalledPackages() and getInstalledApplications() entirely. Use queryIntentActivities() with a launcher intent instead:

val intent = Intent(Intent.ACTION_MAIN, null)
intent.addCategory(Intent.CATEGORY_LAUNCHER)
val apps = packageManager.queryIntentActivities(intent, 0)

This gives you only launchable apps, which is what users expect. Each ResolveInfo has activityInfo.packageName and loadLabel(packageManager) for the name.

The real gotcha: loadLabel() and loadIcon() hit the file system every time. We cached everything in a Room database after the first load. Made scrolling buttery smooth.

Watch out for Android 15 restrictions too. Even with QUERY_ALL_PACKAGES, some system apps get filtered out now. Test on actual Android 15 devices, not just emulators.

One more thing - wrap your PackageManager calls in try-catch. I’ve seen random SecurityExceptions on some OEM ROMs even with proper permissions.

Your permission setup’s wrong for Android 15. Don’t put <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" /> inside the queries tag - it goes directly in the manifest. The queries tag does something completely different.

For getting the apps, use packageManager.getInstalledPackages(PackageManager.GET_META_DATA). Each PackageInfo gives you applicationInfo.loadLabel() for the name, packageName for the ID, and applicationInfo.loadIcon() for icons.

Heads up - this gets really slow with lots of apps. Run it on a background thread and use ViewModel with StateFlow. I froze my UI testing on a phone with 200+ apps and had to learn this the hard way.

You’re on the right track with getting PackageManager from context. But there’s a problem with your permission - you need <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" /> at the manifest level, not inside queries.

For getting apps, use getInstalledApplications(PackageManager.GET_META_DATA) instead of getInstalledPackages since you want application info. This gives you ApplicationInfo objects where you can grab loadLabel(packageManager) for display names, packageName for identifiers, and check flags & ApplicationInfo.FLAG_SYSTEM to tell system apps from user-installed ones.

Here’s what caught me off guard - the performance hit is brutal. Loading app labels and icons synchronously will destroy your UI. I moved all data loading to a Repository class with coroutines and cached everything. Your Composable should just observe the data through a ViewModel instead of hitting PackageManager directly.