Ever wondered how your favorite apps always know where you are? Whether it’s guiding you to the nearest taco joint or tracking your morning run, location services are the unsung heroes of mobile development. As a developer, getting a handle on these features can level up your app game big time. Today, we’re diving into how to snag the last known location and keep those updates flowing using Kotlin Flow in Android, Kotlin Multiplatform (KMP), and Compose Multiplatform (CMP). I’ll throw in some code snippets, sprinkle a bit of humor, and make sure you walk away ready to tackle location-based projects like a pro.
Let’s jump right in!
What’s the Deal with Location Services and Kotlin Flow?
Location services in Android give your app the power to pinpoint where the user is, thanks to tools like the Fused Location Provider API from Google Play Services. Pair that with Kotlin Flow—a slick way to handle streams of data—and you’ve got a recipe for smooth, reactive location handling. Flow is part of Kotlin’s coroutines toolkit, perfect for dealing with stuff like location updates that keep coming at you over time.
Picture this: you’re coding an app to track your dog’s wild adventures in the park. You need his location pronto, and you want updates as he chases squirrels. Kotlin Flow makes this a breeze by letting you treat location data like a stream you can tap into whenever you need. Let’s see how to make it happen.
Setting Up Your Project
Before we get to the fun stuff, we’ve got to lay the groundwork. That means adding the right dependencies and sorting out permissions. Here’s what you need in your Android build.gradle
file:
implementation "com.google.android.gms:play-services-location:21.0.1"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-play-services:1.6.4"
Code language: CSS (css)
These are the latest versions as of now—keep an eye on the Google Maven repository for updates. For KMP or CMP, you might tweak these depending on your platforms, but we’ll get to that later.
Next, pop these lines into your AndroidManifest.xml
:
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
Code language: HTML, XML (xml)
Permissions aren’t just a formality—Android won’t hand over location data unless you ask nicely at runtime too. We’ll handle that in the code soon. For now, let’s assume your project’s ready to roll.
Snagging the Last Known Location
Alright, let’s grab that last known spot where your user (or their dog) was hanging out. We’ll use the Fused Location Provider API, which is fast and reliable. Since this is a one-time grab, a suspend function fits the bill perfectly:
suspend fun getLastKnownLocation(context: Context): Location? {
val fusedLocationClient = LocationServices.getFusedLocationProviderClient(context)
return try {
fusedLocationClient.lastLocation.await()
} catch (e: SecurityException) {
Log.e("LocationError", "Permission denied, whoops!", e)
null
}
}
Code language: JavaScript (javascript)
Call this from a coroutine scope, like in your activity:
lifecycleScope.launch {
val location = getLastKnownLocation(this@MainActivity)
location?.let {
println("Last seen at: ${it.latitude}, ${it.longitude}")
} ?: run {
println("Location’s playing hide and seek—check permissions!")
}
}
Code language: JavaScript (javascript)
Quick heads-up: if you forget permissions, you’ll be staring at null
for hours, wondering why the universe hates you. Been there, done that—save yourself the headache and double-check!
Streaming Location Updates with Kotlin Flow
Now for the real magic: getting location updates as they happen. This is where Kotlin Flow shines. First, set up a location request to tell the system how often you want updates:
val locationRequest = LocationRequest.create().apply {
interval = 10000 // 10 seconds
fastestInterval = 5000 // 5 seconds
priority = LocationRequest.PRIORITY_HIGH_ACCURACY
}
Code language: JavaScript (javascript)
Then, whip up a Flow to stream those updates:
fun locationFlow(context: Context): Flow<Location> = callbackFlow {
val fusedLocationClient = LocationServices.getFusedLocationProviderClient(context)
val locationCallback = object : LocationCallback() {
override fun onLocationResult(locationResult: LocationResult) {
for (location in locationResult.locations) {
trySend(location).isSuccess // Send each new location downstream
}
}
}
fusedLocationClient.requestLocationUpdates(
locationRequest,
locationCallback,
Looper.getMainLooper()
)
awaitClose {
fusedLocationClient.removeLocationUpdates(locationCallback) // Clean up when done
}
}
Code language: JavaScript (javascript)
To use this in your app, collect the Flow like so:
lifecycleScope.launch {
locationFlow(this@MainActivity).collect { location ->
println("New spot: ${location.latitude}, ${location.longitude}")
// Update your UI or save the data—sky’s the limit!
}
}
Code language: JavaScript (javascript)
The awaitClose
block ensures you’re not sucking up battery life when the Flow’s cancelled—like when your user closes the app to avoid your terrible dog-tracking UI (kidding, I’m sure it’s great).
Handling Permissions Like a Grown-Up
You can’t just grab locations without asking. Here’s how to request permissions using the Activity Result API:
val locationPermissionRequest = registerForActivityResult(
ActivityResultContracts.RequestMultiplePermissions()
) { permissions ->
when {
permissions[Manifest.permission.ACCESS_FINE_LOCATION] == true -> {
println("Permission granted—let’s roll!")
lifecycleScope.launch {
locationFlow(this@MainActivity).collect { location ->
println("Location: ${location.latitude}, ${location.longitude}")
}
}
}
else -> {
println("Permission denied. No location for you!")
// Maybe show a sad puppy GIF
}
}
}
locationPermissionRequest.launch(arrayOf(Manifest.permission.ACCESS_FINE_LOCATION))
Code language: JavaScript (javascript)
This checks for fine location access, but you could add coarse location too if you’re feeling generous. Always test this flow—nothing’s worse than an app that crashes because you assumed permissions were a done deal.
How Does This Play with KMP and CMP?
If you’re going multiplatform, things get a bit spicier. In KMP, you’re sharing code between Android and iOS, but location APIs are platform-specific. Android uses Fused Location Provider, while iOS leans on Core Location. In CMP, you’re adding Compose UI into the mix, but the location logic stays similar.
Here’s a quick breakdown:
Platform | Location API | Kotlin Flow Integration |
---|---|---|
Android | Fused Location Provider | Use callbackFlow with LocationCallback |
iOS (KMP) | Core Location | Use callbackFlow with CLLocationManager |
CMP | Platform-specific | Wrap platform APIs in Flow |
For KMP, you can abstract this with an expect
/actual
setup. In your common code:
expect fun locationFlow(): Flow<Location>
Code language: HTML, XML (xml)
Android actual:
actual fun locationFlow(): Flow<Location> = // Same as above with Fused Location
Code language: HTML, XML (xml)
iOS actual (pseudo-Kotlin):
actual fun locationFlow(): Flow<Location> = callbackFlow {
val manager = CLLocationManager()
manager.delegate = // Custom delegate sending locations to trySend
manager.startUpdatingLocation()
awaitClose { manager.stopUpdatingLocation() }
}
Code language: HTML, XML (xml)
This keeps your shared code clean while letting platform-specific magic happen behind the scenes. CMP follows the same pattern—just add Compose UI on top.
Tips to Keep You Sane
Let’s wrap up with some hard-earned wisdom:
- Permissions First: Always check before you fetch. Android’s picky, and users hate surprises.
- Battery Life Matters: High-accuracy updates every second sound cool until your app’s blamed for a dead phone. Tune
interval
andpriority
to match your needs. - Error Handling: GPS off? No signal? Be ready with a friendly message instead of a crash.
- Repository Pattern: Stick location logic in a
LocationRepository
class. It’s cleaner and easier to test:
class LocationRepository(private val client: FusedLocationProviderClient) {
fun locationFlow(): Flow<Location> = // Flow implementation
}
Code language: HTML, XML (xml)
- Shut It Down: Stop updates when the app’s paused.
awaitClose
helps, but double-check your lifecycle.
Once, I forgot to stop updates and drained my test phone’s battery in an hour. My app was basically a vampire—don’t let yours be one too!
Wrapping The Location With Kotlin
There you have it—a crash course in grabbing the last known location and streaming updates with Kotlin Flow. We’ve covered the basics for Android, peeked at KMP and CMP, and thrown in some code to get you started. Whether you’re tracking pets, plotting runs, or just messing around, this setup’s got you covered.
Take these examples, tweak them, and build something awesome. If you get stuck, just imagine your location updates as little breadcrumbs leading you back to sanity. Go code something great—I’m rooting for you!
Happy coding!