In 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.
Also, check out the video version of this post.
Step to get continuous location updates on android
- Check Location Permissions
- Start service for location updates
- Start Location update listener
- Call the API to update the location of the server
- 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" />
Code language: HTML, XML (xml)
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)
}
Code language: JavaScript (javascript)
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))
Code language: JavaScript (javascript)
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())
}
}
Code language: JavaScript (javascript)
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" />
Code language: HTML, XML (xml)
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" />
Code language: HTML, XML (xml)
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?)
}
Code language: PHP (php)
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
}
Code language: JavaScript (javascript)
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!!
}
}
}
Code language: PHP (php)
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 able to 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?) {
}
}
Code language: HTML, XML (xml)
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>
Code language: HTML, XML (xml)
That’s it. Now you can able to 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.
Leave a Reply