How to handle asynchronous operation in Workmanager Android? - android

I am using workmanager to upload image file to server and want to pass uploaded imagepath to next worker but Result.success(output) gets called before upload function complete.
class UploadImageWorker(context: Context, workerParameters: WorkerParameters) : RxWorker(context, workerParameters) {
override fun createWork(): Single<Result> {
return Single.fromCallable {
//UploadImageFile()
//updating output
}.map{
Result.success(output)
}
}
}

Success, in this case, is only reporting that the job fired successfully, it is not a measure of the work itself.
You should just use the worker to hand off the work and do something like:
MyFactory.ImageFactory.UploadImageFile(file)
Then inside your ImageFactory use Observer pattern or something similar to subscribe to the state of your UploadImageFile. This would be the true measure of success.

You should use RxWorker. You can return a Single<Result>. Read the documentation for RxWorker for more information.
https://developer.android.com/reference/androidx/work/RxWorker

Related

Android WorkManager - Can I pass input data to a Worker that runs periodically?

In my app I start a WebSocketWorker tasks that runs periodically every 15 minutes. As the name implies, it contains a WebSocket for listening to a socket in the background:
// MainApplication.kt
override fun onCreate() {
super.onCreate()
if (BuildConfig.DEBUG) {
Timber.plant(DebugTree())
}
val work = PeriodicWorkRequestBuilder<WebSocketWorker>(15, TimeUnit.MINUTES).build()
workManager.enqueueUniquePeriodicWork("UniqueWebSocketWorker", ExistingPeriodicWorkPolicy.KEEP, work)
}
The WebSocketWorker contains the following logic:
#HiltWorker
class WebSocketWorker #AssistedInject constructor(
#Assisted appContext: Context,
#Assisted workerParams: WorkerParameters
) : CoroutineWorker(appContext, workerParams) {
inner class MyWebSocketListener : WebSocketListener() {
override fun onMessage(webSocket: WebSocket, text: String) {
Timber.d("The message sent is %s", text)
// do sth. with the message
}
override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) {
t.localizedMessage?.let { Timber.e("onFailure: %s", it) }
response?.message?.let { Timber.e("onFailure: %s", it) }
}
}
override suspend fun doWork(): Result {
try{
// code to be executed
val request = Request.Builder().url("ws://***.***.**.***:8000/ws/chat/lobby/").build()
val myWebSocketListener = MyWebSocketListener()
val client = OkHttpClient()
client.newWebSocket(request, myWebSocketListener)
return Result.success()
}
catch (throwable:Throwable){
Timber.e("There is a failure")
Timber.e("throwable.localizedMessage: %s", throwable.localizedMessage)
// clean up and log
return Result.failure()
}
}
}
As you can see, in the Worker class I set the WebSocket and everything is fine. Listening to the socket works.
Now, I also want to add the "sending of messages" functionality to my app. How can I reuse the websocket created in WebSocketWorker? Can I pass input data to the WebSocketWorker that runs in the background ?
Let's say I have a EditText for typing the message and a Button to send the message with a setOnClickListener attached like this:
binding.sendButton.setOnClickListener {
// get message
val message = binding.chatMessageEditText.text.toString()
// check if not empty
if(message.isNotEmpty()) {
// HOW CAN I REUSE THE WEBSOCKET RUNNING PERIODICALLY IN THE BACKGROUND?
// CAN I PASS THE MESSAGE TO THAT WEBSOCKET ?
// OR SHOULD I CREATE A DIFFERENT WORKER FOR SENDING MESSAGES (e.g.: a OneTimeRequest<SendMessageWorker> for sending messages ?
}
}
From the documentation, I know that you need to build Data objects for passing inputs and so on but there was no example which showcased how to pass input to a worker running periodically in the background.
My experience is saying that you can. Basically you "can't" interact with the worker object via the API. It is really annoying.
For example, with the JS you have the option to get a job and check the parameters of the job. There is no such option with the work. For example, I want to check what is the current state of the restrictions - what is satisfied, what is not. Nothing like this. You can just check states, cancel and that is almost all.
My suggestions is that it is because the WorkManager is a "facade/adapter" over other libraries like JS. It has it's own DB to restore JS jobs on device restart and stuff like this, but beside that if you want to interact with the internals I guess it was just too complicated for them to do so they just skipped.
You can just inject some other object and every time the work can ask it for it's data. I don't see other option.

Android Kotlin future notification with worker with method from main activity

