What is most efficient way schedule work using WorkManager for a system level event- using addContentUriTrigger in Constraints or BroadcastReceiver? - android

I am having a use case where I need to register an incoming call in through my app and later use that data for something. One thing that is clear to me is that we need a WorkManager to run in the background to perform this task, however, the confusion lies in the implementation of this. The logical approach is that the WorkManager's doWork() should be triggered when the call event occurs.
Now, there can be two different implementations for this-
Using a BroadcastReceiver (to register the call event) and then enqueue the workRequest in the onReceive() of the BroadcastReceiver
Adding the Constraints in the workRequest in the following way:
//some code here
val constraints = Constraints.Builder()
.addContentUriTrigger(CallLog.Calls.CONTENT_URI, true)
.setRequiredNetworkType(NetworkType.CONNECTED)
.build()
val workRequest = OneTimeWorkRequestBuilder<JobWorker>().apply {
setConstraints(constraints)
setInitialDelay(Duration.ofSeconds(30))
}.build()
WorkManager.getInstance(applicationContext)
.enqueueUniqueWork(
"CALL_TRACKER",
ExistingWorkPolicy.REPLACE,
workRequest
//some code here
Will this implementation of using the addContentUriTrigger trigger start the WorkManager immediately just as the BroadcastReceiver will do or will it add some delay and start the WorkManager when the resources are available to the android system resources?
Moreover, is using the BroadcastReceiver the best solution to this, please let me know if a better solution exists. Can the Android team remove the permission to read Call information in future due to which this method might not work?
I need the details of all incoming calls for later processing.
Thanks for the help!
Links I have gone through:
addContentUriTrigger
addTriggerContentUri

Will this implementation of using the addContentUriTrigger trigger
start the WorkManager immediately just as the BroadcastReceiver will
do or will it add some delay and start the WorkManager when the
resources are available to the android system resources?
Most likely yes.
Looks like expedited requests might provide a way for you to prioritize the requests: https://developer.android.com/guide/background/persistent/getting-started/define-work#expedited, but even so, there's no guarantee about always fulfilling it. So you have to take into account that depending on resource availability some tasks might get deferred.
Moreover, is using the BroadcastReceiver the best solution to this,
please let me know if a better solution exists.
That depends how critical to you is the response speed to a phone call. I see in your sample you are setting a delay of 30 seconds, so maybe triggering the workmanager instantly it's not crucial?
Can the Android team remove the permission to read Call information in
future due to which this method might not work?
Yes, they can.

Related

How long does WorkManager persist failed jobs?

I have a scenario where I want to retry recently failed jobs but workmanager returns all failed jobs. Is there way to remove old jobs?
For OneTimeWorkRequest, FAILURE it's a final status. It's not going to be re-executed. For PeriodicWorkRequest it is different. Check this blogs:
Introducing WorkManager
WorkManager Periodicity
Also this video covers this.
Back to your question:
Is there a way to remove old jobs?
Yes!
Use WorkManager#pruneWork() to remove jobs in a final state (SUCCEEDED, FAILED and CANCELLED). As you can see from the docs, this method has to be used with caution.
Keep in mind that this removes all jobs that is in the failed state. A way to remove old jobs, is to reduce the amount of time that WorkRequests are keeped in WorkManager's DB setting a custom retention value when you build them. For this use WorkRequest#keepResultsForAtLeast(long duration, TimeUnit timeUnit)
Definitely there is a way to cancel works from WorkManager.
To cancel works from WorkManager you just need to save the UUID of Works.
And you will be able to cancel any work by this piece of code -
WorkManager.getInstance(context).cancelWorkById(UUID); [Deprecated]
Edit:
You can cancel like below - Taken from - https://developer.android.com/topic/libraries/architecture/workmanager/how-to/managing-work#cancelling
// by id
workManager.cancelWorkById(syncWorker.id);
// by name
workManager.cancelUniqueWork("sync");
// by tag
workManager.cancelAllWorkByTag("syncTag");
Under the hood, WorkManager checks the State of the work. If the work is already finished, nothing happens. Otherwise, the work's state is changed to CANCELLED and the work will not run in the future. Any WorkRequest jobs that are dependent on this work will also be CANCELLED.
Currently RUNNING work receives a call to ListenableWorker.onStopped(). Override this method to handle any potential cleanup. See stop a running worker for more information.

Android PeriodicWorkRequestBuilder how to avoid double scheduling?

I'm using the new WorkManager, and was wondering, when I schedule a Periodic work, how can I avoid re-schedule it, is it automatically handled by OS?
If you need to check already running work manager just because you don't want duplicate works. You can simply use enqueueUniquePeriodicWork()
This method allows you to enqueue a uniquely-named
PeriodicWorkRequest, where only one PeriodicWorkRequest of a
particular name can be active at a time. For example, you may only
want one sync operation to be active. If there is one pending, you can
choose to let it run or replace it with your new work.
So you don't need to worry about duplicacy about works.
workmanager.enqueueUniquePeriodicWork(TAG, ExistingPeriodicWorkPolicy.KEEP , photoCheckWork);
Where TAG is unique name by which work manager will check duplicacy.
You can choose between ExistingPeriodicWorkPolicy.KEEP and ExistingPeriodicWorkPolicy.REPLACE.
It's a periodic Worker, so it handles rescheduling the next occurrence automatically.
To avoid that it is re-scheduled, you can return Result.failed() from your Worker or you can cancel the WorkRequest from your app. In this case you should handle cancellation inside your Worker as explained in the documentation.

Best approaches to refresh data periodically in Android

I have two cases in which I need to update data from webserver using API in background. To obtain data on request I use okHttp and Kotlin courtines. And now I am wondering which approaches are the best, when:
I have listview with data from webserver, and I want to update it, let's say every 10 secs, when application is in that certain view. (I was thinking about using for ex. handler with runnable)
I want to display notification when some data will change in some certain way. In that case I think that I should use background service?
With regards,
You might be looking for services: https://developer.android.com/reference/android/app/Service
You can think of these like background processes (they're a bit different, though) These run in the background and interact with other Android components like notification manager (https://developer.android.com/reference/android/app/NotificationManager).
So, essentially, you'll want to create a service to check the API every minute or so, then add a notification to the manager.
IMHO the best approach is to build for offline: store your result in the persistent store (i.e. Room). Subscribe on the query result for your feature. Then you can use the new JetPack's WorkManager for the regular updates ( https://developer.android.com/topic/libraries/architecture/workmanager/ ). Here is the slightly modified code snippet from the documentation:
val dataCheckBuilder =
PeriodicWorkRequestBuilder<DataCheckWorker>(10, TimeUnit.SECONDS)
// ...if you want, you can apply constraints to the builder here...
// Create the actual work object:
val dataCheckWork = dataCheckBuilder.build()
// Then enqueue the recurring task:
WorkManager.getInstance().enqueue(dataCheckWork)

How to implement PeriodicWorkRequest chain?

I study Android WorkManager, and fond one problem.
I have 2 Works, first of them fetch some data from server and second preload resources (depends on result of first work). I need doing this chains one time per hour.
I need something like:
workManager.beginWith(work1).then(work2)
But in WorkManger API I found chain only for OneTimeWorkRequest.
You cannot chain PeriodicWorkRequests. For your use-case you might consider using a OneTimeWorkRequest with a Worker that enqueues a copy of itself at the end of doWork() with an initial delay (to simulate periodicity).
That way you can still do chaining. I would tag all work requests consistently so you can getWorkInfosByTagLiveData() correctly.
Promoting Andrew's comment into an answer:
Google's had an open ticket to add this functionality
(see here).
Google's official solution there was to create a periodic work request with an "chain initiation" worker, in that worker's doWork() define and enqueue a chain of one-time work requests.
I was able do to it through creating a simple PeriodicWorkRequest with its doWork() implements the following
override suspend fun doWork(): Result {
...
WorkManager.getInstance(context).beginWith(oneTimeWork1).then(oneTimeWork2).enqueue()
Result.success()
}
Not ideal but this way it triggers the chain at its periodic interval

Migrating from JobScheduler to WorkManager seems like a massive undertaking. Any advice+

I've used JobScheduler for a while now. A typical example would be:
JobScheduler triggers the onStartJob() method. This kicks off some sort of task that might include several other background processes (getting device position, making a network call, etc). I then use an interface to call back to the JobService once the task is complete or if it fails.
However, with WorkManager it seems like there is basically no way to run work asynchronously. I know WorkManager will run Workers in a separate thread but it seems like a single worker must run synchronously? If so I'll have to rework a lot of the logic in my app it seems unless I'm missing something.
Say I had a weather app and it would use the JobScheduler to run a JobService that did the following:
onStartJob() --> get device position --> make API request for local weather data -> write to database -> call jobFinished()
With WorkManager, is the intended implementation of something like this to chain multiple workers together? And if so, how do you pass data from one Worker to another? Again, seems like it would be a massive undertaking for any slightly complex app to migrate to the WorkManager API.
I think what you are looking for is a ListenableWorker. It has exactly that interface. Rather than you having to call jobFinished() in the end, we do the work for you when you resolve the ListenableFuture.
https://developer.android.com/reference/androidx/work/ListenableWorker.html#startWork() is analogous to onStartJob in your case.

Categories

Resources