Solution 1 :
Here you can learn how to use work manager.
Create a new project and add WorkManager dependency in app/buid.gradle file
implementation "android.arch.work:work-runtime:1.0.0"
Create a base class of Worker:-
package com.wave.workmanagerexample;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.content.Context;
import android.os.Build;
import android.support.annotation.NonNull;
import android.support.v4.app.NotificationCompat;
import androidx.work.Data;
import androidx.work.Worker;
import androidx.work.WorkerParameters;
/**
* Created on : Mar 26, 2019
* Author : AndroidWave
*/
public class NotificationWorker extends Worker {
private static final String WORK_RESULT = "work_result";
public NotificationWorker(@NonNull Context context, @NonNull WorkerParameters workerParams) {
super(context, workerParams);
}
@NonNull
@Override
public Result doWork() {
Data taskData = getInputData();
String taskDataString = taskData.getString(MainActivity.MESSAGE_STATUS);
showNotification("WorkManager", taskDataString != null ? taskDataString : "Message has been Sent");
Data outputData = new Data.Builder().putString(WORK_RESULT, "Jobs Finished").build();
return Result.success(outputData);
}
private void showNotification(String task, String desc) {
NotificationManager manager = (NotificationManager) getApplicationContext().getSystemService(Context.NOTIFICATION_SERVICE);
String channelId = "task_channel";
String channelName = "task_name";
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel channel = new
NotificationChannel(channelId, channelName, NotificationManager.IMPORTANCE_DEFAULT);
manager.createNotificationChannel(channel);
}
NotificationCompat.Builder builder = new NotificationCompat.Builder(getApplicationContext(), channelId)
.setContentTitle(task)
.setContentText(desc)
.setSmallIcon(R.mipmap.ic_launcher);
manager.notify(1, builder.build());
}
}
Create WorkRequest:-
Let’s move to MainActivity and create a WorkRequest to execute the work that we just created. Now first we will create WorkManager. This work manager will enqueue and manage our work request.
WorkManager mWorkManager = WorkManager.getInstance();
Now we will create OneTimeWorkRequest, because I want to create a task that will be executed just once.
OneTimeWorkRequest mRequest = new OneTimeWorkRequest.Builder(NotificationWorker.class).build();
Using this code we built work request, that will be executed one time only
Enqueue the request with WorkManager:-
btnSend.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mWorkManager.enqueue(mRequest);
}
});
Fetch the particular task status:-
mWorkManager.getWorkInfoByIdLiveData(mRequest.getId()).observe(this, new Observer<WorkInfo>() {
@Override
public void onChanged(@Nullable WorkInfo workInfo) {
if (workInfo != null) {
WorkInfo.State state = workInfo.getState();
tvStatus.append(state.toString() + "n");
}
}
});
Finally, MainActivity looks like this.
package com.wave.workmanagerexample;
import android.arch.lifecycle.Observer;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import androidx.work.OneTimeWorkRequest;
import androidx.work.WorkInfo;
import androidx.work.WorkManager;
public class MainActivity extends AppCompatActivity {
public static final String MESSAGE_STATUS = "message_status";
TextView tvStatus;
Button btnSend;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tvStatus = findViewById(R.id.tvStatus);
btnSend = findViewById(R.id.btnSend);
final WorkManager mWorkManager = WorkManager.getInstance();
final OneTimeWorkRequest mRequest = new OneTimeWorkRequest.Builder(NotificationWorker.class).build();
btnSend.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mWorkManager.enqueue(mRequest);
}
});
mWorkManager.getWorkInfoByIdLiveData(mRequest.getId()).observe(this, new Observer<WorkInfo>() {
@Override
public void onChanged(@Nullable WorkInfo workInfo) {
if (workInfo != null) {
WorkInfo.State state = workInfo.getState();
tvStatus.append(state.toString() + "n");
}
}
});
}
}
Problem :
I´ve been facing a issue with WorkManager when reescheduling jobs. Currently, I´ve found that at some point after launching a request with Okhttp and raising an error on AuthInterceptor it gets stuck and no other job gets launched.
This is the JobOrganizer
class that manages the first steps of the work planing. It chains the first queue of jobs. You will see more jobs that are not pasted here but the main difference is that the first chained job goes without WiFi network constraints and the others does.
object JobOrganizer {
const val WORK_INTERVAL: Long = 20
const val SCH_DATA_UPDATE_WORK_RESCHEDULE = "scheduled_data_update_work_reschedule"
const val SCH_DATA_UPDATE_WORK = "scheduled_data_update_work"
private val schDataUpdateJob: OneTimeWorkRequest
get() = OneTimeWorkRequestBuilder<SCHDataUpdateJob>()
.addTag(SCH_DATA_UPDATE_WORK)
.setConstraints(wifiConstraint)
.build()
val wifiConstraint: Constraints
get() = Constraints.Builder()
.setRequiredNetworkType(NetworkType.UNMETERED)
.setRequiresDeviceIdle(false)
.setRequiresBatteryNotLow(false)
.setRequiresCharging(false)
.setRequiresStorageNotLow(false)
.build()
fun getWorkInfos(context: Context, tag: String): LiveData<List<WorkInfo>> {
val workManager = WorkManager.getInstance(context)
return workManager.getWorkInfosByTagLiveData(tag)
}
private fun clearWorks(workManager: WorkManager) {
workManager.pruneWork()
}
private fun cancelSCHJobs(context: Context) {
val workManager = WorkManager.getInstance(context)
workManager.cancelAllWorkByTag(SCH_DATA_UPDATE_WORK )
clearWorks(workManager)
}
fun scheduleJobs(context: Context) {
cancelSCHJobs(context)
WorkManager.getInstance(context)
.beginWith(schTypesDownloadJob)
.then(schDownloadJob)
.then(schDataUpdateJob)
.then(schDataUploadJob)
.then(schCleanupJob)
.enqueue()
FirebaseAnalytics.getInstance(context).logEvent(AnalyticsEvents.Sync.SYNC_SCH_CONFIGURE_FORM_CLEANUP, Bundle())
}
}
The AuthInterceptor class
class AuthInterceptor(private val context: Context?) : Interceptor {
@Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
val originalRequest = chain.request()
if (context == null) {
return chain.proceed(originalRequest)
}
val auth = AuthRepository(context).getAuth()
if (auth.isNullOrEmpty()) {
return chain.proceed(originalRequest)
}
val version = String.format(
"%s: %s (build %s)",
BuildConfig.FLAVOR,
BuildConfig.VERSION_NAME,
BuildConfig.VERSION_CODE
)
val compressedRequest = originalRequest.newBuilder()
.header("Authorization", String.format("Bearer %s", auth[0].token))
.header("mobile-app-version", version)
.build()
return chain.proceed(compressedRequest)
}
}
The Update Job that reeschedules itself with a 30 min delay. The main try / catch is for the AuthInterceptor errors.
class SCHDataUpdateJob(var context : Context, params : WorkerParameters) : Worker(context, params) {
override fun doWork(): Result {
FirebaseAnalytics.getInstance(context).logEvent(AnalyticsEvents.Sync.SYNC_SCH_UPDATE_START, Bundle())
var success = UPDElementTypesJob(context).doWork()
if (!success) {
FirebaseAnalytics.getInstance(context).logEvent(AnalyticsEvents.Sync.SYNC_UPD_ELEMENTTYPES_ERROR, Bundle())
Log.e("SYNC", "SCHDataUpdateJob UPDElementTypesJob error")
}
FirebaseAnalytics.getInstance(context).logEvent(AnalyticsEvents.Sync.SYNC_SCH_UPDATE_FINISH, Bundle())
val dataUpdateWorkRequest = OneTimeWorkRequestBuilder<SCHDataUpdateJob>()
.setInitialDelay(JobOrganizer.WORK_INTERVAL, TimeUnit.MINUTES)
.addTag(JobOrganizer.SCH_DATA_UPDATE_WORK)
.setConstraints(JobOrganizer.wifiConstraint)
.build()
WorkManager.getInstance(applicationContext)
.enqueue(dataUpdateWorkRequest)
Log.e("SYNC", "SCHDataUpdateJob finished")
return Result.success()
}
}
This is the fragment that calls scheduleJobs().
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
super.onCreateView(inflater, container, savedInstanceState)
mainView = inflater.inflate(R.layout.fragment_draft_list, container, false)
sync = mainView!!.findViewById(R.id.sync)
sync.onClick {
mFirebaseAnalytics!!.logEvent(AnalyticsEvents.UPDATE_BUTTON_CLICKED, Bundle())
if (!ConnectionUtils.isConnectedToWifi(activity!!.applicationContext)) {
showConnectivityDialog()
} else {
sync.visibility = View.GONE
doAsync {
JobOrganizer.scheduleJobs(context!!)
}
}
}
if (forceDownload) {
JobOrganizer.scheduleJobs(context!!)
}
return mainView!!
}
At some point the this last job doens’t get scheduled or doesn’t run. Any clue?
Thanks.
Comments
Comment posted by Harry Timothy
Where and how do you call
Comment posted by reixa
scheduleJobs() gets called just after the login to start the sync proccess, inside a Fragment. You can also call scheduleJobs() by clicking on the update button of that same fragment. Both of them are declared on onCreateView.
Comment posted by Harry Timothy
I think I know the answer, but there is one last condition I need you to verify: if you set a breakpoint at
Comment posted by reixa
The first time they get called, both of them. The jobs get fired but at some point they stop being reescheduled. SCHUpdateJob gets itself reescheduled when it succeed.
Comment posted by Furkan Yurdakul
Also please make sure you are not calling