barbeau / gpstest

The #1 open-source Android GNSS/GPS test program
Apache License 2.0
1.77k stars 362 forks source link

Run GPSTest as foreground Service when in background #299

Closed barbeau closed 2 years ago

barbeau commented 5 years ago

Summary:

GPSTest was originally implemented to remaining running in the background when you press the Home key. As discussed in https://github.com/barbeau/gpstest/issues/237 and https://github.com/barbeau/gpstest/issues/297, this has been interpreted to be a feature, not a bug, as it can be helpful to prime GPS in some situations.

However, given the advances in Android since 1.5, background execution should be overhauled to the current Android best practices, which means running it as a foreground Service when in the background. This will allow consistent behavior across devices and a clear notice to the user that GPSTest is still running in the background.

Some UI decisions need to be implemented here, including whether we prompt the user to ask if they want to continue running GPSTest in the background at some point, or just invert the default option and always have GPSTest stop running when the user puts the app in the background unless the user enables a setting to run in the background.

Steps to reproduce:

Start GPSTest and hit the Home button

Expected behavior:

Give me some notice that it's running in the background, and use an Android foreground Service

Observed behavior:

App simply doesn't de-register the LocationListener, which on some devices means it continues running in the background, while on other devices it will be killed. EDIT Dec 10, 2020 As of https://github.com/barbeau/gpstest/commit/ae93b7ffe79c19686c2179ad4181c92f4702509b, the LocationListener is always unregistered in Activity onPause().

Device and Android version:

N/A

barbeau commented 5 years ago

As mentioned in https://github.com/barbeau/gpstest/issues/306, users would also like to be able to log to Android Studio and eventually files when the screen turns off - this is the same issue.

barbeau commented 5 years ago

We also get to specify a notification when running in the background - a user requested something similar to the GPS Status app:

image

GPS Tools looks like this:

image

zemix commented 4 years ago

Yeah, thanks for replay on Play Store, I personally absolutel support this topic, the way it is implemented in GPS Locker application for example! (duplicate my review) "Nice GPS testing app! Only one suggestion - add please ability to lock🔒GPS signal(status) after pressing back button (exit from app) for background staying Gps alive and don't loose 🛰 satellites! Of course as Option in Settings. It would be very useful, because for my own opinion as a solid user - your GPSTest app finds signal and lock satellites more faster and I am really love Sorting option in your app."

barbeau commented 4 years ago

Related to implementing ViewModel and Service:

barbeau commented 3 years ago

Given the most recent Android architecture principles and tools, it seems like the following is the best architecture:

  1. Use a foreground service to listen for location updates
  2. Send location and calculated metadata to a datastore that supports observables. Room with Flow (as of Room 2.2) seems like the best option (see this for an example).
  3. Listen to the datastore within the Fragments using LiveData and ViewModels, which will then populate the views when new updates get send to the datastore.

This design is discussed here, with the below demo app currently using LocalBroadcastManager instead of Room with Flow (with a plan to remove LBMs for Room or DataStore): https://github.com/googlecodelabs/while-in-use-location/issues/12

DataStore is a possible alternative to Room, although with some of the more complex metadata it seems like Room is a better option.

barbeau commented 3 years ago

Good example of LifecycleService: https://github.com/gs-ts/TrackMyPath/blob/c3bd825d4c3f2eaea37ca5e659eb6127f5778e14/app/src/main/java/com/gts/trackmypath/presentation/service/LocationService.kt

Some other good resources:

barbeau commented 3 years ago

I've created a demo here (based on Google's code sample) in the room branch that uses Room and Flow for location updates: https://github.com/barbeau/while-in-use-location

And an article covering implementation: https://barbeau.medium.com/room-kotlin-flow-the-modern-android-architecture-for-location-aware-apps-9c110e12e31a

barbeau commented 3 years ago

Here's a good example showing StateFlow with loading, etc. states: https://github.com/philipplackner/StateFlow

...from this YouTube video: https://www.youtube.com/watch?v=Qk2mIpE_riY&t=325s

See also: https://developer.android.com/kotlin/flow/stateflow-and-sharedflow#stateflow

Using MutableStateFlow without a backing private variable: https://link.medium.com/8Vn6X07Cwhb

With Jetpack Compose: https://youtu.be/QDcBO4iROyE

barbeau commented 3 years ago

Good example of managing service restarts using KTX: https://github.com/android/health-samples/blob/f32c8956daa3f9e2c9bce6cf45102a8ae34bc22e/ExerciseSample/app/src/main/java/com/example/exercise/ExerciseService.kt

And example of music service in background using Kotlin: https://github.com/android/uamp/blob/99e44c1c5106218c62eff552b64bbc12f1883a22/common/src/main/java/com/example/android/uamp/media/MusicService.kt

barbeau commented 3 years ago

Another possibility is to wrap the location listener in its own LocationRepository (e.g. instead of using Room).

Some guidance in this direction:

Guidance on converting callback APIs to flow:

Example of using Kotlin and LiveData with LocationManager/LocationRepo design, but uses PendingIntents - maybe we could convert this to Flow (?):

There is actually a snippet in this article: https://medium.com/androiddevelopers/a-safer-way-to-collect-flows-from-android-uis-23080b1f8bda

...that points to this Gist: https://gist.github.com/manuelvicnt/92d1bf42f413d519f0e2cf1aff0f9b9d#file-locationflow-kt

...which is close to what we want:

// Implementation of a cold flow backed by a Channel that sends Location updates
fun FusedLocationProviderClient.locationFlow() = callbackFlow<Location> {
    val callback = object : LocationCallback() {
        override fun onLocationResult(result: LocationResult?) {
            result ?: return
            try { offer(result.lastLocation) } catch(e: Exception) {}
        }
    }
    requestLocationUpdates(createLocationRequest(), callback, Looper.getMainLooper())
        .addOnFailureListener { e ->
            close(e) // in case of exception, close the Flow
        }
    // clean up when Flow collection ends
    awaitClose {
        removeLocationUpdates(callback)
    }
}

EDIT - the full code for the above snippet is in https://github.com/googlecodelabs/kotlin-coroutines/blob/master/ktx-library-codelab/step-06/

barbeau commented 3 years ago

A good video visually explaining the differences between the different types of Flows with animations, in context of updating locations on a map: https://youtu.be/RoGAb0iWljg

And the corresponding repos:

barbeau commented 2 years ago

Here's an article I wrote on wrapping the location listener in its own LocationRepository with Kotlin callbackFlow: https://barbeau.medium.com/kotlin-callbackflow-a-lightweight-architecture-for-location-aware-android-apps-f97b5e66aaa2

Link to example on GitHub is in the article. I'm using this pattern in GPSTest.