Kotlin extension functions for easy and idiomatic GeoQuery
ing on Firebase Database with GeoFire, on Android.
GeoFire is built upon callbacks.
This library provides a series of extension functions that allow for a more idiomatic use of GeoQuery in Kotlin Android projects, by:
- Converting
GeoQuery
callbacks to KotlinFlow
s - Automatically mapping
GeoLocation
Flow
s to correspondingDataSnapshot
in another database path. - Automatically mapping
GeoLocation
Flow
s to corresponding data of typeT
in another database path.
On project-level build.gradle
, add Jitpack repository:
allprojects {
repositories {
maven { url 'https://jitpack.io' }
}
}
On app-level build.gradle
, add dependency:
dependencies {
implementation 'com.github.psteiger:geofire-ktx:0.6.0'
}
Given the GeoQuery
:
val geoFire = GeoFire(Firebase.database.getReference("geofire"))
val geoLocation = GeoLocation(0.0, 0.0)
val radius = 100.0
val geoQuery = geoFire.queryAtLocation(geoLocation, radius)
We recommend converting the GeoQuery to Flows for consuming the query result data. You can convert it to:
Flow<Map<Key, GeoLocation>>
Flow<Map<Key, LocationDataSnapshot>>
// A pair of GeoLocation and DataSnapshotFlow<Map<Key, LocationData<T>>
// A pair of GeoLocation and data of type T.
2 and 3 are for the use cases of querying GeoLocations to subsequently query for the related data on another database reference.
val nearbyGeoLocations: Flow<Map<Key, GeoLocation>> =
geoQuery
.asFlow()
.flowOn(Dispatchers.IO)
val nearbyUsers: Flow<Map<Key, LocationDataSnapshot>> =
geoQuery
.asFlow(Firebase.database.getReference("users"))
.flowOn(Dispatchers.IO)
.onEach { map ->
map.onEach {
val key = it.key
val (geoLocation, dataSnapshot) = it.value
}
}
val nearbyUsers: Flow<Map<Key, LocationData<User>>> =
geoQuery
.asTypedFlow<User>(Firebase.database.getReference("users"))
.flowOn(Dispatchers.IO)
.onEach { map ->
map.onEach {
val key = it.key
val (geoLocation, user) = it.value
}
}
- In examples above,
Key
is just atypealias
toString
. - All flows above are cold, and need to be collected so they start running (e.g. with
launchIn()
) - Consider converting the flows to
SharedFlow
(Flow<T>.shareIn()
) orStateFlow
(Flow<T>.stateIn()
) if multiple collectors will be used.
If you still want to use GeoQuery callbacks and not Kotlin flows, we also offer a convenience builder for building the callback object in a more idiomatic way:
sealed class GeoQueryState {
data class Ready(val geoLocations: Map<Key, GeoLocation>) : GeoQueryState
data class Cancelled(val exception: Exception) : GeoQueryState
}
private val _geoLocations = MutableSharedFlow<GeoQueryState>() // private mutable shared flow
val geoLocations = _geoLocations.asSharedFlow() // publicly exposed as read-only shared flow
geoQuery.addGeoQueryEventListener {
val locations = mutableMapOf<Key, GeoLocation>()
onKeyEntered { locations[this] = it }
onKeyExited { locations.remove(this) }
onKeyMoved { locations[this] = it }
onGeoQueryReady { geoLocations.emit(GeoQueryState.Ready(locations.toMap())) }
onGeoQueryError { geoLocations.emit(GeoQueryState.Cancelled(toException())) }
}