<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Funny Shoebox</title>
    <description>The latest articles on DEV Community by Funny Shoebox (@johnandout).</description>
    <link>https://dev.to/johnandout</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2982982%2F5fb7a467-a433-4095-9726-768c44ad211f.png</url>
      <title>DEV Community: Funny Shoebox</title>
      <link>https://dev.to/johnandout</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/johnandout"/>
    <language>en</language>
    <item>
      <title>Tips on Building Android Applications with Long-running Location Service using Google Play Location API</title>
      <dc:creator>Funny Shoebox</dc:creator>
      <pubDate>Fri, 28 Mar 2025 23:02:27 +0000</pubDate>
      <link>https://dev.to/johnandout/tips-on-building-android-applications-with-long-running-location-service-using-google-play-location-bpi</link>
      <guid>https://dev.to/johnandout/tips-on-building-android-applications-with-long-running-location-service-using-google-play-location-bpi</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Google Play Location API is a powerful API that allows your Android app to utilize your phone's location functionalities with a plethora of customizations. &lt;/p&gt;

&lt;p&gt;This article presents a skeleton of a typical architecture of the location &lt;code&gt;Service&lt;/code&gt; class, and points out how the performance of the app can be tweaked in order to find the right balance between accuracy, power usage, privacy, and more. In the end, a quick summary of the essentials to make the app work and the tips and their takeaways are presented.&lt;/p&gt;

&lt;h2&gt;
  
  
  Architecture Skeleton
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Permissions
&lt;/h3&gt;

