- 💡 Compose Multiplatform’s UI model, which reacts to changes, needs APIs that know about side effects, like LaunchedEffect, to handle permissions while the app runs.
- 📱 Over 60% of Android devices now run Android 10+. This means stricter rules are in place for background location access.
- 🧰 FusedLocationProviderClient will not work unless you grant the right permissions. And it does not ask for them itself.
- ⚠️ Permission dialogs fail without a message if started outside of lifecycle-aware scopes in Compose.
- 🛠 Accompanist Permissions or ActivityResultLauncher help with permission requests better for Compose-based apps.
Compose Multiplatform Location Permissions Not Working? Here’s How To Fix It
You put in FusedLocationProviderClient, used your Composables, and checked your code again. But your app still quietly fails to get location data. There is no permission dialog. No error shows up. Just nothing. If you are building with Compose Multiplatform and wondering why location permissions are not working, you are not alone. This silent failure is a common problem, especially when moving from old Views to a modern UI. Let's look at why it happens and how to fix it.
Understanding the Compose Multiplatform Environment
Compose Multiplatform is Jetpack Compose's way to share UI development across platforms like Android, Desktop, and later iOS. It lets developers write Composables once and use them on different operating systems. This means less repeated code and easier app upkeep.
But this way of building things makes some tasks harder. For example, it affects how you ask for permissions or use device sensors and services. In Compose, UI elements react to state changes. And lifecycle events just happen, they do not tell the UI what to do like in old Android Views.
In traditional Android development, asking for permissions usually happens in Activity.onCreate() or Fragment.onStart(). In Compose, these methods do not control the UI. Instead, reactive functions like LaunchedEffect, SideEffect, and remember* APIs help control when and how permission requests show up. If you try to use old methods, or ask for permissions outside of these reactive scopes, the system will just ignore your request. You will not even see an error.
How Android Location Permissions Work Internally (2024 Update)
Since Android 6.0 (API level 23), developers must ask for permissions when the app runs, besides listing them in the AndroidManifest.xml. These permission requests have gotten harder in newer Android versions. This is true for location data, because of more privacy rules.
Here are the main location permission groups you can use:
ACCESS_COARSE_LOCATION: Finds the user's general spot using cell tower and Wi-Fi data. It is not very exact, so use it when you do not need a precise location.ACCESS_FINE_LOCATION: Uses GPS and is needed for very exact location uses.ACCESS_BACKGROUND_LOCATION: Gives the app location access when it is running in the background. This came out in Android 10 (API 29). Now, there are strict Play Store rules about it.
What's New in 2024?
- Android 11 and higher requires a two-step permission process. You must get foreground permissions first. Then you can ask for background location access.
- The system will automatically deny or quietly ignore background location requests. This happens unless you give a clear reason to the user.
- Often, the system denies these requests without telling you. This happens if the code tries to ask for background permissions too early or at the wrong time in the app's lifecycle.
Over 60% of Android devices now run Android 10 or newer (AppBrain, 2023). So, these longer, privacy-focused ways of asking for permissions are common in apps today.
FusedLocationProviderClient and Permissions: The Relationship
The FusedLocationProviderClient is Google's best API for getting location data in Android. It uses signals from GPS, Wi-Fi, and cell towers. This helps it find the most exact location while using very little battery.
Why It Matters
This API does not check or ask for permissions. It just assumes your app already has what it needs. If not, its methods, like getLastLocation(), simply do nothing or return null.
This means that to use it correctly with Compose Multiplatform, you need:
- Correct AndroidManifest.xml declarations.
- To ask for permissions when the app runs.
- To separate calls for each platform. You can do this using Kotlin Multiplatform's
expect/actualdeclarations. This helps keep things safe across platforms. - To prevent silent failures if you do not have the right access.
When using FusedLocationProviderClient, permissions must be in place before any location task can work. If you do not grant these beforehand, your app might never get any location. You will not get a crash or error to tell you why.
Compose Permissions Pitfalls: Why Your Dialog Might Not Show
In Compose, starting side effects, such as permission dialogs, needs to follow Compose's reactive design. Unlike Views, Compose does not have direct lifecycle methods to call functions like requestPermissions().
Here are common mistakes:
-
🚫 Calling requests outside of the UI lifecycle
- If a permission dialog starts from a
ViewModelor outside aComposable, it probably runs before the UI is ready. Or it might not run at all.
- If a permission dialog starts from a
-
🌀 Ignoring Compose’s structure
- Compose limits how often it rebuilds the UI and runs side effects. If your code is not in the right scope (for example, not inside
LaunchedEffect), the system might skip permission requests completely.
- Compose limits how often it rebuilds the UI and runs side effects. If your code is not in the right scope (for example, not inside
-
🧩 Missing reactive permission state
- You need to link permission states to
rememberPermissionState. If you do not, Compose will not know when to update the UI if permission status changes.
- You need to link permission states to
-
📵 Permission previously denied
- If a user denied permission and chose "Don't Ask Again", the prompt will not show up again. The user must turn it back on in System Settings.
Knowing these small details is important. It helps you build a location feature that works well in Compose Multiplatform.
The Role of Accompanist Permissions Library
The Accompanist Permissions library connects Compose's reactive way of working with Android's direct permission system. It gives you APIs like rememberPermissionState() and rememberMultiplePermissionsState(). These track permission status easily over time.
Here's a simple example:
val locationPermissionState = rememberPermissionState(Manifest.permission.ACCESS_FINE_LOCATION)
LaunchedEffect(Unit) {
if (!locationPermissionState.status.isGranted) {
locationPermissionState.launchPermissionRequest()
}
}
Key Features:
- It tracks permission status by itself.
- It has easy-to-use methods to show permission dialogs, like
launchPermissionRequest(). - It works well with
LaunchedEffect. This stops it from asking for permission many times. - It supports asking for many permissions at once, all through one API.
It is great for quick tests and even real apps. But you still need to test all the tricky situations well.
Step-by-Step: Debugging When Location Permissions Don’t Show
If location dialogs do not show up, or if permissions seem ignored, use this checklist to find the problem:
-
✅ Manifest Declarations
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <!-- Optional based on use case --> <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" /> -
✅ Composable Block
- Make sure permission request code is inside a Composable that is visible and active.
-
✅ LaunchedEffect Block
- Side effects, such as prompts, should be inside
LaunchedEffect(Unit). Or they should depend on changes in state.
- Side effects, such as prompts, should be inside
-
✅ Condition Checks
- Always check
status.isGrantedbefore asking again. This stops too many dialogs from showing up. It also helps avoid system rejections.
- Always check
-
✅ Logging
- Add logs or Toasts before each permission call. This helps confirm the code is actually running.
Example:
Log.d("Permissions", "Permission granted: ${permissionState.status.isGranted}")
Code Walkthrough: Minimal Working Compose Example
Here’s a short but working Compose screen. It asks for location permission and gets the last known location:
@Composable
fun LocationAccessScreen() {
val context = LocalContext.current
val locationPermissionState = rememberPermissionState(
permission = Manifest.permission.ACCESS_FINE_LOCATION
)
LaunchedEffect(Unit) {
if (!locationPermissionState.status.isGranted) {
locationPermissionState.launchPermissionRequest()
}
}
if (locationPermissionState.status.isGranted) {
val fusedLocationClient = LocationServices.getFusedLocationProviderClient(context)
fusedLocationClient.lastLocation.addOnSuccessListener { location ->
location?.let {
Log.d("Location", "Latitude: ${it.latitude}, Longitude: ${it.longitude}")
}
}
}
}
Make sure to include the permission in your manifest:
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
You can use this pattern for bigger apps too. Just make sure to manage state well, for example, with ViewModel and navigation.
Common Mistakes and Misinterpretations
Do not make these common mistakes:
- ❌ Trying to ask for permissions outside a visible Composable, or before the UI is ready
- ❌ Using direct code without
LaunchedEffect - ❌ Thinking
isGrantedis a permanent state. It can change. - ❌ Not testing what happens when a user clicks "Do Not Ask Again"
- ❌ Thinking permission dialogs will just show up when the app starts
Fix these to make your permissions much more reliable.
When to Avoid Accompanist: Alternatives and Manual Handling
The Accompanist library is useful. But it is not always ready for full apps or regularly updated for all platforms. You can also use Android's ActivityResultContracts by hand. Do this if you need more control, or if you are working with older parts of an app.
val launcher = rememberLauncherForActivityResult(
ActivityResultContracts.RequestPermission()
) { isGranted ->
if (isGranted) {
// Proceed with FusedLocationProviderClient
} else {
// Show rationale or instructions
}
}
Start the launcher inside LaunchedEffect. Or start it when a user does something, like pressing a button.
Testing Permissions in Compose: Best Practices
Permissions work differently on various Android versions and phone makers. So, test widely:
- 🔄 Use
adb shell pm reset-permissionsto clear permission settings between app starts. - 📱 Test on physical devices: Emulators might not work the same as real phones for background services and GPS.
- 🧪 Test what happens when permission is denied, granted, and when "Do Not Ask Again" is chosen. This makes sure the user experience is fully checked.
- 🎯 Try out tricky situations. For example, what if the app starts from a deep link? Or what if navigation is cut off?
Tools like Firebase Test Lab can run tests on many devices automatically.
Compose Multiplatform vs. Android: Platform-Specific Implementations
Compose Multiplatform works on many systems, so location services need to be kept separate.
Here’s how:
// commonMain
expect fun getCurrentLocation(): Location?
// androidMain
actual fun getCurrentLocation(): Location? {
val context = AndroidContextProvider.getApplicationContext()
val client = LocationServices.getFusedLocationProviderClient(context)
var result: Location? = null
client.lastLocation.addOnSuccessListener {
result = it
}
return result
}
This way, platforms that do not support Android APIs (like iOS, Desktop, and Web) do not get Android-specific code.
What the Community Often Gets Wrong
Many developers still make mistakes on forums and dev channels:
- Copying old View code into Composables without changing it for side effects
- Thinking Compose handles permissions by itself, like Views used to
- Using bad logic, such as waiting for screen
onCreateinstead of state changes - Adding background permission to the manifest but not using it. This can lead to Google Play rejecting your app.
Write code carefully. Always check how APIs work when user permissions are part of it.
Security and Legal Considerations
Location permissions deal with private user data. So, they must follow rules like GDPR and others.
- 📜 Show clear, short messages that tell users why you need permission.
- 📵 Never make users grant permissions just to use the app. Instead, make the app still work in a simpler way. You can ask for permission later, or skip some features.
- 🕵️♂️ Do not ask for
ACCESS_BACKGROUND_LOCATIONunless your app really needs it. Also, write down why you need it in the Play Console. - ✔️ Make sure your app uses the correct API level for Play Store rules. If not, old SDK levels or wrong permissions will cause your app to be denied.
Respect user trust. Asking for too many permissions can hurt how many people keep your app. It can also affect reviews and how visible your app is in the store.
Developer Takeaways
Handling location permissions in a Compose Multiplatform project means you need to be careful with Android APIs inside Compose's reactive system. If you use tools like Accompanist the right way, and debug carefully, you can stop most silent failures. Think about if Accompanist is right for you. Or if you need more control, especially in big apps. In that case, manual handling might be better. As Compose Multiplatform gets better, expect simpler user flows for permissions. Until then, be ready, test everything, and always be clear with users.
Citations
- Android Developer Documentation. (n.d.). Request App Permissions. Google. https://developer.android.com/training/permissions/requesting
- AppBrain. (2023). Android API statistics. https://www.appbrain.com/stats/top-android-sdk-versions
- Google Developers. (2023). Fused Location Provider API. https://developers.google.com/location-context/fused-location-provider