Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Run GPSTest as foreground Service when in background #299

Closed
barbeau opened this issue Jun 10, 2019 · 12 comments · Fixed by #550
Closed

Run GPSTest as foreground Service when in background #299

barbeau opened this issue Jun 10, 2019 · 12 comments · Fixed by #550
Milestone

Comments

@barbeau
Copy link
Owner

barbeau commented Jun 10, 2019

Summary:

GPSTest was originally implemented to remaining running in the background when you press the Home key. As discussed in #237 and #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 ae93b7f, the LocationListener is always unregistered in Activity onPause().

Device and Android version:

N/A

@barbeau
Copy link
Owner Author

barbeau commented Jul 2, 2019

As mentioned in #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
Copy link
Owner Author

barbeau commented Sep 17, 2019

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

@barbeau barbeau modified the milestones: v3.4, v3.5, v3.6 Sep 20, 2019
@zemix
Copy link

zemix commented Feb 20, 2020

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
Copy link
Owner Author

barbeau commented Apr 15, 2021

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):
android/codelab-while-in-use-location#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
Copy link
Owner Author

barbeau commented May 18, 2021

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
Copy link
Owner Author

barbeau commented Jun 28, 2021

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
Copy link
Owner Author

barbeau commented Jul 3, 2021

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
Copy link
Owner Author

barbeau commented Jul 15, 2021

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
Copy link
Owner Author

barbeau commented Nov 10, 2021

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.

barbeau added a commit that referenced this issue Nov 11, 2021
This is a major overhaul of the app internals to support a foreground service (i.e., a notification that shows current GNSS status even when the main Activity UI is not showing).

This involves moving all the API listeners for GNSS events into their own "manager" classes based on Kotlin callbackFlow that exist the entire lifecycle of the app, so the various app components (Fragments, ViewModels, Activity, Service) can all receive updates from this single source.

This bumps minSdkVersion to Android N (24) so we we don't need to deal with the legacy GPS* APIs anymore, which greatly simplifies the data processing within the app.

*  Convert Activity and Fragments to Kotlin
*  Add service with notification
*  Remove legacy GPS* APIs (pre-Android N)
*  Convert all location / GNSS API registrations to data managers based on Kotlin callbackFlow as detailed in https://barbeau.medium.com/kotlin-callbackflow-a-lightweight-architecture-for-location-aware-android-apps-f97b5e66aaa2.
    *  NMEA
    *  Location
    *  GNSS measurement
    *  Antenna
    *  Nav message
    *  Sensor (orientation)
* Add view binding to fragments
* Fix filter bug - it currently shows # signals being filtered, not number satellites (doesn't match # Sats field)
* Move aggregation of signals to satellites into Flow/ViewModel.
* Change Status to Composable - Use LiveData in ViewModel instead - see https://bladecoder.medium.com/kotlins-flow-in-viewmodels-it-s-complicated-556b472e281a that says don't use Flows in ViewModels.

Closes #299
Closes #492
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants