Migrate Android Job into Android Work manager - android

I have implemented Evernote Android Job in my android application. but i want to change it as WorkManager.
JobManager.create(this).addJobCreator(new MyJob());
public class MyJob implements JobCreator {
#Nullable
#Override
public Job create(#NonNull String tag) {
switch (tag) {
case SyncMasterDataJOB.TAG:
return new SyncMasterDataJOB();
}
return null;
}
}
Job Class:
public class SyncMasterDataJOB extends Job {
public static final String TAG = "job_note_sync";
#NonNull
#Override
protected Result onRunJob(#NonNull Params params) {
return Result.SUCCESS;
}
public static void schedulePeriodic() {
try{
new JobRequest.Builder(SyncMasterDataJOB.TAG)
.setPeriodic(15*1000, 5*1000)
.setUpdateCurrent(true)
.build()
.schedule();
} catch (Exception e){
e.printStackTrace();
}
}
How can i change Job into android workmanager.

WorkManager is highly configurable and will allow you to create a PeriodicWorkRequest or a OneTimeWorkRequest these are guaranteed to succeed. PeriodicWorkRequest will fire when you schedule the work, as well as when you have specified in the timer. It will execute in the background even if the app is closed or backgrounded. If you didn't want your task to execute immediately you can use a PWR(PeriodicWorkRequest) with a FlexInterval. See the docs below for more info.
WorkManager Docs
WorkManager Architecture
WorkmManager CodeLab
For example, I created two PeriodicWorkRequests that refresh services and keeps the user logged in always by renewing their token. When the user authenticates the PeriodicWorkRequest is created. In my case, I didn't need it to fire right away as they have just received and cached this information so I utilized the FlexInterval. When the app is backgrounded or closed, the workers continue to refresh services every 12 hours and refresh the token every 6. It works like a charm.
Here is an example:
Build Work:
override fun beginWork() {
val periodicWorkRequest = PeriodicWorkRequest.Builder(
MyWorker::class.java,
REPEAT_INTERVAL, TimeUnit.MINUTES, // How often work should repeat
// Flex not required.
FLEX_INTERVAL, TimeUnit.MINUTES) // Limits execution into a time window
.setConstraints(
Constraints.Builder().setRequiredNetworkType(
NetworkType.CONNECTED).build())
.addTag(MY_WORKER_TAG)
.build()
WorkManager.getInstance().enqueueUniquePeriodicWork(
MY_UNIQUE_WORK,
ExistingPeriodicWorkPolicy.KEEP,
periodicLoginRequest)
Worker:
class MyWorker(context: Context, params: WorkerParameters) : Worker(context, params) {
override fun doWork(): Result {
// DO WORK HERE
Result.success()
} else {
// HANDLE FAILURE HERE
Result.failure()
}
The above is a simple implementation, but it should give you the general idea.

Related

Check for network status update while application killed

My scenario is the following:
I'm working on a chat application and I would like to implement some type synchronization service that starts itself when device recovers network connection. Anytime device has network connection again, unsent messages are going to be automatically sent. With independence of application state (foregorund, background or killed).
Options tried:
1. Broadcast Receiver with android.net.conn.CONNECTIVITY_CHANGE
This scenario only works when the application is active (Foreground or Backround) but stops working when app is killed.
2. Foreground service
A notification is going to be shown all the time which is not ideal. Also I want to avoid draining users' battery.
3. AndroidX.Work.Worker
PeriodicWorkRequest networkCheckingPeriodicWork = PeriodicWorkRequest.Builder.
From<ConnectivityChangeWroker>(repeatInterval:30, repeatIntervalTimeUnit: Java.Util.Concurrent.TimeUnit.Minutes, flexInterval:25, flexIntervalTimeUnit:Java.Util.Concurrent.TimeUnit.Minutes)
.SetConstraints(new Constraints.Builder().SetRequiredNetworkType(AndroidX.Work.NetworkType.Connected)
.SetRequiredNetworkType(AndroidX.Work.NetworkType.Unmetered).Build()).Build();
WorkManager.Instance.EnqueueUniquePeriodicWork("", ExistingPeriodicWorkPolicy.Replace, networkCheckingPeriodicWork);
public class ConnectivityChangeWroker : AndroidX.Work.Worker
{
public ConnectivityChangeWroker(Context context, WorkerParameters workerParameters) : base(context, workerParameters)
{
}
public override Result DoWork()
{
try
{
//Start synch service
return Result.InvokeSuccess();
}
catch (Exception)
{
return Result.InvokeFailure();
}
}
}
But in this case, I'm not achieving the desired behaviour. For my undestanding, I just set a periodic work that checks for network connection, and if there is one, runs DoWork() method.
-- EDIT --
4.JobService
Java.Lang.Class javaClass = Java.Lang.Class.FromType(typeof(ConnectivityChangeJob));
ComponentName component = new ComponentName(Application.Context, javaClass);
JobInfo jobInfo = new JobInfo.Builder(1, component)
.SetRequiredNetworkType(Android.App.Job.NetworkType.Any)
.SetOverrideDeadline(5000)
.SetPersisted(true)
.Build();
JobScheduler jobScheduler = (JobScheduler)GetSystemService(JobSchedulerService);
jobScheduler.Schedule(jobInfo);
[Service(Name = "Extintores.ConnectivityChangeJob", Permission = "android.permission.BIND_JOB_SERVICE")]
public class ConnectivityChangeJob : JobService
{
private Intent startServiceIntent;
public ConnectivityChangeJob()
{
}
public override bool OnStartJob(JobParameters jobParams)
{
//Start synchService
return true;
}
public override bool OnStopJob(JobParameters jobParams)
{
return true; //Reschedule the job
}
}
But in this case, OnStartJob is only fired the first time the applicatio is opened and, apparently, never again.
Is there any way I can achieve what I'm aming for?
-- EDIT --
I want to achieve the same bahaviour as applications as WhatsApp. When it detects network connection again, automatically all unsent messages are going to be send.
I guess the AndroidX.Work.Worker is the best option.
In DoWork you should update databases and send requests.
Besides worker supports long-running workers
Example DownloadWorker:
class DownloadWorker(context: Context, parameters: WorkerParameters) :
CoroutineWorker(context, parameters) {
private val notificationManager =
context.getSystemService(Context.NOTIFICATION_SERVICE) as
NotificationManager
override suspend fun doWork(): Result {
val inputUrl = inputData.getString(KEY_INPUT_URL)
?: return Result.failure()
val outputFile = inputData.getString(KEY_OUTPUT_FILE_NAME)
?: return Result.failure()
// Mark the Worker as important
val progress = "Starting Download"
setForeground(createForegroundInfo(progress))
download(inputUrl, outputFile)
return Result.success()
}
private fun download(inputUrl: String, outputFile: String) {
// Downloads a file and updates bytes read
// Calls setForeground() periodically when it needs to update
// the ongoing Notification
}
// Creates an instance of ForegroundInfo which can be used to update the
// ongoing notification.
private fun createForegroundInfo(progress: String): ForegroundInfo {
val id = applicationContext.getString(R.string.notification_channel_id)
val title = applicationContext.getString(R.string.notification_title)
val cancel = applicationContext.getString(R.string.cancel_download)
// This PendingIntent can be used to cancel the worker
val intent = WorkManager.getInstance(applicationContext)
.createCancelPendingIntent(getId())
// Create a Notification channel if necessary
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
createChannel()
}
val notification = NotificationCompat.Builder(applicationContext, id)
.setContentTitle(title)
.setTicker(title)
.setContentText(progress)
.setSmallIcon(R.drawable.ic_work_notification)
.setOngoing(true)
// Add the cancel action to the notification which can
// be used to cancel the worker
.addAction(android.R.drawable.ic_delete, cancel, intent)
.build()
return ForegroundInfo(notificationId, notification)
}
#RequiresApi(Build.VERSION_CODES.O)
private fun createChannel() {
// Create a Notification channel
}
companion object {
const val KEY_INPUT_URL = "KEY_INPUT_URL"
const val KEY_OUTPUT_FILE_NAME = "KEY_OUTPUT_FILE_NAME"
}
}

Android PeriodicWorkRequest Issue

Workmanager is not launching the worker. I have a debug breakpoint in the worker class. I am not hitting breakpoint. I am following the example provided by WorkManager tutorial. I am not sure where I am going wrong.
I have created following worker class
public class MessageSyncWorker extends Worker {
public MessageSyncWorker(#NonNull Context context, #NonNull WorkerParameters workerParams) {
super(context, workerParams);
}
#NonNull
#Override
public Result doWork() {
Log.d(MessageSyncWorker.class.getSimpleName(), "In Message Sync Worker");
return Result.success();
}
}
I am enqueuing the work in MainActivity as below
private void CreateWorkRequest() {
Data.Builder dataBuilder = new Data.Builder();
dataBuilder.putString("URI", "http//192.168.1.103:5168/api/sync");
Constraints constraints = new Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED).build();
PeriodicWorkRequest workRequest = new PeriodicWorkRequest.Builder(MessageSyncWorker.class, 1, TimeUnit.MINUTES, 3, TimeUnit.MINUTES)
.addTag("MessageSyncWorker")
.setInputData(dataBuilder.build()).build();
WorkManager workManager = WorkManager.getInstance(getApplicationContext());
workManager.enqueue(workRequest);
}
First thing make sure your PeriodicWorkRequest is not created multiple times and you can check with WorkManager.enqueueUniquePeriodicWork method.
You can find more information on WorkManager documentation. The basic is to use WorkManager.enqueueUniquePeriodicWork(String, ExistingPeriodicWorkPolicy, PeriodicWorkRequest) instead of the WorkManager.enqueue(PeriodicWorkRequest) you’re currently using.
Also, if you use PeriodicWorkRequest with interval less than 15 min, you should return Result.retry(), not success or failure.

Work manager class having issue with executing doWork() method

First let me correct please :
Work Manager : The minimum repeat interval that can be defined is 15 minutes (same as the JobScheduler API).
If this is not correct please let me know.
I have created below class for executing periodic work request :
object WorkManagerUtils {
fun syncWorkManager() {
val myConstraints = Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.build()
val syncRequest = PeriodicWorkRequest
.Builder(MyWorker::class.java, 20000, TimeUnit.MILLISECONDS)
.setConstraints(myConstraints)
.build()
WorkManager
.getInstance()
.enqueueUniquePeriodicWork(
Constants.WORKER,
ExistingPeriodicWorkPolicy.KEEP,
syncRequest)
}
}
Below is my Worker class. Please check :
class MyWorker(val context: Context, param: WorkerParameters) : Worker(context, param) {
override fun doWork(): Result {
if (isNetworkAvailable(context)) {
callSyncApi()
} else {
WorkManagerUtils.syncWorkManager()
}
return Result.success()
}
private fun callSyncApi() {
ToastUtils.shortToast(0,"This is working")
}
}
Calling this in my Activity as below :
WorkManagerUtils.syncWorkManager()
You can notice that currently I am just displaying toast as my work. I want to check that is this working or not ?
But the toast is not displaying.
Any interval under 15 minutes will be replaced by 15 minutes.
Assuming your ToastUtils.showToast(...) works, I believe work manager chose 15 minutes of interval and the “KEEP” existing work policy prevented rescheduling and testing.
I suggest while testing change the existing work policy to “REPLACE”.
From work manager 1.0.0 source:
public final class PeriodicWorkRequest extends WorkRequest {
...
public static final long MIN_PERIODIC_INTERVAL_MILLIS = 15 * 60 * 1000L; // 15 minutes
...
}
public class WorkSpec {
...
public void setPeriodic(long intervalDuration) {
if (intervalDuration < MIN_PERIODIC_INTERVAL_MILLIS) {
...
IntervalDuration = MIN_PERIODIC_INTERVAL_MILLIS;
}
}
...
}
There is an overload of setPeriodic function which applies the same interval enforcement.

JobScheduler - How to skip first job run of periodic job?

in my app i have set a periodic job that is set to run every 30 minutes.
The first job run occurs right when I do schedule that periodic job, which is not wanted in my case.
What I want is to skip the first run so that it will run for the first time after 30+ minutes.
My two thoughts on how to approach this was to either have it not run at all for the first 30 minutes somehow (some kind of delay), or mark the first job run as done before even having the chance to start.
Unfortunately I have not found any method in JobInfo that would allow me to do any of those.
Another workaround that would fulfill my needs would be to somehow limit the jobs to only occur while app is in the background. It does not entirely solve the issue but it could serve as a workaround in my case.
Following is my current code for scheduling the periodic job:
private void scheduleJob() {
ComponentName componentName = new ComponentName(this, myRecurringTask.class);
JobInfo info = new JobInfo.Builder(JOB_ID, componentName)
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
.setPersisted(true)
.setPeriodic(1800000)
.build();
JobScheduler scheduler = (JobScheduler) getSystemService(JOB_SCHEDULER_SERVICE);
scheduler.schedule(info);
}
I hope someone has run into the same situation and can help me resolve it... Thank you!
Use WorkManager for scheduling backgound work, see introduction here.
1. Add Dependency:
implementation "androidx.work:work-runtime-ktx:2.4.0"
2. Create Worker Class:
class DataRefresher(context: Context, params: WorkerParameters) : CoroutineWorker(context, params) {
override suspend fun doWork(): Result { //will run on background thread
//your logic
return try {
//your logic
Result.success()
} catch (e: HttpException) {
Result.retry()
}
}
}
3. Create Application Class:
class DevBytesApplication : Application() {
private val backgroundScope = CoroutineScope(Dispatchers.Default) //standard background thread
override fun onCreate() { //called when app launches, same as Activity
super.onCreate()
initWork()
}
private fun initWork() {
backgroundScope.launch { //run in background, not affecting ui
setupDataRefreshingWork()
}
}
#SuppressLint("IdleBatteryChargingConstraints")
private fun setupDataRefreshingWork() {
val constraints = Constraints.Builder()
.setRequiredNetworkType(NetworkType.UNMETERED) //when using wifi
.setRequiresBatteryNotLow(true)
.setRequiresCharging(true)
.setRequiresDeviceIdle(true) //when not running heavy task
.build()
val repeatingRequest = PeriodicWorkRequestBuilder<DataRefresher>(1, TimeUnit.DAYS) //【15 minutes is minimum!!】
.setConstraints(constraints)
.setInitialDelay(30, TimeUnit.MINUTES) //【initial delay!!】
.build()
WorkManager.getInstance(this).enqueueUniquePeriodicWork(
DataRefresher::class.java.simpleName, //work name
ExistingPeriodicWorkPolicy.KEEP, //if new work comes in with same name, discard it
repeatingRequest
)
}
}
4. Setup AndroidManifest:
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.devbytestest">
<application
android:name=".DevBytesApplication" //【here, must!!!】
...
</application>
</manifest>

Synchronous or Asynchronous Rxjava inside the Worker (from WorkManager component) what's the right choice?

I'm new to the new architecture component WorkManager, I do my API calls via Retrofit and RxJava.
My use case here is to get new posts from the Backend, then show notification, and update a widget.
So the code inside doWork() method from the Worker class, may look like something like this.
#NonNull
#Override
public Result doWork() {
AppDependencies appDependencies = new AppDependencies((Application) getApplicationContext());
Repository repository = appDependencies.getRepository();
repository.getNewPosts()
.flatMap(newPosts -> repository.inserPosts(newPosts).toObservable())
.doOnError(Timber::e)
//if success - > return Result.SUCCESS,
// -> show notification
// -> update widget
// error-> return Result.Failure
.dontKnowWhatBestNextThing; //blocking or subscribing
//if we reached here then Retry
return Result.RETRY;
}
My Question is what is the right way to use a RxJava code inside the Worker Class because the doWork() method has a return value, so Do I have to make Rx code Synchronous.
if I'm using the nonblocking Rx approach, how can I return value (Success - Failure - Retry)
Since WorkManager version 1.0.0-alpha12 they added a new artifact called work-rxjava2 that includes RxWorker class exactly for this purpose. It is a special case of ListenableWorker expecting Single<Result>.
To implement it, first make sure you include correct artifacts to your build.gradle:
dependencies {
...
implementation "android.arch.work:work-runtime-ktx:$work_version"
implementation "android.arch.work:work-rxjava2:$work_version"
}
And implement your RxWorker:
class MyRxWorker(context : Context, params : WorkerParameters) : RxWorker(context, params) {
val remoteService = RemoteService()
override fun createWork(): Single<Result> {
return remoteService.getMySingleResponse()
.doOnSuccess { /* process result somehow */ }
.map { Result.success() }
.onErrorReturn { Result.failure() }
}
}
Edit: WorkManager now officially supports an RxWorker. Take a look at the answer above for more information.
doWork happens on a background thread. So it's safe to block. You should wait for the Observable to complete before you return a Result.
We are also working on making this easier with asynchronous APIs. Stay tuned.
Yes, make the Rx code synchronous. The documentation for doWork is minimal, but the description
Override this method to do your actual background processing.
implies that it's expected or at least allowed to block. And of course, you cannot know what doWork should return until the network request has been resolved.
I found the solution.
You should use RxWorker or SettableFuture for async job
This is my solution for getting current location. Working like a charm
class LocationWorker(context: Context, private val workerParams: WorkerParameters) :
ListenableWorker(context, workerParams) {
lateinit var mFuture: SettableFuture<ListenableWorker.Result>
private var fusedLocationProviderClient = FusedLocationProviderClient(context)
#SuppressLint("RestrictedApi", "MissingPermission")
override fun startWork(): ListenableFuture<Result> {
val uniqueId = workerParams.inputData.getString(UNIQUE_ID_KEY)
mFuture = SettableFuture.create()
Timber.d("mFutureStart")
fusedLocationProviderClient.lastLocation.addOnSuccessListener { location ->
Timber.d("location == $location")
if (location != null) {
mFuture.set(Result.success())
} else mFuture.set(Result.failure())
}
return mFuture
}
}
You can use both Rxjava and Coroutine with Work Manager. Have a look at this Medium post. Hopefully it will help you. Thank you.

Categories

Resources