Pandiyan Mani
3 min readSep 29, 2022

--

Get location using Background/Foreground Service in Android

First we need to implement

implementation 'com.google.android.gms:play-services-location:20.0.0'

in the app level build gradle file then we need to add the required permission in the manifest file such as

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

I am creating an interface which holds interval for location update and another class inside interface for error message in case of any failure

package com.vibe.getlocation

import android.location.Location
import kotlinx.coroutines.flow.Flow

interface LocationClient {

fun getLocationUpdates(interval: Long): Flow<Location>

class LocationException(message: String): Exception()
}

After i am creating an file for Permission check

package com.vibe.getlocation

import android.Manifest
import android.content.Context
import android.content.pm.PackageManager
import androidx.core.content.ContextCompat

fun Context.hasPermission(): Boolean {
return ContextCompat.checkSelfPermission(
this,
Manifest.permission.ACCESS_COARSE_LOCATION
) == PackageManager.PERMISSION_GRANTED &&
ContextCompat.checkSelfPermission(
this,
Manifest.permission.ACCESS_FINE_LOCATION
) == PackageManager.PERMISSION_GRANTED
}

And we create a class for location client implementation LocationClientImpl

package com.vibe.getlocation

import android.annotation.SuppressLint
import android.content.Context
import android.location.Location
import android.location.LocationManager
import android.os.Looper
import com.google.android.gms.location.FusedLocationProviderClient
import com.google.android.gms.location.LocationCallback
import com.google.android.gms.location.LocationRequest
import com.google.android.gms.location.LocationResult
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.launch

class LocationClientImpl(
private val context: Context,
private val client: FusedLocationProviderClient
): LocationClient {

@SuppressLint("MissingPermission")
override fun getLocationUpdates(interval: Long): Flow<Location> {
return callbackFlow {
if(!context.hasPermission()) {
throw LocationClient.LocationException("Misssing Location Permission")
}
val locationManager = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
val isGpsEnabled = locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER);
val isNetworkEnabled = locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER)
if(!isGpsEnabled && !isNetworkEnabled) {
throw LocationClient.LocationException("Gps disbled")
}

val request = LocationRequest.create()
.setInterval(interval)
.setFastestInterval(interval)

val locationcallback = object: LocationCallback() {
override fun onLocationResult(result: LocationResult) {
super.onLocationResult(result)
result.locations.lastOrNull()?.let {
launch { send(it) }
}
}
}

client.requestLocationUpdates(
request,
locationcallback,
Looper.getMainLooper()
)


awaitClose {
client.removeLocationUpdates(locationcallback)
}
}
}
}

Then we create a service for Start and stop location update

package com.vibe.getlocation

import android.app.NotificationManager
import android.app.Service
import android.content.Context
import android.content.Intent
import android.os.IBinder
import androidx.core.app.NotificationCompat
import com.google.android.gms.location.LocationServices
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach

class LocationService: Service() {
private val serviceScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
lateinit var locationClient: LocationClient
override fun onBind(p0: Intent?): IBinder? {
return null
}

override fun onCreate() {
super.onCreate()
locationClient = LocationClientImpl(
applicationContext,
LocationServices.getFusedLocationProviderClient(applicationContext)
)
}

override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
return super.onStartCommand(intent, flags, startId)
when(intent!!.action) {
ACTION_START -> start()
ACTION_STOP -> stop()
}
}

fun start() {
val notification = NotificationCompat.Builder(this, "location")
.setContentTitle("Tracking Lccation")
.setContentText("Location is null")
.setSmallIcon(R.drawable.ic_launcher_background)
.setOngoing(true)

val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
locationClient.getLocationUpdates(1000L)
.catch { e -> e.printStackTrace() }
.onEach {
val lat = it.latitude
val long = it.longitude
val updatenotifcation = notification.setContentText(
"Location: ($lat,$long)"
)
notificationManager.notify(
1,
updatenotifcation.build()
)
}.launchIn(serviceScope)

startForeground(1, notification.build())
}

fun stop() {
stopForeground(true)
stopSelf()
}

override fun onDestroy() {
super.onDestroy()
serviceScope.cancel()
}

companion object {
const val ACTION_START = "ACTION_START"
const val ACTION_STOP = "ACTION_STOP"
}
}

and add that service in manifest as

<service android:name=".LocationService" android:foregroundServiceType="location"></service>

After i am creating an application class for notification channel

package com.vibe.getlocation

import android.app.Application
import android.app.NotificationChannel
import android.app.NotificationManager
import android.content.Context
import android.os.Build

class MyApp: Application() {

override fun onCreate() {
super.onCreate()
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel = NotificationChannel(
"location",
"location",
NotificationManager.IMPORTANCE_DEFAULT
)
val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
notificationManager.createNotificationChannel(channel)
}
}
}

and declared under application tag of manifest file as below

<application
android:name=".MyApp"

Finally we call from th MainActivity

package com.vibe.getlocation


import android.content.Intent
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.material.Button
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.vibe.getlocation.ui.theme.GetLocationTheme

class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
GetLocationTheme {
// A surface container using the 'background' color from the theme
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colors.background
) {
Column(modifier = Modifier.fillMaxSize()) {
Button(onClick = { Intent(applicationContext, LocationService::class.java).apply {
action = LocationService.ACTION_START
startService(this)
} }) {
Text(text = "Start")

}

Spacer(modifier = Modifier.height(10.dp))
Button(onClick = { Intent(applicationContext, LocationService::class.java).apply {
action = LocationService.ACTION_STOP
startService(this)
} }) {
Text(text = "Stop")

}
}
}
}
}
}
}

--

--