I am downloading file with "Coroutine Worker" work Manager, when application is in background the downloading work properly but when application is killed, work Manager stop working.
I tried to keep it alive with a boolean variable in an infinite loop to make it work but it didn't work as well.
class UploadWorker(
private val appContext: Context,
workerParams: WorkerParameters
) : CoroutineWorker(appContext, workerParams) {
private var isDownloadCompleted = false
override suspend fun doWork(): Result {
val filName = inputData.getString("filName") ?: ""
val url = inputData.getString("URL") ?: ""
/*when file is downloaded, I change the status of the boolean to true, so it break the
loop*/
//Loop break when download file is completed, occur error, or pause.
while (!isDownloadCompleted) {
Log.e("tag**", "downloaded file $isDownloadCompleted")
}
// Indicate whether the work finished successfully with the Result
return Result.success()
}
}
val imageData = workDataOf("URL" to url, "filName" to filName)
val constraints = Constraints.Builder()
.setRequiresBatteryNotLow(false)
.setRequiredNetworkType(NetworkType.CONNECTED)
.setRequiresStorageNotLow(false)
.build()
Log.e("tag**", "createReq")
val uploadWorkRequest: WorkRequest =
OneTimeWorkRequestBuilder<UploadWorker>()
.setInputData(imageData)
.setConstraints(constraints)
.setInitialDelay(0, TimeUnit.SECONDS)
.setBackoffCriteria(
BackoffPolicy.LINEAR,
OneTimeWorkRequest.MIN_BACKOFF_MILLIS,
TimeUnit.MILLISECONDS
)
.build()
I wanted to make it work whether application is killed or in background.
Check if this helps you ->
Kotlin ->
class UploadWorker(
private val appContext: Context,
workerParams: WorkerParameters
) : CoroutineWorker(appContext, workerParams) {
private var isDownloadCompleted = false
override suspend fun doWork(): Result {
val filName = inputData.getString("filName") ?: ""
val url = inputData.getString("URL") ?: ""
/*when file is downloaded, I change the status of the boolean to true, so it break the
loop*/
//Loop break when download file is completed, occur error, or pause.
while (!isDownloadCompleted) {
Log.e("tag**", "downloaded file $isDownloadCompleted")
}
// Indicate whether the work finished successfully with the Result
return Result.success()
}
}
Instead of using OneTimeWorkRequest, Try Using PeriodicWorkRequest, Hope it works.
val imageData = workDataOf("URL" to url, "filName" to filName)
val constraints = Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.setRequiresBatteryNotLow(false)
.setRequiresStorageNotLow(false)
.build()
Log.e("tag**", "createReq")
val uploadWorkRequest: WorkRequest =
PeriodicWorkRequestBuilder<UploadWorker>(2, TimeUnit.HOURS)
.setInputData(imageData)
.setConstraints(constraints)
.setInitialDelay(0, TimeUnit.SECONDS)
.setBackoffCriteria(
BackoffPolicy.LINEAR,
OneTimeWorkRequest.MIN_BACKOFF_MILLIS,
TimeUnit.MILLISECONDS
)
.build()
Nothing says that WorkManager will work if the app is killed. If the app is killed it is dead. That's it. What WorkManager ensures is that:
the application is less likely to be killed while a Work is in progress because WorkManager raised its own Service
If the application is killed and respectively a work is stopped - WM will ensure that the Work will be resumed in a later stage and it will finish for sure at some point when the Constraints are met.
All things are good but I forgot to attached a notification with Work Manager.
class UploadWorker(
private val appContext: Context,
workerParams: WorkerParameters
) : CoroutineWorker(appContext, workerParams) {
private var isDownloadCompleted = false
override suspend fun doWork(): Result {
notification = NotificationCompat.Builder(
applicationContext,
appContext.getString(R.string.channel_name)
)
.setSmallIcon(R.mipmap.ic_launcher)
.setContentTitle(getString(R.string.app_name))
.setContentText("0MB/ $fileSize")
.setOnlyAlertOnce(true)
.setProgress(100, 0, false)
.setPriority(NotificationCompat.PRIORITY_MAX)
.setOngoing(true)
.setAutoCancel(false)
.setDefaults(NotificationCompat.DEFAULT_ALL)
val filName = inputData.getString("filName") ?: ""
val url = inputData.getString("URL") ?: ""
//attach your notification, so workmanager will be killed
setForegroundAsync(ForegroundInfo(id, notification.build()))
/*when file is downloaded, I change the status of the boolean to true, so it break the
loop*/
//Loop break when download file is completed, occur error, or pause.
while (!isDownloadCompleted) {
Log.e("tag**", "downloaded file $isDownloadCompleted")
}
// Indicate whether the work finished successfully with the Result
return Result.success()
}
}
Related
Using this library "androidx.work:work-runtime-ktx:2.5.0" to run a background thread.
But OneTimeWorkRequest is being cancelled everything i exit the screen. What i am trying to acheive is set it run on the application level can someone help please or some hints? here is my code below :
var workManager = WorkManager.getInstance(requireContext())
val data: Data.Builder = Data.Builder().apply {
putLong("inspectionId", id)
}
val task = OneTimeWorkRequest.Builder(NewSyncWorkManager::class.java).apply {
// setConstraints(constraints)
setInputData(data.build())
setInitialDelay(1,TimeUnit.SECONDS)
addTag(id.toString())
}.build()
workManager.enqueue(task)
while NewSyncWorkManager is as following:
class NewSyncWorkManager(context: Context,workerParams: WorkerParameters) :
CoroutineWorker(context, workerParams) {
override suspend fun doWork(): Result {
showNotification()
val foregroundInfo = ForegroundInfo(notificationId, notification!!)
setForeground(foregroundInfo)
if (inspectionId == 0L) {
Log.d("wah", "inspectionId to upload is missing, stopping")
return Result.failure()
}
val propertySections = sectionsRepo.getPropertyLayout(inspectionId)?.reportSections
.....
UPLOAD requests to the server
}
}
Fixed the issue by running the UPLOAD requests to the server inside a GlobalScope
So I created a simple Worker class. I would like to start this worker as onTimeWork and Periodic Work as well.
Before WorkManager I used Android Job, and inside the Job there is a dedicated method to decide whetherthe current job is periodic: params.isPeriodic
Is there any way to check this in Worker class inside doWork method?
**Worker:**
override fun doWork(): Result {
var workResult = Result.success()
val isPeriodic = false
if (isPeriodic) {
...
}
launch {
}
return workResult
}
**Schedules:**
un schedulePeriodicAsync(context: Context) {
val constraint =
Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED).build()
val newMessageWorker =
PeriodicWorkRequest.Builder(
NewMessageWorker::class.java,5,
TimeUnit.MINUTES)
.setConstraints(constraint)
.setBackoffCriteria(
BackoffPolicy.LINEAR,
OneTimeWorkRequest.MIN_BACKOFF_MILLIS,
TimeUnit.MILLISECONDS
).build()
WorkManager.getInstance(context).enqueueUniquePeriodicWork(
DailySyncWorker.TAG,
ExistingPeriodicWorkPolicy.REPLACE,newMessageWorker)
}
fun scheduleNowAsync(context: Context, workCallback: JobCallback? = null) {
jobCallback = workCallback
val constraint =
Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED).build()
val newMessageWorker =
OneTimeWorkRequest.Builder(NewMessageWorker::class.java)
.setConstraints(constraint)
.setBackoffCriteria(
BackoffPolicy.LINEAR,
OneTimeWorkRequest.MIN_BACKOFF_MILLIS,
TimeUnit.MILLISECONDS
)
.build()
WorkManager.getInstance(context).enqueueUniqueWork(
TAG,
ExistingWorkPolicy.REPLACE,newMessageWorker)
}
So I have an Android Job which handle network request.
This job can started in many ways, so it can run easily parralel, which is bad for me.
I would like to achive that, the job don't started twice, or if it started, than wait before the try catch block, until the first finishes.
So how can I to reach that, just only one object be/run at the same time.
I tried add TAG and setUpdateCurrent false, but it didn't do anything, so when I started twice the job, it rung parallel. After that I tried mutex lock, and unlock. But it did the same thing.
With mutex, I should have to create an atomic mutex, and call lock by uniq tag or uuid?
Ok, so I figured it out what is the problem with my mutex, the job create a new mutex object every time, so it never be the same, and it never will wait.
My Job:
class SendCertificatesJob #Inject constructor(
private val sendSync: SendSync,
private val sharedPreferences: SharedPreferences,
private val userLogger: UserLogger,
private val healthCheckApi: HealthCheckApi
) : Job(), CoroutineScope {
override val coroutineContext: CoroutineContext
get() = Dispatchers.IO
private val countDownLatch = CountDownLatch(1)
private val mutex = Mutex()
override fun onRunJob(params: Params): Result {
var jobResult = Result.SUCCESS
if (!CommonUtils.isApiEnabled(context))
return jobResult
val notificationHelper = NotificationHelper(context)
var nb: NotificationCompat.Builder? = null
if (!params.isPeriodic) {
nb = notificationHelper.defaultNotificationBuilder.apply {
setContentTitle(context.resources.getString(R.string.sending_certificates))
.setTicker(context.resources.getString(R.string.sending_certificates))
.setOngoing(true)
.setProgress(0, 0, true)
.setSmallIcon(android.R.drawable.stat_notify_sync)
.setLargeIcon(
BitmapFactory.decodeResource(
context.resources,
R.mipmap.ic_launcher
)
)
}
notificationHelper.notify(NOTIFICATION_ID, nb)
jobCallback?.jobStart()
}
val failureCount = params.failureCount
if (failureCount >= 3) {
nb?.setOngoing(false)
?.setContentTitle(context.resources.getString(R.string.sending_certificates_failed))
?.setTicker(context.resources.getString(R.string.sending_certificates_failed))
?.setProgress(100, 100, false)
?.setSmallIcon(android.R.drawable.stat_sys_warning)
notificationHelper.notify(NOTIFICATION_ID, nb)
return Result.FAILURE
}
GlobalScope.launch(Dispatchers.IO) {
mutex.lock()
userLogger.writeLogToFile("SendCertificatesJob.onRunJob(), date:" + Calendar.getInstance().time)
try {
//Test
var doIt = true
var count =0
while (doIt){
Timber.d("SendSyncWorker: $count")
count++
delay(10000)
if(count == 12)
doIt = false
}
healthCheckApi.checkHealth(ApiModule.API_KEY).await()
try {
sendSync.syncRecordedClients()
} catch (e: Exception) {
e.printStackTrace()
}
val result = sendSync().forEachParallel2()
result.firstOrNull { it.second != null }?.let { throw Exception(it.second) }
val sb = StringBuilder()
if (nb != null) {
nb.setOngoing(false)
.setContentTitle(context.resources.getString(R.string.sending_certificates_succeeded))
.setTicker(context.resources.getString(R.string.sending_certificates_succeeded))
.setProgress(100, 100, false)
.setStyle(NotificationCompat.BigTextStyle().bigText(sb.toString()))
.setSmallIcon(android.R.drawable.stat_notify_sync_noanim)
notificationHelper.notify(NOTIFICATION_ID, nb)
jobCallback?.jobEnd()
}
sharedPreferences.edit().putLong(KEY_LATEST_CERTIFICATES_SEND_DATE, Date().time)
.apply()
} catch (e: Exception) {
Timber.tag(TAG).e(e)
if (nb != null) {
nb.setOngoing(false)
.setContentTitle(context.resources.getString(R.string.sending_certificates_failed))
.setTicker(context.resources.getString(R.string.sending_certificates_failed))
.setProgress(100, 100, false)
.setSmallIcon(android.R.drawable.stat_sys_warning)
notificationHelper.notify(NOTIFICATION_ID, nb)
jobCallback?.jobEnd()
}
jobResult = Result.RESCHEDULE
} finally {
countDownLatch.countDown()
mutex.unlock()
}
}
countDownLatch.await()
return jobResult
}
Job schedule:
fun scheduleNowAsync(_jobCallback: JobCallback? = null) {
jobCallback = _jobCallback
JobRequest.Builder(TAG_NOW)
.setExecutionWindow(1, 1)
.setBackoffCriteria(30000, JobRequest.BackoffPolicy.LINEAR)
.setRequiredNetworkType(JobRequest.NetworkType.CONNECTED)
.setRequirementsEnforced(true)
.setUpdateCurrent(true)
.build()
.scheduleAsync()
}
fun schedulePeriodicAsync() {
jobCallback = null
JobRequest.Builder(TAG)
.setPeriodic(900000)
.setRequiredNetworkType(JobRequest.NetworkType.CONNECTED)
.setRequirementsEnforced(true)
.setUpdateCurrent(true)
.build()
.scheduleAsync()
}
I found the solution for my problem.
So because I use dagger, I provided a singleton Mutex object, and injected into the job. When the job starts call mutex.lock(), and beacuse there is only 1 object from the mutex, even if another job starts, the second job will waite until the firsjob is done.
I have setup the worker class, my intent was to fetch data from the web each 24 hours and i want to get a notification with fetched data. I used periodic work request. My app behavior is weird, notifications fire off only when i start the app. And when app is created, i receive multiple notifications with the data i want. I want only one notification each 24 hours.
Here is my worker class code
#RequiresApi(Build.VERSION_CODES.O)
class PeriodicWork(context: Context, workerParameters: WorkerParameters) :
CoroutineWorker(context, workerParameters) {
private val repository =
Repository(Database.invoke(applicationContext))
override suspend fun doWork(): Result {
try {
val notificationString = getNotificationResponse().body()!!.value
val notification =
NotificationCompat.Builder(applicationContext, NOTIFICATION_CHANNEL_ID)
.setSmallIcon(R.drawable.ic_drawable)
.setContentTitle("Notification")
.setContentText(notificationString)
.setStyle(
NotificationCompat.BigTextStyle()
.bigText(notificationString)
)
.setPriority(NotificationCompat.PRIORITY_HIGH)
.build()
createNotificationChannel()
with(NotificationManagerCompat.from(applicationContext)) {
notify(1, notification)
}
} catch (e: Exception) {
}
return Result.success()
}
private fun createNotificationChannel() {
val channel = NotificationChannel(
NOTIFICATION_CHANNEL_ID,
"New Notification",
NotificationManager.IMPORTANCE_HIGH
).apply {
description = "Notification channel"
}
val notificationManager =
applicationContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
notificationManager.createNotificationChannel(channel)
}
private suspend fun getNotificationResponse(): Response<WantedData> {
val notificationResponse =
withContext(Dispatchers.IO) { repository.getRandomString() }
repository.saveString(notificationResponse.body()!!)
return notificationResponse
}
}
MainActivity part of code where i instantiate worker in a function which i call in onCreate lifecycle method
private fun setupPeriodicRequest() {
val periodicRequest =
PeriodicWorkRequestBuilder<PeriodicWork>(24, TimeUnit.HOURS)
.build()
WorkManager.getInstance()
.enqueueUniquePeriodicWork(
"Periodic Notification",
ExistingPeriodicWorkPolicy.REPLACE,
periodicRequest
)
}
If needed i can provide more info, thanks in advance!!!
The problem is when you instantiate worker in setupPeriodicRequest function. You're passing ExistingPeriodicWorkPolicy.REPLACE in your enqueueUniquePeriodicWork function.
Instead, you should use ExistingPeriodicWorkPolicy.KEEP that will only create WorkRequest if not exists.
So, this is how your setupPeriodicRequest function should look like:
private fun setupPeriodicRequest() {
val periodicRequest = PeriodicWorkRequestBuilder<PeriodicWork>(24, TimeUnit.HOURS)
.build()
WorkManager.getInstance()
.enqueueUniquePeriodicWork(
"Periodic Notification",
ExistingPeriodicWorkPolicy.KEEP,
periodicRequest
)
}
From the code documentation about ExistingPeriodicWorkPolicy:
public enum ExistingPeriodicWorkPolicy {
/**
* If there is existing pending (uncompleted) work with the same unique
* name, cancel and delete it. Then, insert the newly-specified work.
*/
REPLACE,
/**
* If there is existing pending (uncompleted) work with the same unique
* name, do nothing.
* Otherwise, insert the newly-specified work.
*/
KEEP
}
I solved my problem with multiple network calls. The setup within the worker class was bad. When i put createNotificationChannel() function above network call the behavior i was experiencing disappeared. I would guess that app was making network calls as long as worker did not do its supposed job. In my case i had network call on first line and it would fire off network request until the notification was created.
override suspend fun doWork(): Result {
try {
createNotificationChannel()
val notificationString = getNotificationResponse().body()!!.value
val notification =
NotificationCompat.Builder(applicationContext, NOTIFICATION_CHANNEL_ID)
.setSmallIcon(R.drawable.ic_drawable)
.setContentTitle("Notification")
.setContentText(notificationString)
.setStyle(
NotificationCompat.BigTextStyle()
.bigText(notificationString)
)
.setPriority(NotificationCompat.PRIORITY_HIGH)
.build()
with(NotificationManagerCompat.from(applicationContext)) {
notify(1, notification)
}
I'm trying to cancel a task that hasn't started yet.
My Worker class code:
class TestWork(context: Context, workerParams: WorkerParameters) :
Worker(context, workerParams) {
override fun doWork(): Result {
val data = inputData.getInt(KEY_OBJ, -1)
runBlocking {
for (i in 1..3){
Log.d("MyTag", "testWork: $data")
delay(1000)
}
}
return Result.SUCCESS
}
override fun onStopped() {
super.onStopped()
Log.d("MyTag", "stopped")
}
companion object {
const val KEY_OBJ = "key"
val WORK_NAME = "name"
}
}
The code sample that running 3 works and canceling second.
But second work continues.
val data1 = Data.Builder()
.putInt(TestWork.KEY_OBJ, 1)
.build()
val workRequest1 = OneTimeWorkRequest.Builder(TestWork::class.java)
.setInputData(data1)
.addTag("1")
.build()
val data2 = Data.Builder()
.putInt(TestWork.KEY_OBJ, 2)
.build()
val workRequest2 = OneTimeWorkRequest.Builder(TestWork::class.java)
.setInputData(data2)
.addTag("2")
.build()
val data3 = Data.Builder()
.putInt(TestWork.KEY_OBJ, 3)
.build()
val workRequest3 = OneTimeWorkRequest.Builder(TestWork::class.java)
.setInputData(data3)
.addTag("3")
.build()
WorkManager.getInstance().enqueueUniqueWork(
TestWork.WORK_NAME,
ExistingWorkPolicy.APPEND,
workRequest1
)
WorkManager.getInstance().enqueueUniqueWork(
TestWork.WORK_NAME,
ExistingWorkPolicy.APPEND,
workRequest2
)
WorkManager.getInstance().enqueueUniqueWork(
TestWork.WORK_NAME,
ExistingWorkPolicy.APPEND,
workRequest3
)
WorkManager.getInstance().cancelWorkById(workRequest2.id)
How to cancel second work only? While the first is executed
You should use the isStopped() method call inside your onWork method
This is explained in the video presented at the Android Developer Summit. Around minute 15 into the presentation there's a whole section on how to stop work in WorkManager that goes through these details
As I see, you have a mistake in your cancelling line, you have to replace cancelWorkById("2") by cancelAllWorkByTag("2") because you are adding tags .addTag("2") to the Work.
I'm currently using WorkManager and I have tried to cancel works with tags and it works.