I want to schedule my MainActivty to perform checks on its data periodically without showing UI to the user. When a condition is true, I want to show a notification.
Furthermore, I need to check for this notification only once per day, without an exact timing and I want this check to remain scheduled even if the device is rebooted /after poweron.
Looks like the worker model with TimeUnit.ONE_DAY is the best fit for me (Here I am setting the interval at one minute so I don't need to wait for one day to test).
I create in "onCreate" method the Periodic Request Builder and the enqueue the request in the work manager.
Outside onCreate method I define the sendNotification method and provide my class implementatio on Notification Worker, where I define the action to be performed when the woker callback is fired.
In main class I have:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
[...]
val CheckRequest = PeriodicWorkRequestBuilder<NotificationWorker>(1, TimeUnit.MINUTES).build()
WorkManager.getInstance(context).enqueue(CheckRequest)
}
fun sendNotification(){
with(NotificationManagerCompat.from(this)) {
notify(notificationId, builder.build())
}
class NotificationWorker(appContext: Context, workerParams: WorkerParameters):
Worker(appContext, workerParams) {
override fun doWork(): Result {
// Do the work here--in this case, upload the images.
MainActivity().sendNotification()
// Indicate whether the work finished successfully with the Result
return Result.success()
}
}
}
My Issue is that I encounter the following error
java.util.concurrent.ExecutionException: java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
I think that what's happening is that I am calling a method from the super class of the worker when the UI is not active, so the app context etc is not availlable to the called method.
Any hin on this point would be gladly appreciated.
Kind regards,

Send back progress for Android WorkManager

I'm using Android WorkManager version 1.0.0-beta-01 to upload some files to the server. Now I want to send back upload progress as it goes on (and not just the final result) to the caller.
class UploadWorker #Inject constructor(context: Application,
parameters: WorkerParameters,
private val uploadService: UploadService)
: RxWorker(context, parameters) {
#SuppressLint("CheckResult")
override fun createWork(): Single<Result> {
val filePath = inputData.getString(FILE_PATH)
//logic for sending files goes here where I want to send back the progress
return Single.just(Result.success())
}
}
I can achieve this by having a singleton object somewhere in my app with LiveData in it, then update the LiveData from my Work and listen to it in the caller but it doesn't seem like a good solution!
Is there any better way to achieve this?

How to dispose Rx in WorkManager?

I implemented an AlarmManager to send notifications when user adds a due date to a Task. However, when the user turns off the device, all the alarms are lost. Now I'm updating the BroadcastReceiver to receive an android.intent.action.BOOT_COMPLETED and reschedule all the alarms set to each task.
My first attempt was to get an Rx Single with all the tasks where the due date is higher than the current time inside the BroadcastReceiver, then reschedule all the alarms. The issue is I'm not able to dispose the Observable once the BroadcastReceiver has no lifecycle. Also, it seems that this is not a good approach.
During my researches, the IntentService was a good solution for this case, but I'm getting into the new WorkManager library and the OneTimeWorkRequest looks like a good and simple solution.
The Worker is being called and executing correctly, but I'm not able to dispose the Observable because the onStopped method is never called.
Here is the implementation, based on this snippet:
class TaskAlarmWorker(context: Context, params: WorkerParameters) :
Worker(context, params), KoinComponent {
private val daoRepository: DaoRepository by inject()
private val compositeDisposable = CompositeDisposable()
override fun doWork(): Result {
Timber.d("doWork")
val result = LinkedBlockingQueue<Result>()
val disposable =
daoRepository.getTaskDao().getAllTasks().applySchedulers().subscribe(
{ result.put(Result.SUCCESS) },
{ result.put(Result.FAILURE) }
)
compositeDisposable.add(disposable)
return try {
result.take()
} catch (e: InterruptedException) {
Result.RETRY
}
}
override fun onStopped(cancelled: Boolean) {
Timber.d("onStopped")
compositeDisposable.clear()
}
}
Is WorkManager a good solution for this case?
Is it possible to dispose the Observable correctly?
Yes WorkManager is a good solution(even could be the best one)
You should use RxWorker instead of Worker. here is an example:
To implement it. add androidx.work:work-rxjava2:$work_version to your build.gradle file as dependency.
Extend your class from RxWorker class, then override createWork() function.
class TaskAlarmWorker(context: Context, params: WorkerParameters) :
RxWorker(context, params), KoinComponent {
private val daoRepository: DaoRepository by inject()
override fun createWork(): Single<Result> {
Timber.d("doRxWork")
return daoRepository.getTaskDao().getAllTasks()
.doOnSuccess { /* process result somehow */ }
.map { Result.success() }
.onErrorReturn { Result.failure() }
}
}
Important notes about RxWorker:
The createWork() method is called on the main thread but returned single is subscribed on the background thread.
You don’t need to worry about disposing the Observer since RxWorker will dispose it automatically when the work stops.
Both returning Single with the value Result.failure() and single with an error will cause the worker to enter the failed state.
You can override onStopped function to do more.
Read more :
How to use WorkManager with RxJava
Stackoverflow answer
You can clear it in onStoped() method then compositeDisposable.dispose();
Then call super.onStoped()

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