&lt;p&gt;To compile an app which uses Google Play Location API, we need to add &lt;code&gt;com.google.android.gms:play-services-location&lt;/code&gt; as the dependency to &lt;code&gt;build.gradle.kts&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dependencies {
    ...
    implementation 'com.google.android.gms:play-services-location:21.0.1'
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For the app to function properly in runtime, we need to add the following permissions to &lt;code&gt;AndroidManifest.xml&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;?xml version="1.0" encoding="utf-8"?&amp;gt;
&amp;lt;manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"&amp;gt;

    ...

    &amp;lt;uses-permission android:name="android.permission.FOREGROUND_SERVICE" /&amp;gt;
    &amp;lt;uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION" /&amp;gt;
    &amp;lt;uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" /&amp;gt;

    &amp;lt;uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /&amp;gt;
    &amp;lt;uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /&amp;gt;


    &amp;lt;uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" /&amp;gt;

    ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We leverage &lt;code&gt;FOREGROUND_SERVICE&lt;/code&gt;, &lt;code&gt;FOREGROUND_SERVICE_LOCATION&lt;/code&gt;, and &lt;code&gt;ACCESS_BACKGROUND_LOCATION&lt;/code&gt; to enable a foreground service, to enable a foreground service that uses location, and to access location while the app is in the background respectively.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ACCESS_COARSE_LOCATION&lt;/code&gt; and &lt;code&gt;ACCESS_FINE_LOCATION&lt;/code&gt; allows the user to control the granularity of the location precision, either an approximate or precise location, in order to strike a balance between the user's preference on privacy, power usage, and location-tracking performance. These two permissions are also required for some older Android APIs. See &lt;strong&gt;TIP A&lt;/strong&gt; below for details.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TIP A&lt;/strong&gt;: On Android's &lt;a href="https://developer.android.com/develop/background-work/services/fgs/restrictions-bg-start" rel="noopener noreferrer"&gt;Restrictions on starting a foreground service from the background&lt;/a&gt; page, although it states that &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"if the service declares a foreground service type of location and is started by an app that has the &lt;code&gt;ACCESS_BACKGROUND_LOCATION&lt;/code&gt; permission, this service can access location information all the time, even when the app runs in the background", &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;it is found that this behavior is inconsistent, and sometimes does not work as intended. However, this permission should always be included. Note that &lt;code&gt;ACCESS_BACKGROUND_LOCATION&lt;/code&gt; was added back in API 29, so the consistent behavior may have to do with some degrees of incompatibility between permissions &lt;/p&gt;

&lt;p&gt;Note that Android requires the following steps for Android 10 (API level 29) or lower:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Include the &lt;code&gt;ACCESS_BACKGROUND_LOCATION&lt;/code&gt; permission, as well as either the A&lt;code&gt;CCESS_COARSE_LOCATION&lt;/code&gt; or &lt;code&gt;ACCESS_FINE_LOCATION&lt;/code&gt; permission, in your app's manifest.&lt;br&gt;
Request the &lt;code&gt;ACCESS_BACKGROUND_LOCATION&lt;/code&gt; permission, as well as one of the other location permissions: &lt;code&gt;ACCESS_COARSE_LOCATION&lt;/code&gt; or ACCESS_FINE_LOCATION. You can request these permissions in a single operation."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;TIP B&lt;/strong&gt;: Some Android versions and devices may pause this location service periodically when it runs in the background, especially when battery saver or other power-saving mechanism is on. To override this, &lt;code&gt;REQUEST_IGNORE_BATTERY_OPTIMIZATIONS&lt;/code&gt; is used to allow the service to persist in the background without hibernating. Note that this should be brought up to the user, such as via a dialog, to notify them that their power usage might increase if they enable this bypass option.&lt;/p&gt;

&lt;p&gt;Setting up permissions is usually the hardest part to get right, especially when the app are designed to be backward compatible with older Android versions. Consult the Android documentation meticulously and use conditional statements to refine the scope of (runtime) permissions needed for each API versions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Location Service
&lt;/h3&gt;

&lt;h3&gt;
  
  
  Class Variables
&lt;/h3&gt;

&lt;p&gt;In our Service class, we want to define these variables for the Google Play Location API to work:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class LocationService: Service() {
    // Obsolete!
    private var serviceRunningInForeground = false

    // Recommended for "background running" service
    private lateinit var notificationManager: NotificationManager


    // Google Play services location APIs. 
    // fusedLocationProviderClient requires a LocationRequest and LocationCallback instance.
    private lateinit var fusedLocationProviderClient: FusedLocationProviderClient
    private lateinit var locationRequest: LocationRequest
    private lateinit var locationCallback: LocationCallback
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the past, it was typical for developers to create a Location &lt;code&gt;Service&lt;/code&gt; class which could switch between being in the foreground and background. In Android 11 or lower, this could be achieved by adding the flag (such as &lt;code&gt;serviceRunningInForeground&lt;/code&gt;) and using it to call functions such as &lt;code&gt;startForeground&lt;/code&gt; when applicable to control the service. &lt;/p&gt;

&lt;p&gt;However, this is no longer allowed as a security measure, and this requirement is now governed by Android's internal system policies: since location permission is a type of &lt;em&gt;while-in-use&lt;/em&gt; permissions, an app can never create a location service while being in the background, even if the app falls into some of the exemptions from background start restrictions.&lt;/p&gt;

&lt;p&gt;From a developer's point of view, although a foreground service could still transition into a background service, it is impossible to bring it back from the background into a foreground service: for Android 12 (API level 13) or higher, foreground services cannot be started while app is running in the background. In fact, if there are no foreground activities running, the app will crash when it attempts to start the background service or bringing a service from the background to the foreground.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TIP&lt;/strong&gt;: given this caveat, the only way to have location service running "in the background" is to always start the location service as a foreground service while the app has an activity visible to the user (i.e. a "visible activity"), and keeps the service in the foreground in some form (such as a notification). In fact, Android recommends using a notification via &lt;code&gt;NotificationManager&lt;/code&gt; to inform the user all the time that a service is accessing the location functionality of the device, as a measure for user privacy and to make the user aware of such tasks running.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;onCreate&lt;/code&gt; - When the Service is First Created
&lt;/h3&gt;

&lt;p&gt;Initialization of these variables are usually done in the &lt;code&gt;onCreate&lt;/code&gt; method:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    override fun onCreate() {
        // Initialize the notification manager so we can launch the service as a foreground service.
        notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager

        // The location provider
        fusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(this)

        // The LocationRequest object for requesting location updates.
        locationRequest = LocationRequest.Builder(10000)
            .setPriority(...)
            ... 
            .set...().build()

        // The LocationCallback object for handling location updates.
        locationCallback = object : LocationCallback() {
            override fun onLocationResult(locationResult: LocationResult) {
                if (locationResult.lastLocation != null) {
                    ...
                }

                val intent = Intent(ACTION_FOREGROUND_ONLY_LOCATION_BROADCAST)
                intent
                    .putExtra(EXTRA_LOCATION,
                     locationResult.lastLocation)

                LocalBroadcastManager
                    .getInstance(applicationContext)
                    .sendBroadcast(intent)
            }
        }

        startForeground(NOTIFICATION_ID, generateNotification())
    }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the &lt;code&gt;onCreate&lt;/code&gt; class, this is where the &lt;code&gt;LocationRequest&lt;/code&gt; is initialized. The service also uses the this method to attach to a relevant &lt;code&gt;NotificationManager&lt;/code&gt; and start in the foreground via &lt;code&gt;startForeground&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TIP&lt;/strong&gt;: &lt;code&gt;LocationRequest.Builder&lt;/code&gt; has a ton of methods that provides customizations to the location service running, including how often it is triggered, the duration for the whole service, the accuracy and quality of the ping, and the minimum update distance between location updates. &lt;br&gt;
I recommend that the app provides an UI for the user to customize this themselves, instead of setting a pre-defined instance for the whole app. This is particularly important because this single line of code can affect the performance of your app significantly, in terms of your use cases and functionalities, and the performance of the user's device: more accurate and frequent pinging of locations could result in a substantial increase in the device's power usage and possibly incur overheat issues. &lt;br&gt;
For example, simply increasing the ping interval from every 10 seconds to every minute could prolong the device battery by over 50% (from personal experience). Thus, depending on the end user's device and preference, the user should have the control of how they want your app to run.&lt;/p&gt;
&lt;h3&gt;
  
  
  &lt;code&gt;onStartCommand&lt;/code&gt; - Starting the Service
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
        return START_STICKY
    }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;In the &lt;code&gt;onStartCommand&lt;/code&gt;, which is called by the system whenever a client calls the service explicitly, return &lt;code&gt;START_STICKY&lt;/code&gt;. This means that the system will try to re-create this service (essentially restarting it) whenever this service is killed, e.g. when system resource is scarce.&lt;/p&gt;
&lt;h3&gt;
  
  
  [Optional] &lt;code&gt;onBind&lt;/code&gt;, &lt;code&gt;onRebind&lt;/code&gt;, &lt;code&gt;onUnbind&lt;/code&gt; (and &lt;code&gt;onTaskRemoved&lt;/code&gt;) - (Un-/Re-)Binding the Service to a Client
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    override fun onBind(intent: Intent): IBinder {
        stopForeground(STOP_FOREGROUND_REMOVE)
        serviceRunningInForeground = false

        // Your custom subclass for Binder
        return localBinder
    }

    override fun onRebind(intent: Intent) {
        stopForeground(STOP_FOREGROUND_REMOVE)
        serviceRunningInForeground = false

        super.onRebind(intent)
    }

    override fun onUnbind(intent: Intent): Boolean {
         if (!configurationChange) {
            startForeground(NOTIFICATION_ID, notification)
            serviceRunningInForeground = true
        }

        return true // Ensures onRebind() is called if MainActivity (client) rebinds.
    }


    override fun onTaskRemoved(rootIntent: Intent?) {
        if (!configurationChange) {
            Log.d(TAG, "Start foreground service")
            val notification = generateNotification(currentLocation)
            startForeground(NOTIFICATION_ID, notification)
            serviceRunningInForeground = true
        }
    }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Due to the Android API permission changes stated above, these code are no longer necessary for Android 12 or higher, since the location service should always be running as a foreground service. The code above is included as a reference to show how it was done before Android 12. In particular, it showcases the usage of the custom flag variable &lt;code&gt;serviceRunningInForeground&lt;/code&gt; as a helpful indicator to know where the service is currently running, and a &lt;code&gt;configurationChange&lt;/code&gt; flag to indicate whether the "change" is induced by a switch in service location, and not simply due to a configuration change such as rotating the screen from portrait to landscape. &lt;/p&gt;

&lt;p&gt;Thus, overriding &lt;code&gt;onRebind&lt;/code&gt;, &lt;code&gt;onUnbind&lt;/code&gt;, and &lt;code&gt;onTaskRemoved&lt;/code&gt; is no longer necessary, allowing developers to simply rely on their default implementations.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TIP&lt;/strong&gt;: Note that the &lt;code&gt;onBind&lt;/code&gt; method is still necessary. A custom &lt;code&gt;Binder&lt;/code&gt; subclass should be declare for the app to know that this service exists. This could be done easily with just three lines of code, as shown below.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    /**
     * Class used for the client Binder. Since this service runs in the same process as its
     * clients, we don't need to deal with inter-process communication (IPC).
     */
    inner class LocalBinder : Binder() {
        internal val service: ForegroundOnlyLocationService
            get() = this@ForegroundOnlyLocationService
    }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Allowing clients to subscribe to the Location Service
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    fun subscribeToLocationUpdates() {
        SharedPreferenceUtil.saveLocationTrackingPref(this, true)
        startService(
            Intent(
                applicationContext,
                ForegroundOnlyLocationService::class.java
            )
        )

    }

    fun unsubscribeToLocationUpdates() {
        try {
            val removeTask = fusedLocationProviderClient
                .removeLocationUpdates(locationCallback)

            removeTask.addOnCompleteListener { task -&amp;gt;
                ...
            }
            removeTask.addOnFailureListener { task -&amp;gt;
                ...
            }
        } catch (unlikely: SecurityException) {
            ...
        }
    }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, the location &lt;code&gt;Service&lt;/code&gt; class would need to public methods for clients (e.g. &lt;code&gt;Activity&lt;/code&gt;) to use this itself. The code above shows the standard way of how to start and end the service.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Working with Android has always been enjoyable due to its detailed documentation and a large collection of working examples online. When it comes to Google Play Location API, developers could get started easily, but they must be follow the documentation closely in order to cater for a wide variety of supported devices. &lt;/p&gt;

&lt;p&gt;Here are the main takeaways regarding optimization techniques and common pitfalls when working with Android and Google Play Location API:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Start with a simple architecture for the location service class, and build up from there.&lt;/li&gt;
&lt;li&gt;Include permissions that you need, based on your target Android versions and API levels. For each version, check what combination of permissions are needed for your app to work.&lt;/li&gt;
&lt;li&gt;Allow the user to customize your location service. Running the location functionality on a device is a power-hungry process, so users should have control on how much resources they are willing to give up. Customization includes permissions, location updates frequency, and how long the service is allowed to run.&lt;/li&gt;
&lt;li&gt;Continuing from above, user privacy should also be considered. Giving the user options to choose between high and low location accuracy, and showing the user that a location service is running (in the "background") are recommended practice from Android.&lt;/li&gt;
&lt;li&gt;Last but not least, when the location functionality is not working as intended, first consult Android's own documentation to see if there has been any updates to the usage of Google Play Location API. In particular, pay extra attention to the permissions and lifecycle of the service, which are the two most important factors when it comes to whether the service can be run at all.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;BONUS. If all else fails, explore other Android's capabilities such as &lt;code&gt;PowerManager.WakeLock&lt;/code&gt; and &lt;code&gt;AlarmManager&lt;/code&gt; as workarounds. These can also be used as safeguards to make sure the service does not dominate over the use of device resources. However, these should only be used as a last resort as existing location API should already cover your use cases.&lt;/p&gt;

&lt;p&gt;Happy Coding!&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://developer.android.com/develop/sensors-and-location/location/permissions" rel="noopener noreferrer"&gt;https://developer.android.com/develop/sensors-and-location/location/permissions&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://developer.android.com/develop/background-work/services/fgs/restrictions-bg-start" rel="noopener noreferrer"&gt;https://developer.android.com/develop/background-work/services/fgs/restrictions-bg-start&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://developer.android.com/reference/android/location/LocationRequest.Builder" rel="noopener noreferrer"&gt;https://developer.android.com/reference/android/location/LocationRequest.Builder&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://developer.android.com/develop/sensors-and-location/location/permissions" rel="noopener noreferrer"&gt;https://developer.android.com/develop/sensors-and-location/location/permissions&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://developer.android.com/reference/android/app/Service" rel="noopener noreferrer"&gt;https://developer.android.com/reference/android/app/Service&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Appendix - a Minimal Example of a Long-running Location Service
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class LocationService: Service() {
    private lateinit var notificationManager: NotificationManager
    private lateinit var fusedLocationProviderClient: FusedLocationProviderClient
    private lateinit var locationRequest: LocationRequest
    private lateinit var locationCallback: LocationCallback

    override fun onCreate() {
        notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager

        fusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(this)


        locationRequest = LocationRequest.Builder(10000)
            .setPriority(...)
            ... 
            .set...().build()

        locationCallback = object : LocationCallback() {
            override fun onLocationResult(locationResult: LocationResult) {
                if (locationResult.lastLocation != null) {
                    ...
                }

                val intent = Intent(ACTION_FOREGROUND_ONLY_LOCATION_BROADCAST)
                intent
                    .putExtra(EXTRA_LOCATION,
                     locationResult.lastLocation)

                LocalBroadcastManager
                    .getInstance(applicationContext)
                    .sendBroadcast(intent)
            }
        }

        startForeground(NOTIFICATION_ID, generateNotification())
    }

    override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
        return START_STICKY
    }

    inner class LocalBinder : Binder() {
        internal val service: ForegroundOnlyLocationService
            get() = this@ForegroundOnlyLocationService
    }
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>android</category>
      <category>gps</category>
      <category>location</category>
      <category>kotlin</category>
    </item>
  </channel>
</rss>
