Skip to content

Instantly share code, notes, and snippets.

Last active May 23, 2024 12:11
Show Gist options
  • Save LouisCAD/0a648e2b49942acd2acbb693adfaa03a to your computer and use it in GitHub Desktop.
Save LouisCAD/0a648e2b49942acd2acbb693adfaa03a to your computer and use it in GitHub Desktop.
Create a Flow of location updates on Android (using kotlinx.coroutines), backed by Fused Location Provider from Google Play Services.
* Copyright 2019 Louis Cognault Ayeva Derman. Use of this source code is governed by the Apache 2.0 license.
import android.location.Location
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.channels.SendChannel
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.channelFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.tasks.await
import splitties.init.appCtx // Similar to Firebase auto-initialization of applicationContext,
// usages can be replaced with Context receiver or parameter if you really enjoy parameter passing.
inline fun fusedLocationFlow(
configLocationRequest: LocationRequest.() -> Unit
): Flow<Location> = fusedLocationFlow(
locationRequest = LocationRequest.create().apply(configLocationRequest)
fun fusedLocationFlow(
locationRequest: LocationRequest
): Flow<Location> = channelFlow {
val locationClient = LocationServices.getFusedLocationProviderClient(appCtx)
val locationCallback = object : LocationCallback() {
override fun onLocationResult(result: LocationResult) {
result.locations.forEachByIndex { offerCatching(it) }
locationClient.lastLocation.await<Location?>()?.let { send(it) }
locationClient.requestLocationUpdates(locationRequest, locationCallback, null).await()
awaitClose {
* [SendChannel.offer] that returns `false` when this [SendChannel.isClosedForSend], instead of
* throwing.
* [SendChannel.offer] throws when the channel is closed. In race conditions, especially when using
* multithreaded dispatchers, that can lead to uncaught exceptions as offer is often called from
* non suspending functions that don't catch the default [CancellationException] or any other
* exception that might be the cause of the closing of the channel.
// Copy pasted from splitties.coroutines
fun <E> SendChannel<E>.offerCatching(element: E): Boolean {
return runCatching { offer(element) }.getOrDefault(false)
* Iterates the receiver [List] using an index instead of an [Iterator] like [forEach] would do.
* Using this function saves an [Iterator] allocation, which is good for immutable lists or usages
* confined to a single thread like UI thread only use.
* However, this method will not detect concurrent modification, except if the size of the list
* changes on an iteration as a result, which may lead to unpredictable behavior.
* @param action the action to invoke on each list element.
// Copy pasted from splitties.collections
inline fun <T> List<T>.forEachByIndex(action: (T) -> Unit) {
val initialSize = size
for (i in 0..lastIndex) {
if (size != initialSize) throw ConcurrentModificationException()
Copy link

i think so, i already made this class today and it worked without await()

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment