I want to sync Room database periodically. I am using work manager to do it. I have created a periodic work request and I want to send a table with setInputData(TABLE HERE..). I want to know if I am I doing it right. And if right, how can I send the table to work manager periodically? If wrong, please help me with a suitable solution.
Here is my code:
//schedule recurring task only once
//Fragment
if(!SessionManager.getBoolenFromPreferences(getActivity(),REFRESH_ATTENDANCE)){
attendanceViewModel.setupPeriodicRefreshWork();
SessionManager.putBoolenInPreferences(getActivity(), true, REFRESH_ATTENDANCE);
}
//view model
public void setupPeriodicRefreshWork() {
AttendanceScheduler.refreshWork();
}
//Scheduler
public static void refreshWork() {
//define constraints
Constraints myConstraints = new Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.build();
//How to Set Data Here ?
Data source = new Data.Builder()
.put(HERE)
.build();
PeriodicWorkRequest refreshWork =
new PeriodicWorkRequest.Builder(AttendanceWorker.class, 1, TimeUnit.HOURS)
.setConstraints(myConstraints)
.setInputData(source)
.build();
WorkManager.getInstance().enqueue(refreshWork);
}
Here is my worker code:
public class AttendanceWorker extends Worker {
private Executor executor;
private static final String TAG = "AttendanceWorker";
public AttendanceWorker(#NonNull Context context, #NonNull WorkerParameters workerParams) {
super(context, workerParams);
executor = Executors.newSingleThreadExecutor();
}
#SuppressLint("CheckResult")
#NonNull
#Override
public Result doWork() {
Context context = getApplicationContext();
AttendanceDao attendanceDao = DatabaseInstance.getInstance(context).attendanceDao();
Attendance attendance = attendanceDao.getAttendanceDetailsForSync();
NetworkUtils.getAPIService().saveAttendanceDetails(attendance).compose(RxUtils.applySchedulers())
.subscribe(
(AttendanceResponse attendanceResponse) -> executor.execute(() ->
{
if (attendanceResponse != null) {
if (attendanceResponse.getResult().equals("1")) {
Log.d(TAG, "Attendance Synced!");
}
}
}),
Throwable::printStackTrace
);
return Result.success();
}
}
Related
-I am developing app in Flutter and I need to run some code of line in background for specific time on everyday so i implemented work manager in native and then i pass call to flutter using method channel but the Problem is WorkManager Not Working in Background Or After Terminating Application. (I am Running Application on Android-11 and Samsung Device)
here is my code..
public class MainActivity extends FlutterActivity {
#Override
public void configureFlutterEngine(#NonNull FlutterEngine flutterEngine) {
super.configureFlutterEngine(flutterEngine);
NativeMethodChannel.configureChannel(flutterEngine);
setUpWorker();
}
private void setUpWorker() {
WorkManager workManager = WorkManager.getInstance(this);
PeriodicWorkRequest periodicSyncDataWork =
new PeriodicWorkRequest.Builder(NotificationWorker.class, 1, TimeUnit.MINUTES)
.build();
workManager.enqueue(
periodicSyncDataWork //work request
);
}
}
public class NativeMethodChannel {
private static MethodChannel methodChannel;
public static void configureChannel(FlutterEngine flutterEngine) {
String CHANNEL_NAME = "com.example.mutual_fund/android";
methodChannel = new MethodChannel(flutterEngine.getDartExecutor().getBinaryMessenger(), CHANNEL_NAME);
}
public static void sendBroadcastString(String broadcastType) {
methodChannel.invokeMethod(broadcastType, broadcastType);
}
}
public class NotificationWorker extends Worker {
Handler mainHandler = new Handler(Looper.getMainLooper());
public NotificationWorker(#NonNull Context context, #NonNull WorkerParameters workerParams) {
super(context, workerParams);
}
#NonNull
#Override
public Result doWork() {
try{
Runnable myRunnable = new Runnable() {
#Override
public void run() {
NativeMethodChannel.sendBroadcastString(Constants.internet_available_method);
}
};
mainHandler.post(myRunnable);
}catch (Exception error){
Log.d("Worker Error-----", error.getMessage());
}
return Result.success();
}
}
class FlutterMethodChannel {
static const channelName =
'com.example.mutual_fund/android'; // this channel name needs to match the one in Native method channel
late MethodChannel methodChannel;
static FlutterMethodChannel instance = FlutterMethodChannel();
void configureChannel() {
methodChannel = const MethodChannel(channelName);
methodChannel.setMethodCallHandler(methodHandler);
}
Future<void> methodHandler(MethodCall call) async {
switch (call.method) {
case "internet_available_method":
print("internet_available_method called from native android code");
Get.put(AlertController()).sendBackgroundAlertNotifications();
break;
case "no_internet_connection_method":
print("no_internet_connection_method called from native android code");
break;
default:
print('no method handler for method ${call.method}');
}
}
What I am doing :
I need to download a file of 100 mb from background
I am using work manager with retrofit to achieve this
I am launching retrofit from work manager
It is a one time download (once I download the file. I don't need the service running in background to download again)
What is Happening :
If I keep the app in the foreground I am successfully able to
download the file
But say once after launching the worker thread I close the app. I
notice file don't get download in background.
Question: How to make sure all 100 mb file is downloaded in background till completion
In activity I am launching:
final WorkManager mWorkManager = WorkManager.getInstance(context);
final OneTimeWorkRequest mRequest = new OneTimeWorkRequest.Builder(DictionaryWorker.class)
.setConstraints(new Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.setRequiresBatteryNotLow(true)
.setRequiresStorageNotLow(true)
.build())
.addTag(DICTIONARY_WORKER_TAG)
.build();
mWorkManager.enqueue(mRequest);
In the worker class:
public class DictionaryWorker extends Worker {
private final String CURRENT_SCREEN = DictionaryWorker.this.getClass().getSimpleName();
private static final String WORK_RESULT = "work_result";
private Context context;
private NetworkComponent networkComponent;
public DictionaryWorker(#NonNull Context context, #NonNull WorkerParameters workerParams) {
super(context, workerParams);
this.context = context;
}
#NonNull
#Override
public Result doWork() {
initCnxNetworkConnection(context);
Data outputData = new Data.Builder().putString(WORK_RESULT, "Jobs Finished").build();
return Result.failure(outputData);
}
private RandomUsersApi getNetworkService(Context context) {
if(networkComponent==null){
networkComponent = DaggerNetworkComponent.builder()
.contextModule(new ContextModule(context))
.networkModule(new NetworkModule())
.okHttpClientModule(new OkHttpClientModule())
.build();
}
return networkComponent.getService();
}
/********************************************************** RETROFIT *************************************************/
private void initCnxNetworkConnection(final Context context) {
try{
Call<ResponseBody> call = getNetworkService(context).downloadDictionary();
call.enqueue(new Callback<ResponseBody>() {
#Override
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
//File is downloaded here and written to storage
}
#Override
public void onFailure(Call<ResponseBody> call, Throwable t) {
Timber.e(CURRENT_SCREEN, "error");
}
});
}catch (Exception ex){
incompleteDownload();
Timber.e(CURRENT_SCREEN, "error%s", ex);
}
}
/********************************************************** RETROFIT *************************************************/
}
I am just trying for below scenario
Guaranteed execution:– The WorkManager will make sure to start the execution of background tasks under different conditions, even if we come out of the app.
I have an app that downloads data from an online database. It uses PeriodicWorkRequest and it doesn't work as expected. I use WorkManager 2.3.0.
There is my WorkManager settings
public class ReminderUtil {
public static final String ARTICLE_WORK_TAG = "article_work";
public static void scheduleReminder(Context context) {
PeriodicWorkRequest request = new PeriodicWorkRequest
.Builder(ArticleWorker.class, 120, MINUTES, 10, MINUTES)
.setInitialDelay(1, MINUTES)
.build();
WorkManager.getInstance(context).enqueueUniquePeriodicWork(ARTICLE_WORK_TAG,
ExistingPeriodicWorkPolicy.KEEP, request);
}
Worker class
public class ArticleWorker extends Worker {
private Context mContext;
public ArticleWorker(#NonNull Context context, #NonNull WorkerParameters workerParams) {
super(context, workerParams);
mContext = context;
}
#NonNull
#Override
public Result doWork() {
//downloading new article date list
List<ArticleLight> recentArticleLightList = QueryUtils.getArticlesLight(ArticleUtils.articleUrl);
//sorting by Date
Collections.sort(recentArticleLightList, (o1, o2) -> o2.getDate().compareTo(o1.getDate()));
Date newestDate = recentArticleLightList.get(0).getDate();
String articleText = recentArticleLightList.get(0).getTitle();
//compare newestDate with last saved sate
SharedPreferences prefs = mContext.getSharedPreferences("MyPref", Context.MODE_PRIVATE);
long savedDateLong = prefs.getLong("date", 0);
savedDateLong = savedDateLong - 604800000;//todo this is a notification test
Date savedDate = new Date(savedDateLong);
Log.i("myTAG", "WORKER");
if (newestDate.compareTo(savedDate) > 0) {
//we have new article!
NotificationUtil.createNotification(mContext, articleText);
Log.i("myTAG", "NOTIFICATION");
}
return Result.success();
}
}
And there is my log
2020-02-02 15:34:39.506 29571-7035/com.g84.spacenewstheguardian I/myTAG: WORKER
2020-02-02 15:34:39.521 29571-7035/com.g84.spacenewstheguardian I/myTAG: NOTIFICATION
2020-02-02 15:50:00.983 29571-31372/com.g84.spacenewstheguardian I/myTAG: WORKER
2020-02-02 15:50:00.998 29571-31372/com.g84.spacenewstheguardian I/myTAG: NOTIFICATION
2020-02-02 16:05:01.981 29571-337/com.g84.spacenewstheguardian I/myTAG: WORKER
2020-02-02 16:05:01.995 29571-337/com.g84.spacenewstheguardian I/myTAG: NOTIFICATION
2020-02-02 16:20:32.641 29571-3899/com.g84.spacenewstheguardian I/myTAG: WORKER
2020-02-02 16:20:32.710 29571-3899/com.g84.spacenewstheguardian I/myTAG: NOTIFICATION
As you can see the worker should run every 120 minutes with 10 minutes flexi time but it runs approximately every 16 minutes.
EDIT:
My goal is create a daily request at a specific time. So I tried to change the PeriodicWorkRequest to OneTimeWorkRequest.
When app is installed, it triggers the OneTimeWorkRequest with calculated initialDelay to a required hour. When the Worker is triggered, it sets new OneTimeWorkRequest (again with calculated initialDelay).
So far it works as I require, but still testing.
public class ReminderUtil {
public static final String ARTICLE_WORK_TAG = "article_work";
private static final int dayInMill = 86400000;
private static final int minDelay = 960000;//16 min - Min interval for WorkManager
public static void scheduleReminder2(Context context) {
if(!isWorkScheduled2(context)) { // check if my work is not already scheduled
scheduleWork2(context); // schedule my work
}
}
public static void scheduleWork2(Context context) {
OneTimeWorkRequest.Builder myBuilder =
new OneTimeWorkRequest.Builder(ArticleWorker.class)
.setInitialDelay(calculateDelay(), TimeUnit.MILLISECONDS)
.addTag(ARTICLE_WORK_TAG)
.setConstraints(new Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.build());
OneTimeWorkRequest myRequest = myBuilder.build();
WorkManager.getInstance(context)
.enqueue(myRequest);
}
private static boolean isWorkScheduled2(Context context) {
WorkManager instance = WorkManager.getInstance(context);
ListenableFuture<List<WorkInfo>> statuses = instance.getWorkInfosByTag(ReminderUtil.ARTICLE_WORK_TAG);
try {
boolean running = false;
List<WorkInfo> workInfoList = statuses.get();
for (WorkInfo workInfo : workInfoList) {
WorkInfo.State state = workInfo.getState();
running = state == WorkInfo.State.RUNNING | state == WorkInfo.State.ENQUEUED;
}
return running;
} catch (ExecutionException | InterruptedException e) {
e.printStackTrace();
return false;
}
}
private static long calculateDelay() {
Calendar now = Calendar.getInstance();
Calendar future = Calendar.getInstance();
// When to run the job
int hourOfTheDay = 8;
future.set(Calendar.HOUR_OF_DAY, hourOfTheDay);
future.set(Calendar.MINUTE, 0);
future.set(Calendar.SECOND, 0);
long diff = future.getTimeInMillis() - now.getTimeInMillis();
if (diff > minDelay) {
return diff;
}
else
return diff + dayInMill;
}
}
And Worker class
public class ArticleWorker extends Worker {
private Context mContext;
public ArticleWorker(#NonNull Context context, #NonNull WorkerParameters workerParams) {
super(context, workerParams);
mContext = context;
}
#NonNull
#Override
public Result doWork() {
//downloading new article date list
List<ArticleLight> recentArticleLightList = QueryUtils.getArticlesLight(ArticleUtils.articleUrl);
//sorting by Date
Collections.sort(recentArticleLightList, (o1, o2) -> o2.getDate().compareTo(o1.getDate()));
Date newestDate = recentArticleLightList.get(0).getDate();
String articleText = recentArticleLightList.get(0).getTitle();
//compare newestDate with last saved sate
SharedPreferences prefs = mContext.getSharedPreferences("MyPref", Context.MODE_PRIVATE);
long savedDateLong = prefs.getLong("date", 0);
savedDateLong = savedDateLong - 604800000;//todo this is a notification test
Date savedDate = new Date(savedDateLong);
Log.i("myTAG", "WORKER");
if (newestDate.compareTo(savedDate) > 0) {
//we have new article!
NotificationUtil.createNotification(mContext, articleText);
Log.i("myTAG", "NOTIFICATION");
}
ReminderUtil.scheduleWork2(mContext);//todo test of oneTime Work
return Result.success();
}
}
First thing make sure your PeriodicWorkRequest is not created multiple times and you can check with WorkManager.enqueueUniquePeriodicWork method.
PeriodicWorkRequest.Builder myWorkBuilder =
new PeriodicWorkRequest.Builder(ArticleWorker.class, 120, MINUTES, 10,
MINUTES);
PeriodicWorkRequest myWork = myWorkBuilder.build();
WorkManager.getInstance()
.enqueueUniquePeriodicWork("jobTag", ExistingPeriodicWorkPolicy.KEEP, myWork);
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.
I am looking for a solution for uploading data to Firebase without having the user wait for the data to upload, so that the user can use the app in offline mode.
Let's suppose that the app is about places. In this app, the user can upload an image and an object containing address, city, state, country, latitude, longitude, description, etc. Let's say a big POJO.
Firebase Realtime Database and Firebase Firestore can wait for (I do not know for how long) a stable internet connection to send the data. But Firebase Storage do not have this feature.
So I've found WorkManager. It seemed to solve the problem, but I had to serialize my POJO into small primitive variable types in order to send the POJO to Worker class.
The result I want to achieve is:
1) upload image to Firebase Storage
2) download URL of the image
3) send the POJO to Firebase Firestore with the ImageUrl in it.
QUESTIONS
1) Is WorkManager best suited for this kind of purpose?
2) How many times can an user trigger this background job without causing any issue to the app in offline mode?
3) How to propperly send the POJO to the Worker class?
Here is what I've done so far:
Get pushKey for the new place, start the background job and keep navigating through activities:
savePlaceData.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
DocumentReference docRef = DatabaseRouter.getPlaceCollectionRef().document();
String key = docRef.getId();
uploadPlaceDataInBackground(key)
Intent intent = new Intent(PlaceActivity.this, OtherActivity.class);
startActivity(intent);
}
});
Set the request for the background job:
private void uploadPlaceDataInBackground(String placeKey) {
// TESTING WORKMANAGER FOR UPLOADING IMAGES TO FIREBASE STORAGE
// Create a Constraints object that defines when the task should run
Constraints constraints = new Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.build();
// Passing data to the worker class
Data.Builder uploadBuilder = new Data.Builder();
uploadBuilder.putString("image_uri", placeImageUri.toString());
uploadBuilder.putString("image_pushkey", placeKey);
Data ImageUriInputData = uploadBuilder.build();
// ...then create a OneTimeWorkRequest that uses those constraints
OneTimeWorkRequest uploadWorkRequest = new OneTimeWorkRequest
.Builder(UploadImageWorker.class)
.setConstraints(constraints)
.setInputData(ImageUriInputData)
.build();
OneTimeWorkRequest downloadWorkRequest = new OneTimeWorkRequest
.Builder(DownloadImageUrlWorker.class)
.setConstraints(constraints)
.build();
// Converting placeObject into Map
Data.Builder uploadPlaceBuilder = new Data.Builder();
Map<String, Object> placeMap = convertPlaceObjectIntoMap();
uploadPlaceBuilder.putAll(placeMap);
Data placeInfoInputData = uploadPlaceBuilder.build();
OneTimeWorkRequest uploadPlaceWorkRequest = new OneTimeWorkRequest
.Builder(UploadPlaceWorker.class)
.setConstraints(constraints)
.setInputData(placeInfoInputData)
.build();
// Execute and Manage the background service
WorkManager workManager = WorkManager.getInstance(getActivity());
workManager.beginWith(uploadWorkRequest)
.then(downloadWorkRequest)
.then(uploadPlaceWorkRequest)
.enqueue();
}
Below are the Worker classes:
public class UploadImageWorker extends Worker {
public UploadImageWorker(#NonNull Context context, #NonNull WorkerParameters workerParams) {
super(context, workerParams);
}
#NonNull
#Override
public Result doWork() {
String imageUriInput = getInputData().getString("image_uri");
String imagePushKey = getInputData().getString("image_pushkey");
final Result[] result = {Result.retry()};
CountDownLatch countDownLatch = new CountDownLatch(1);
StorageReference storageRef = DatabaseRouter.getPlaceStorageRef(imagePushKey).child(imagePushKey+".jpg");
storageRef.putFile(Uri.parse(imageUriInput)).addOnCompleteListener(new OnCompleteListener<UploadTask.TaskSnapshot>() {
#Override
public void onComplete(#NonNull Task<UploadTask.TaskSnapshot> task) {
if (task.isSuccessful()) {
result[0] = Result.success(getInputData());
} else {
Log.i(TAG, "onComplete: image NOT uploaded - RETRYING");
result[0] = Result.retry();
}
countDownLatch.countDown();
}
});
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
return result[0];
}
}
public class DownloadImageUrlWorker extends Worker {
public DownloadImageUrlWorker(#NonNull Context context, #NonNull WorkerParameters workerParams) {
super(context, workerParams);
}
#NonNull
#Override
public Result doWork() {
String imageUriInput = getInputData().getString("image_uri");
String imagePushKey = getInputData().getString("image_pushkey");
final Result[] result = {Result.retry()};
CountDownLatch countDownLatch = new CountDownLatch(1);
StorageReference storageRef = DatabaseRouter.getPlaceStorageRef(imagePushKey).child(imagePushKey+".jpg");
storageRef.getDownloadUrl().addOnSuccessListener(new OnSuccessListener<Uri>() {
#Override
public void onSuccess(Uri uri) {
String imageUrl = uri.toString();
Data.Builder outputBuilder = new Data.Builder();
outputBuilder.putString("image_url", imageUrl);
outputBuilder.putString("image_pushkey", imagePushKey);
Data outputData = outputBuilder.build();
result[0] = Result.success(outputData);
countDownLatch.countDown();
}
}).addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
Log.i(TAG, "onFailure: imageUrl NOT downloaded - RETRYING");
result[0] = Result.retry();
countDownLatch.countDown();
}
});
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
return result[0];
}
}
public class UploadPlaceWorker extends Worker {
public UploadPlaceWorker(#NonNull Context context, #NonNull WorkerParameters workerParams) {
super(context, workerParams);
}
#NonNull
#Override
public Result doWork() {
String imageUrl = getInputData().getString("image_url");
String imagePushKey = getInputData().getString("image_pushkey");
Map<String, Object> placeObject = getInputData().getKeyValueMap();
PlaceModel placeModel = convertMapIntoPlaceObject(placeObject, imageUrl, imagePushKey);
final Result[] result = {Result.retry()};
CountDownLatch countDownLatch = new CountDownLatch(1);
DocumentReference docRef = DatabaseRouter.getPlaceCollectionRef().document(imagePushKey);
docRef.set(placeModel).addOnCompleteListener(new OnCompleteListener<Void>() {
#Override
public void onComplete(#NonNull Task<Void> task) {
if (task.isSuccessful()) {
result[0] = Result.success();
} else {
Log.i(TAG, "onComplete: place NOT uploaded - RETRYING");
result[0] = Result.retry();
}
countDownLatch.countDown();
}
});
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
return result[0];
}
private PlaceModel convertMapIntoPlaceObject(Map<String, Object> placeMap, String imageUrl, String placeKey) {
PlaceModel place = new PlaceModel();
place.setAddress(placeMap.get("a").toString());
place.setCity(placeMap.get("b").toString());
place.setCountry(placeMap.get("c").toString());
place.setDistrict(placeMap.get("d").toString());
place.setG(placeMap.get("e").toString());
place.setId(placeMap.get("f").toString());
place.setImage(imageUrl);
place.setKey(placeKey);
GeoPoint geoPoint = new GeoPoint((Double) placeMap.get("h"), (Double) placeMap.get("i"));
place.setL(geoPoint);
place.setDescription(placeMap.get("j").toString());
return place;
}
}
I appreciate any help!
In my chat app, I want to read messages from the local database and send them to the server by the work manager and retrofit when the user sends new messages.
in parent :
OneTimeWorkRequest workRequest = new OneTimeWorkRequest
.Builder(SendMsg_Work.class)
.setConstraints(new Constraints
.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.build())
.setInitialDelay(5, TimeUnit.SECONDS)
.setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 30, TimeUnit.SECONDS)
.build();
WorkManager.getInstance().enqueue(workRequest);
in worker :
#NonNull
#Override
public Result doWork() {
ChatRoom_Model_Local ll = database.get_unSendMsg();
if (ll != null) {
sendMessage(ll);
} else {
return Result.failure();
}
}
and retrofit - i use RxJava too :
private void sendMessage(ChatRoom_Model_Local model) {
Single<Response<MSG_Response>> api = ApiClient.createService(ApiInterface.class, pr.getData(pr.SendMessage_url))
.sendMSG(pr.getData(pr.MY_TOKEN), model.getOther_user(), model.getMsg_text());
api.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new SingleObserver<Response<MSG_Response>>() {
#Override
public void onSubscribe(Disposable d) { }
#Override
public void onSuccess(Response<MSG_Response> response) {
if (response.isSuccessful() && response.body() != null) {
String message = response.body().getMessage();
Intent bi = new Intent(new Config().getCHAT_SERVICE());
if (message != null && message.equals("xyx")) { // INSERT Successfully
String server_id = response.body().getId();
String localId = response.body().getIdLocal();
bi.putExtra("ChatRoomService_id", localId);
bi.putExtra("ChatRoomService_s_id",server_id);
int result = database.setMsgServerId(localId,server_id);
}
LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(bi);
}
ChatRoom_Model_Local ll = database.get_unSendMsg();
if (ll != null) {
sendMessage(ll);
}
}
#Override
public void onError(Throwable e) {
}
});
}
Now the problem is:
How to return some Result in doWork method while retrofit is still in progress?
What if the worker has already been called and the retrofit is in progress and the worker is called again?
How can I send messages one after another to the server?
Thank you