ku
0.0.5indexedSimplifies location handling in Compose applications with a declarative API. Offers automatic permission management, customizable UI components, flexible configuration, and smart error handling for seamless location fetching.
Simplifies location handling in Compose applications with a declarative API. Offers automatic permission management, customizable UI components, flexible configuration, and smart error handling for seamless location fetching.
Surfaced from shared tags and platforms — no rankings paid for.
A beautiful, declarative Kotlin Multiplatform library for effortless location handling in Compose applications. KU (short for "where are yoU?") simplifies location permissions and fetching with a clean, composable API.
Add KU to your commonMain dependencies:
commonMain {
dependencies {
implementation("io.github.eltonkola:ku:0.0.3")
}
}
Start requesting location immediately when the composable loads:
LocationProvider(
config = LocationConfig(autoRequest = true),
onLocationReceived = { location ->
Text("Location: ${location.format()}")
}
)
Let users control when to request location:
LocationProvider(
onLocationReceived = { location ->
Column {
Text("Latitude: ${location.latitude}")
Text("Longitude: ${location.longitude}")
Text("Accuracy: ${location.accuracy}m")
}
},
onInitial = { onRequestLocation ->
Button(onClick = onRequestLocation) {
Text("Get My Location")
}
}
)
Customize location behavior with LocationConfig:
LocationProvider(
config = LocationConfig(
autoRequest = true,
singleRequest = false, // Set to true for one-time location
highAccuracy = true, // Use GPS for high accuracy
updateIntervalMs = 10_000L, // Update every 10 seconds
minUpdateIntervalMs = 5_000L, // Minimum 5 seconds between updates
timeoutMs = 30_000L // 30 second timeout
),
onLocationReceived = { location ->
MapView(location)
}
)
Override any part of the UI to match your design:
For cases where you prefer callbacks over composable content:
LocationProvider(
onLocationReceived = { location ->
// Handle location update
updateMapLocation(location)
},
onError = { error ->
// Handle error
showErrorToast(error)
},
onPermissionDenied = {
// Handle permission denied
showPermissionDialog()
},
config = LocationConfig(autoRequest = true)
)
Handle different types of location errors:
Use the useLocation hook for more control:
@Composable
fun MyLocationScreen() {
val locationState = useLocation(
config = LocationConfig(autoRequest = true, highAccuracy = true)
)
when (locationState) {
LocationState.Loading -> {
CircularProgressIndicator()
}
is LocationState.Success -> {
LocationDetails(locationState.location)
}
is LocationState.Error -> {
ErrorMessage(locationState.message)
}
LocationState.PermissionDenied -> {
PermissionRequestUI()
}
}
}
data class Location(
latitude: ,
longitude: ,
accuracy: ,
altitude: ?,
bearing: ?,
speed: ?,
timestamp: ,
provider: String?
)
// Format location coordinates
val formatted = location.format(precision = 4) // "40.7128, -74.0060"
// Calculate distance between two locations
val distance = location1.distanceTo(location2) // Distance in meters
sealed class LocationState {
object Loading : LocationState()
data class Success(val location: Location) : LocationState()
data class Error(val message: String) : LocationState()
object PermissionDenied : LocationState()
}
KU automatically handles location errors intelligently:
Add location permissions to your AndroidManifest.xml:
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<!-- Optional: For background location (if needed) -->
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
ProGuard/R8 Rules (if using code obfuscation):
-keep class com.google.android.gms.location.** { *; }
-dontwarn com.google.android.gms.**
Add location usage descriptions to your Info.plist:
<key>NSLocationWhenInUseUsageDescription</key>
<string>This app needs location access to show your current position and nearby places.</string>
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>This app needs location access to provide location-based services.</string>
@Test
fun testLocationProvider() {
// Use Android Location Testing framework or iOS Location Simulator
// KU integrates well with standard platform testing tools
}
val mockLocation = Location(
latitude = 40.7128,
longitude = -74.0060,
accuracy = 10.0f,
altitude = 100.0,
speed = null,
bearing = null,
timestamp = System.currentTimeMillis(),
provider = "mock"
)
Location always returns null/error:
timeoutMs in LocationConfigUI keeps flashing between states:
Battery drain:
updateIntervalMs and minUpdateIntervalMssingleRequest = true if you only need one locationhighAccuracy = false for less precise but battery-friendly locationiOS permission issues:
Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.
Copyright 2024 Elton Kola
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http:
Unless applicable law agreed to writing, software
distributed under the License distributed an BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express implied.
See the License the specific language governing permissions
limitations under the License.
LocationProvider(
onLocationReceived = { location ->
LocationCard(location)
},
onInitial = { onRequestLocation ->
Card(
modifier = Modifier
.fillMaxWidth()
.clickable { onRequestLocation() }
) {
Row(
modifier = Modifier.padding(16.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Column {
Text(
"Find My Location",
style = MaterialTheme.typography.titleMedium
)
Text(
"Tap to get your current position",
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
}
Icon(
Icons.Default.LocationOn,
contentDescription = "Location",
tint = MaterialTheme.colorScheme.primary
)
}
}
},
onLoading = {
Card {
Column(
modifier = Modifier.padding(24.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
CircularProgressIndicator(
color = MaterialTheme.colorScheme.primary
)
Spacer(modifier = Modifier.height(16.dp))
Text(
"Finding your location...",
style = MaterialTheme.typography.bodyMedium
)
Text(
"This may take a few moments",
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
}
}
},
onPermissionDenied = { requestPermission ->
Card(
colors = CardDefaults.cardColors(
containerColor = MaterialTheme.colorScheme.errorContainer
)
) {
Column(
modifier = Modifier.padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
Icon(
Icons.Default.LocationOff,
contentDescription = null,
tint = MaterialTheme.colorScheme.onErrorContainer,
modifier = Modifier.size(48.dp)
)
Spacer(modifier = Modifier.height(12.dp))
Text(
"Location Permission Required",
style = MaterialTheme.typography.titleMedium,
color = MaterialTheme.colorScheme.onErrorContainer
)
Text(
"We need access to your location to show nearby places and services.",
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onErrorContainer,
textAlign = TextAlign.Center
)
Spacer(modifier = Modifier.height(16.dp))
Button(
onClick = requestPermission,
colors = ButtonDefaults.buttonColors(
containerColor = MaterialTheme.colorScheme.error
)
) {
Text("Grant Permission")
}
}
}
},
onError = { errorMessage, retry ->
Card(
colors = CardDefaults.cardColors(
containerColor = MaterialTheme.colorScheme.errorContainer
)
) {
Column(
modifier = Modifier.padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
Icon(
Icons.Default.Error,
contentDescription = null,
tint = MaterialTheme.colorScheme.onErrorContainer
)
Spacer(modifier = Modifier.height(8.dp))
Text(
"Location Error",
style = MaterialTheme.typography.titleMedium,
color = MaterialTheme.colorScheme.onErrorContainer
)
Text(
errorMessage,
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onErrorContainer,
textAlign = TextAlign.Center
)
Spacer(modifier = Modifier.height(12.dp))
Button(onClick = retry) {
Text("Try Again")
}
}
}
}
)
LocationProvider(
onError = { errorMessage, retry ->
when {
errorMessage.contains("GPS") || errorMessage.contains("Location services") -> {
Column {
Text("GPS is disabled")
Text("Please enable location services in your device settings")
Spacer(modifier = Modifier.height(8.dp))
Row {
Button(onClick = { openLocationSettings() }) {
Text("Open Settings")
}
Spacer(modifier = Modifier.width(8.dp))
OutlinedButton(onClick = retry) {
Text("Retry")
}
}
}
}
errorMessage.contains("timeout") -> {
Column {
Text("Location request timed out")
Text("Make sure you're not indoors and have a clear view of the sky")
Spacer(modifier = Modifier.height(8.dp))
Button(onClick = retry) {
Text("Try Again")
}
}
}
else -> {
Column {
Text("Location Error")
Text(errorMessage)
Spacer(modifier = Modifier.height(8.dp))
Button(onClick = retry) {
Text("Retry")
}
}
}
}
},
onLocationReceived = { location ->
MapView(location)
}
)
| Parameter | Type | Description | Default |
|---|
onLocationReceived | @Composable (Location) -> Unit | Content displayed when location is successfully obtained | Required |
modifier | Modifier | Modifier for the LocationProvider container | Modifier |
config | LocationConfig | Configuration for location behavior | LocationConfig() |
onPermissionDenied | @Composable (() -> Unit) -> Unit | Content displayed when permission is denied | Default permission UI |
onLoading | @Composable () -> Unit | Content displayed while fetching location | Default loading UI |
onError | @Composable (String, () -> Unit) -> Unit | Content displayed when an error occurs | Default error UI |
onInitial | @Composable (() -> Unit) -> Unit | Content displayed before location request | Default button |
| Property | Type | Description | Default |
|---|
autoRequest | Boolean | Whether to request location automatically | false |
singleRequest | Boolean | Whether to request location only once | false |
highAccuracy | Boolean | Whether to use high accuracy (GPS) | true |
updateIntervalMs | Long | Interval between location updates (ms) | 10_000L |
minUpdateIntervalMs | Long | Minimum interval between updates (ms) | 5_000L |
maxUpdateDelayMs | Long | Maximum delay for location updates (ms) | 15_000L |
timeoutMs | Long | Timeout for location requests (ms) | 30_000L |