Dealing with FusedLocationProviderClient, LocationRequest and LocationCallback classes when you need to fetch the current user location can be cumbersome. You don’t want to add this logic to the ViewModel because you will get stub exceptions when running unit tests (and generally, it’s bad practice). Do you add it to the activity/fragment, and then pass the data to the ViewModel? What happens if you need the location in another activity/fragment? This can get messy quick.
With the help of Flow and dependency injection, we can make this task a little smoother. Kotlin Coroutines introduced Flow, which is an Coroutines implementation of the Reactive Steams API. I won’t go into the workings of Flow, it has already been covered many times (see here), but the gist of it is: Asynchronous cold stream of values.
How does this apply to location updates? Well, we can wrap the location update logic we wrote above in a Flow builder. Here’s an example:
LocationUpdatesUseCase
is doing a few things here:
FusedLocationProviderClient
is injected via Dagger- Our
LocationCallback
is wrapped in a callBackFlow. WithincallBackFlow
,offer
is called for each update (MapLocation
is a simple data class to encapsulate location data) - Finally, resources are cleaned-up (i.e. the callback listener is removed) in
awaitClose
Now we can fetch location updates using:
Our location updates will be returned to the caller when a terminal event is called on the Flow(e.g. collect
). Using Flow in this way has a few advantages:
- Cold stream: the code inside a flow builder does not run until the Flow is collected.
- Cancellation: When the context from which the Flow was called is closed, the Flow is also closed. The
awaitClose
block is then called and our location update callback is removed. Tidy!