21

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)

Cœur
  • 37,241
  • 25
  • 195
  • 267
Mohamed Ibrahim
  • 3,714
  • 2
  • 22
  • 44
  • Inside the lambda, we can obviously return any of the either statuses - Success, Failure or Retry only making sure that the same line is in the last line of execution. – Debdeep Aug 08 '18 at 22:36
  • @Debdeep could you please make it more clear with some code – Mohamed Ibrahim Aug 08 '18 at 22:39
  • use lambda with open braces inside `onError` or `onSuccess` and since we know either condition works and are probably the last one in chaining, inside braces do your work and in the last line, return the respective status to `WorkManager` – Debdeep Aug 08 '18 at 22:42
  • 'return Result.RETRY' at the end is okay and should reach only on rare cases. Mostly you would want to return the status from inside. – Debdeep Aug 08 '18 at 22:45
  • BTW, obtaining a reference to the `Application` by casting the application context is not guaranteed to work. I've mostly seen it fail in emulators, but also very rarely on real devices. It's safer to set a static field in the application's `onCreate` and provide a static getter. – StackOverthrow Aug 09 '18 at 00:28
  • can you share the last result with us, I'm lost and I need some hints – Ran Oct 11 '18 at 12:10

5 Answers5

38

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() }
    }
}
Semanticer
  • 1,962
  • 1
  • 18
  • 29
6

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.

Rahul
  • 19,744
  • 1
  • 25
  • 29
1

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.

StackOverthrow
  • 1,158
  • 11
  • 23
0

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
   }
}
Edhar Khimich
  • 1,468
  • 1
  • 17
  • 20
0

You can use both Rxjava and Coroutine with Work Manager. Have a look at this Medium post. Hopefully it will help you. Thank you.

Aminul Haque Aome
  • 2,261
  • 21
  • 34