How to get continuous location updates in Android

this post, I will explain how to update the live location to the server every some interval. For example, sending live location to the server every 1 minute.

To fetch the live location we are using an android service to run in the background to fetch the location continuously within the given time interval. Due to the current restriction on android services, we cannot run the service in the background or after closing the app. To run the android app in the background we need to start the service in the foreground.

I am using locationListener to fetch the location information on the given interval. Also, using Retrofit to upload location details to API.

Step to get continuous location updates on android

  1. Check Location Permissions
  2. Start service for location updates
  3. Start Location update listener
  4. Call the API to update the location of the server
  5. Adding boot complete receiver

Check Location Permissions

To get the location in android we need ACCESS_FINE_LOCATION and ACCESS_COARSE_LOCATION permissions. So we need to ask this permission in run time.

define the permission in Manifests.xml.

<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

checking the permissions at runtime,

if (!checkPermission()) {
            requestPermission()
        }
        private fun checkPermission(): Boolean {
            val result = ContextCompat.checkSelfPermission(applicationContext, Manifest.permission.ACCESS_FINE_LOCATION)
            val result1 = ContextCompat.checkSelfPermission(applicationContext, Manifest.permission.ACCESS_COARSE_LOCATION)
            return result == PackageManager.PERMISSION_GRANTED && result1 == PackageManager.PERMISSION_GRANTED
        }
        private fun requestPermission() {
            ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION), 1)
        }

Start service for location updates

Create LocationService class and extends the service to create a service. Also, override the onstart and onstartcommand methods.

class LocationService : Service() {

        override fun onCreate() {
            super.onCreate()

        }
        override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
            return START_STICKY
        }
        override fun onBind(intent: Intent): IBinder? {
            return null
        }
        override fun onDestroy() {
            super.onDestroy()
        }

    }

The next step is to run the service to fetch the location continuously. So as I mentioned before we need to start the service as a foreground service to run on Android O.

 ContextCompat.startForegroundService(this, Intent(this, LocationService::class.java))

On the oncreate of the service, we need to start the foreground with a notification. This will avoid the termination of the android app after closing.

 override fun onCreate() {
            super.onCreate()
            isServiceStarted = true
            val builder: NotificationCompat.Builder =
                NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID)
                    .setOngoing(false)
                    .setSmallIcon(R.drawable.ic_launcher_background)
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                val notificationManager: NotificationManager =
                    getSystemService(NOTIFICATION_SERVICE) as NotificationManager
                val notificationChannel = NotificationChannel(
                    NOTIFICATION_CHANNEL_ID,
                    NOTIFICATION_CHANNEL_ID, NotificationManager.IMPORTANCE_LOW
                )
                notificationChannel.description = NOTIFICATION_CHANNEL_ID
                notificationChannel.setSound(null, null)
                notificationManager.createNotificationChannel(notificationChannel)
                startForeground(1, builder.build())
            }
        }

Once, the service class is created, we need to define the service in the manifest file inside the application tag.

<service
                android:name=".LocationService"
                android:enabled="true"
                android:exported="true" />

Also, we need to add foreground service permission in the user permissions to run the foreground service.

<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />

Start Location update listener

In the onStartCommand of the service, we need to start the location update listener to fetch the location update on the service. I have created the LocationHelper class to initiate the location listener. also, create an interface to update the live location to service.

Check this link to learn more about getting the current location in detail.

LocationHelper.kt

class LocationHelper {
        var LOCATION_REFRESH_TIME = 3000 // 3 seconds. The Minimum Time to get location update
        var LOCATION_REFRESH_DISTANCE =
            0 // 0 meters. The Minimum Distance to be changed to get location update
        @SuppressLint("MissingPermission")
        fun startListeningUserLocation(context: Context, myListener: MyLocationListener) {
            val mLocationManager = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
            val locationListener: LocationListener = object : LocationListener {
                override fun onLocationChanged(location: Location) {
                    myListener.onLocationChanged(location) // calling listener to inform that updated location is available
                }
                override fun onProviderEnabled(provider: String) {}
                override fun onProviderDisabled(provider: String) {}
                override fun onStatusChanged(provider: String, status: Int, extras: Bundle) {}
            }
            mLocationManager.requestLocationUpdates(
                LocationManager.GPS_PROVIDER,
                LOCATION_REFRESH_TIME.toLong(),
                LOCATION_REFRESH_DISTANCE.toFloat(),
                locationListener
            )
        }
    }
    interface MyLocationListener {
        fun onLocationChanged(location: Location?)
    }

in the requestLocationUpdates(), we need to pass the location provided, the minimum time for the location refresh, and the minimum distance for the location refresh. in our case, we need to get the location every 3 seconds. So I set 3000 ms.

Now, we need to call the startListeningUserLocation() helper method in our onStartCommand().

override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
            val timer = Timer()
            LocationHelper().startListeningUserLocation(
                this, object : MyLocationListener {
                    override fun onLocationChanged(location: Location?) {
                        mLocation = location
                        mLocation?.let {
                        }
                    }
                })
            return START_STICKY
        }

Call the API to update the location of the server

Once you receive the location on service, er need to call the API from the service. To call API, I am using a retrofit API client. Please check the below link to learn more about the retrofit setup.

Retrofit android example [step by step]

ApiClient.kt

