If we are working as Android Developers, almost always we will have some requirement to perform some task in the background. One of the common task is to receive location updates in the background.
Previously, we might have used Evernote Android-job
, BroadcastReceivers
, PendingIntent
, Push notification
, JobIntentService
, Firebase Job dispatcher
or some combination along those lines. But with Google's announcement of deprecating those implementations and moving to WorkManager
has given us a deadline to migrate our existing implementation towards that direction.
In this article, I am going to put some light on how to receive location updates in background using WorkManager.
Worker class
class UpdateLocationWorker(context: Context, workerParams: WorkerParameters, private val someRepository: SomeRepository) :
CoroutineWorker(context, workerParams) {
override suspend fun doWork(): Result {
return withContext(Dispatchers.IO) {
try {
someRepository.getLocation()
Result.success()
} catch (e: Exception) {
Log.d("$TAG", "Exception getting location --> ${e.message}")
Result.failure()
}
}
}
companion object {
private const val TAG = "UpdateLocationWorker"
private const val DEFAULT_MIN_INTERVAL = 15L
@JvmStatic
fun schedule() {
val worker = PeriodicWorkRequestBuilder<LocationUpdateWorker>(DEFAULT_MIN_INTERVAL, TimeUnit.MINUTES)
.addTag(TAG).build()
WorkManager.getInstance(getApplicationInstance()) // provide application instance here
.enqueueUniquePeriodicWork(TAG, ExistingPeriodicWorkPolicy.REPLACE, worker)
}
}
}
Note that the default minimum interval is 15
minutes and that is android default. If we set an interval less than 15
minutes, that will get overridden to 15 minutes
in runtime. More on that here.
Repository class
class SomeRepository(someDao: SomeDao, someLocationManager: SomeLocationManager, scope: CoroutineScope) {
suspend fun getLocation() {
someLocationManager.getUpdatedLocation { location -> onLocationReceived(location) }
}
internal fun onLocationReceived(location: Location?) = coroutineScope.launch {
location?.let { newLocation ->
saveLocation(LocationData(newLocation))
}
}
suspend fun saveLocation(location: LocationData) {
dao.saveLocation(location)
}
}
Location manager class
class SomeLocationManager(val context: Context) {
fun getUpdatedLocation(block: (Location?) -> Unit) {
val permission = ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION)
if (permission == PackageManager.PERMISSION_GRANTED) {
LocationServices.getFusedLocationProviderClient(context).lastLocation.addOnSuccessListener { location ->
block.invoke(location)
}
} else {
block.invoke(null)
}
}
}
Dao class
@Dao
interface SomeDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun saveLocation(location: LocationData)
}
LocationData and utility
data class LocationData(
val provider: String,
val time: Long,
val latitude: Double,
val longitude: Double,
val altitude: Double,
val accuracy: Float
)
fun Location.toLocationData(location: Location) = LocationData(
provider = "fused",
time = this.time,
latitude = this.latitude,
longitude = this.longitude,
altitude = this.altitude,
accuracy = this.accuracy
)
Usage
We could schedule the worker when MainActivity
or the Application
is started.
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle) {
super.onCreate(savedInstanceState)
UpdateLocationWorker.schedule()
}
}
Jobs scheduled with WorkManagers are guaranteed to run in the given interval even if the app is closed or force killed.
For further reading, please follow the WorkManger link above.
Also, please feel free to provide feedback in comments.
Thank you!
Top comments (6)
Hey ! thanks for the tutorial, however when I run the app I get :
Could not instantiate com.david.dave.backgroundTasks.FirebaseUpdateLocationBackgroundTask
Any idea why ?
How to create & pass someRepository instance to UpdateLocationWorker class?
@Hiral For that, you will need to implement your own
WorkerFactory
class and set the worker configuration in yourApplication
class. Something like below:This is one way of implementing this. If you are familiar with DI tools like dagger, there are other ways of implementing this.
I hope this helps.
thank u. This should work.
This is not true. If you force-kill your app from System apps menu, your WorkManager will cease to run.
Hi @Igor Ganapolsky, please correct me if I am wrong, but based on this documentation, once workmanager tasks are scheduled, those will be executed independent of app's life-cycle or even if the device restarts. The tasks are guaranteed to run.