interface ApiClient {
        @GET("updateLocation.json")
        fun updateLocation() : Call<LocationResponse>
        companion object {
            var retrofit: Retrofit? = null
            val logging = HttpLoggingInterceptor().apply {
                this.level = HttpLoggingInterceptor.Level.BODY
            }
            fun getInstance(context: Context) : Retrofit {
                if (retrofit == null) {
                    retrofit = Retrofit.Builder()
                        .baseUrl("https://www.howtodoandroid.com/apis/")
                        .client(OkHttpClient.Builder().addInterceptor(logging).build())
                        .addConverterFactory(GsonConverterFactory.create())
                        .build()
                }
                return retrofit!!
            }
        }
    }

Once the retrofit setup is completed, we need to call the API call in the background thread. service will run on the main thread. So I have created AppExecutor to run the API call in the background.

AppExecutors.kt

class AppExecutors private constructor(
        private val diskIO: Executor,
        private val networkIO: Executor,
        private val mainThread: Executor
    ) {
        fun diskIO(): Executor {
            return diskIO
        }
        fun mainThread(): Executor {
            return mainThread
        }
        fun networkIO(): Executor {
            return networkIO
        }
        private class MainThreadExecutor : Executor {
            private val mainThreadHandler = Handler(Looper.getMainLooper())
            override fun execute(command: Runnable) {
                mainThreadHandler.post(command)
            }
        }
        companion object {
            // For Singleton instantiation
            private val LOCK = Any()
            private var sInstance: AppExecutors? = null
            val instance: AppExecutors?
                get() {
                    if (sInstance == null) {
                        synchronized(LOCK) {
                            sInstance = AppExecutors(
                                Executors.newSingleThreadExecutor(),
                                Executors.newFixedThreadPool(3),
                                MainThreadExecutor()
                            )
                        }
                    }
                    return sInstance
                }
        }
    }

If you run this code now, you can see the app with an update of the current location to a server at every given interval.

LocationService.kt

class LocationService : Service() {
        private val NOTIFICATION_CHANNEL_ID = "my_notification_location"
        private val TAG = "LocationService"
        override fun onCreate() {
            super.onCreate()
            isServiceStarted = true
            val builder: NotificationCompat.Builder =
                NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID)
                    .setOngoing(false)
                    .setSmallIcon(R.drawable.ic_launcher_background)
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                val notificationManager: NotificationManager =
                    getSystemService(NOTIFICATION_SERVICE) as NotificationManager
                val notificationChannel = NotificationChannel(
                    NOTIFICATION_CHANNEL_ID,
                    NOTIFICATION_CHANNEL_ID, NotificationManager.IMPORTANCE_LOW
                )
                notificationChannel.description = NOTIFICATION_CHANNEL_ID
                notificationChannel.setSound(null, null)
                notificationManager.createNotificationChannel(notificationChannel)
                startForeground(1, builder.build())
            }
        }
        override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
            val timer = Timer()
            LocationHelper().startListeningUserLocation(
                this, object : MyLocationListener {
                    override fun onLocationChanged(location: Location?) {
                        mLocation = location
                        mLocation?.let {
                            AppExecutors.instance?.networkIO()?.execute {
                                val apiClient = ApiClient.getInstance(this@LocationService)
                                    .create(ApiClient::class.java)
                                val response = apiClient.updateLocation()
                                response.enqueue(object : Callback<LocationResponse> {
                                    override fun onResponse(
                                        call: Call<LocationResponse>,
                                        response: Response<LocationResponse>
                                    ) {
                                        Log.d(TAG, "onLocationChanged: Latitude ${it.latitude} , Longitude ${it.longitude}")
                                        Log.d(TAG, "run: Running = Location Update Successful")
                                    }
                                    override fun onFailure(call: Call<LocationResponse>, t: Throwable) {
                                        Log.d(TAG, "run: Running = Location Update Failed")
                                    }
                                })
                            }
                        }
                    }
                })
            return START_STICKY
        }
        override fun onBind(intent: Intent): IBinder? {
            return null
        }
        override fun onDestroy() {
            super.onDestroy()
            isServiceStarted = false
        }
        companion object {
            var mLocation: Location? = null
            var isServiceStarted = false
        }
    }
    Adding boot complete receiver
    If you want to start the service automatically after the restart of the device, You need to add the broadcast receiver for boot completed receiver.

    class BootDeviceReceivers : BroadcastReceiver() {
        override fun onReceive(context: Context?, intent: Intent?) {

        }
    }

Adding boot complete receiver

If you want to start the service automatically after the restart of the device, You need to add the broadcast receiver for the boot completed receiver.

class BootDeviceReceivers : BroadcastReceiver() {
        override fun onReceive(context: Context?, intent: Intent?) {

        }
    }

on the onReceive of the boot complete, we need to start the service again. We can do this by calling the same service again.

class BootDeviceReceivers : BroadcastReceiver() {
        override fun onReceive(context: Context?, intent: Intent?) {
            context?.let {
                ContextCompat.startForegroundService(it, Intent(it, LocationService::class.java))
            }
        }
    }

Also, don’t forget to add the broadcast receiver on the manifeats.xml file inside the application tag.

<receiver
                android:name=".BootDeviceReceivers"
                android:enabled="true"
                android:exported="true">
                <intent-filter>
                    <action android:name="android.intent.action.BOOT_COMPLETED"/>
                </intent-filter>
            </receiver>

That’s it. Now you can fetch the location continuously and send the location to the server. Download this example from GITHUB.

Thanks for reading. Please provide your feedback in the comments.


Comments

Leave a Reply

Your email address will not be published. Required fields are marked *


Latest Posts