I am working with WorkManager Alpha 05.
I'm developing a Service that enqueues task on demand of other applications.
It has two methods:
createTask (Create a new task, given a name and a set of data, it returns and ID)
checkTaskStatus (The application asks the services given a ID, the status of the task)
The communication is done via bound services using messages. That means both client and services has the correct implementations to communicate information.
Method 1 is working fine.
I have problems with method 2.
WorkManager.getInstance().getStatusById(taskID)
.observe(LifecycleOwner, Observer {
status -> if (status !=null){
val myResult = status.state.toString()
statusString = myResult
Log.d("Task Status",myResult)
}
})
The observer is logging the status correctly, but I can't send back that message to the client. Is there a way to check the status in a sync way?
I don't really need to have the task attached to a LiveData.
Seems like SynchronousWorkManager was removed on October 11:
Removed WorkManager.synchronous() and WorkContinuation.synchronous() and all related methods. Added ListenableFuture as the return type of many methods in the API. This is a breaking API change.
How to use ListenableFuture:
You can now synchronously get and observe by using ListenableFutures. For example, WorkManager.enqueue() used to return void; it now returns a ListenableFuture. You can call ListenableFuture.addListener(Runnable, Executor) or ListenableFuture.get() to run code once the operation is complete.
More info can be found here.
The WorkManager instance has a synchronous method which returns the SynchronousWorkManager, This will give you a set of methods to perform synchronous operations. Take into account that this is meant to be used in a background thread.
Related
What i am doing
So i am developing a weather forecast application in which i am accessing the device location using fusedLocationProviderClient.lastLocation and most of us knows that the location provider return a task, so for that i am using coroutines Deferred so that whenever the task got completed it gives me the device location
please have a look upon the code for better understanding
private fun getLastDeviceLocation(): Deferred<Location?> {
return if (hasLocationPermission())
fusedLocationProviderClient.lastLocation.asDeferred()
else
throw LocationPermissionNotGrantedException()
}
What my problem is
but now i want my location to be a live location so for that i have to use coroutines flow, so that whenever my location changes i'll be able to fetch weather for the new location automatically, but because i am new to kotlin coroutines i am not sure how to achieve that and how i can be able to use flow with deferred,
can anyone please help me out or if this is not feasible do suggest me some other work aroung to achieve my goal
Thanks in Advance
If you want to convert Deferred to Flow:
val lastLocationFlow: Flow<Location> = flow {
emit(fusedLocationProviderClient.lastLocation.asDeferred().await())
}
flow is the most basic flow builder. It takes a suspendable block as a parameter, that's responsible for emitting values. Deferred.await() will suspend execution until the task returns a Location (or null). The flow will then emit the value and finish.
If you want to convert requestLocationUpdates from callback to Flow, you could use callbackFlow.
I'm trying to get my WorkerInfo with the getWorkInfoByLiveData method that exists in the WorkManager instance.
val workInfo = workManager.getWorkInfoByIdLiveData(uuid).value
The WorkInfo is always null. Also, I'm calling this method from the main thread.
The scenario of how I'm checking this method. I try to enqueue my worker when a user sends a network request and if the internet is not connected I simply register a work with the WorkManager. After some time if I try to get the WorkerInfo with the UUID, it'll always give me null.
Note: When calling getWorkInfoByLiveData the Worker is not executed at that time.
Don't I'm expecting from WorkManager to give me WorkInfo with ENQUEUED State.
Edit 1:
So, another scenario would be like this, the app on which I'm working is like a social app. Now after registering a first worker, let's say the user don't want to see the posts from a specific user so this where I need to register my second worker because the user internet is not available at this time. Now what I need to do is to cancel the previously registered worker and then create a Chain of workers with not to show post of a user to beginWith and then the fetch all posts. Now in order to cancel the worker, I check that if the previous worker is still in Enqueued State then cancel it and create a new chain or workers.
Here is the code.
fun Context.isWorkerRegistered(uuid: UUID?): Boolean {
val id = uuid ?: return false
val workerInfo = workManager.getWorkInfoByIdLiveData(id).value
return workerInfo?.state == WorkInfo.State.ENQUEUED
}
The workInfo instance is always null.
Note: Livedata won't calculate the value until an active observer is added.
getWorkInfoByIdLiveData() returns a LiveData<WorkInfo> that you need to observe to get the workInfo value:
val status = workManager.getWorkInfoByIdLiveData(uuid)
.observe(this, Observer{ workInfo ->
if (workInfo!=null){
// ...
}
}
you can take a look at the WorkManager's codelab to see how it can be used.
I am trying to set up a test for my project - to test that a progress bar is displayed when my app performs a server request.
The code under test uses an AsyncTask to perform the network call.
I have created a blocking server (MockWebServer) to catch and hold the network call - it receives request but doesn't provide a response until i call ".release()". This allows me to verify before the server response occurs.
My logic flows like this:
// Mock server will catch the next network request
BlockingServer blockingServer = createBlockingServer();
// onResume() activity performs network request and shows Progress Spinner
activityTestRule.launchActivity(null);
// onView() waits on UiController.loopUntilIdle() <- Fails here due to timeout.
onView(withId(progressBar)).check(matches(isDisplayed()));
// Tells the server to respond to the network request
blockingServer.release();
onView(withId(progressBar)).check(matches(not(isDisplayed())));
My problem is that because the Code Under Test uses AsyncTask for the server request, Espresso naturally blocks on the verify call (onView()) in order to wait for the AsyncTask to complete before verifying.
What I need is to temporarily stop Espresso idling while waiting for AsyncTask in order to perform the verify while the server is blocking the app logic flow.
(Changing the Code Under Test is not an option)
Can someone help?
So... this is the answer I've arrived at and some working out behind it:
Espresso (specifically calls to onView(), onData(), injectEvent and Actions) uses UiControllerImpl.loopMainThreadUntilIdle() to wait until all "idle-causing" signals are false. It loops over AsyncTask, CompatAsyncTask and something called dynamicIdle to all be idle.
When this method returns the main flow continues.
loopMainThreadUtilIdle() checks an IdleNotifier to check the idle state of each of those three elements. Obviously if you want to stop espresso waiting for AsyncTask the asyncIdle is of particular interest to you.
The IdleNotifier classes are fed into UiControllerImpl at it's construction - this takes place via dagger so you'll need to look at DaggerBaseLayerComponent which uses Providers to grab the construction arguments and pass them into the UiControllerProvider to construct it.
Everything in all of these classes is locked down very tightly. Method and class visibility is usually protected or package-private and final.
The only way I found was to create my own Espresso.java class (onView() and onData()) which used custom DaggerBaseLayerComponent allowing me to use either: My own Providers or My own UiController.
I found however this doesn't solve the whole problem. There is one more mechanism that needs to be coded around - When you're starting activities they use a waitForIdleSync in the Instrumentation class. Usually this is the Runner which is provided in your gradle file. I created my own AndroidJUnitRunner and provided this in gradle to allow me to return from waitForIdleSync on command.
And finally, in startActivitySync in the Instrumentation base class, it uses an array of ActivityWaiter objects to hold up your launchIntent() calls. I couldn't think of a reasonable way of avoiding this so I cheated and created this method in my Runner:
public void clearActivityWaitQueue() {
Object mSync = Whitebox.getInternalState(this, "mSync");
List mWaitingActivities = Whitebox.getInternalState(this, "mWaitingActivities");
if (mSync != null && mWaitingActivities != null) {
mWaitingActivities.clear();
synchronized (mSync) {
mSync.notifyAll();
}
}
}
It uses PowerMock to give me the convenience Whitebox methods to set internal state of Instrumentation:
// Used to give access to Whitebox
androidTestImplementation 'org.powermock:powermock-reflect:1.6.5'
And that's it! Easy right?
(Please tell me it's easier than this and how!!)
I've had a look at the codelab for WorkManager plus some examples on here, but everything in code I have seen is either related to doing work locally on the device or work uploading to the server, not downloading data and responding to the data received. In the developer guidelines it even says, "For example, an app might need to download new resources from the network from time to time," so I thought it would be perfect for this task. My question is if WorkManager can handle the following scenario and if not, what is the proper tool for handling it:
Schedule a job that runs once a day in background
The job is to do a data fetch from the REST API (and post it to a LiveData object if possible).
When the data returns, check that it is newer than local data.
Notify the user that new data is available.
My worker class looks something like this:
public class MyWorker extends Worker {
#NonNull
#Override
public WorkerResult doWork() {
lookForNewData();
return WorkerResult.SUCCESS;
}
public void lookForNewData() {
MutableLiveData<MyObject> liveData = new MutableLiveData<>();
liveData.observe(lifeCycleOwner, results -> {
notifyOnNewData(results);
})
APILayer.getInstance().fetchData(searchParams, liveData)
}
My issue is of course that the LiveData object can't observe because there is no activity or fragment that can be its LifecycleOwner. But even if I used a callback from the API to respond to the data arriving, my worker would already have posted that it was successful and it probably would not proceed with the callback, right? So I kind of know this approach is totally wrong, but I can't see any code for getting data with WorkManager
Please help with a proper solution and some example code or some links, either with WorkManager if it can handle this kind of work or something else if it is more appropriate.
Schedule a job that runs once a day in background
You can schedule a PeriodicWorkRequest for that, which should be queued with enqueueUniquePeriodicWork. This makes sure only one PeriodicWorkRequest of a particular name can be active at a time.
Constraints constraint = new Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.build();
PeriodicWorkRequest workRequest = new PeriodicWorkRequest.Builder(MyWorker.class, 1, TimeUnit.DAYS)
.setConstraints(constraint)
.build();
WorkManager workManager = WorkManager.getInstance();
workManager.enqueueUniquePeriodicWork("my_unique_worker", ExistingPeriodicWorkPolicy.KEEP, workRequest);
The job is to do a data fetch from the REST API (and post it to a LiveData object if possible).
This can by done by sending your request synchronously within doWork() of your worker. I wouldn't use LiveData within your Worker class. We come to that later. The API call would look with Retrofit for example like that:
#Override
public WorkerResult doWork() {
Call<MyData> call = APILayer.getInstance().fetchData();
Response<MyData> response = call.execute();
if (response.code() == 200) {
MyData data = response.body();
// ...
} else {
return Result.RETRY;
}
// ...
return Result.SUCCESS;
}
When the data returns, check that it is newer than local data.
You fetched your API data in a synchronous way. Fetch your local data also synchronously and do whatever you need to do to compare them.
Notify the user that new data is available.
If you schedule a task with WorkManager it is guaranteed to run, even if your app is force-quit or the device is rebooted. So your task might complete while your app is not running. If you want to notify the user in any case you can send a notification. If you want to notify the user within a certain screen you can subscribe on your tasks status. For example like this (taken from the official guide):
WorkManager.getInstance().getStatusById(compressionWork.getId())
.observe(lifecycleOwner, workStatus -> {
// Do something with the status
if (workStatus != null && workStatus.getState().isFinished()) {
// ...
}
});
There's also getStatusesForUniqueWork(String uniqueWorkName) for our example.
The official guide is also explaining how to return data from you Task with which you can call setValue() on your MutableLiveData for example.
I would propose to update your local data within your Worker, subscribe on your workers status and once it succeeds update your UI with the local data (if you are not subscribed on your local data anyways, i.e. with Room and LiveData).
Edit: In reference to point 4, reading status of periodic work requests works a little different. They are only switching between ENQUEUED and RUNNING until CANCELLED. But will never have the state SUCCEEDED or FAILED. So listening for isFinished() might not be what you are expecting.
This is initial thought. Somebody please correct me if i'm wrong.
my worker would already have posted that it was successful and it probably would not proceed with the callback, right?
we can use the callback from API reponse, to construct output Data of the worker and set it using worker.setOutputData()
Then listen to the LiveData<WorkStatus> from workManager. From this workstatus we can get outputData using, workStatus.getOutputdata(). This data can give us the API response we want.
We can pass this response to next worker in the worker chain to carry out tasks like updating local DB.
I come from an iOS background and I'm new to Android.
Is there an efficient and fast way to make the same network API call but with different parameters each time where the parameters are stored in an array. I would only want to return when all the network API calls have completed, but I don't want any of the api calls in the loop to block other api calls in the loop.
I basically want the equivalent of this Swift code. Basically the function below won't return until all network calls getData has either succeeded or failed. How would I accomplish the same thing below in Android?
func getDataForParameters(array: NSArray) {
let group = dispatch_group_create()
for (var i = 0; i < array!.count(); i++) {
let param = array![i]
dispatch_group_enter(group)
getData(param, success: {
() in
dispatch_group_leave(group)
}, failure: {
() in
dispatch_group_leave(group)
})
}
dispatch_group_wait(group, DISPATCH_TIME_FOREVER)
}
You have many ways to achieve this.
You can use Thread.join() in case you are using threads
you can use 3rd party libraries like RxJava.
you can write your own event dispatcher here is an ugly example
This answer also covers your question Callable and Future
If the network calls in the loop shouldn't block other network call then you should make the network calls asynchronously.
You can use google's volley network library to make the network calls and they execute asynchronously. Follow the below link for volley
https://developer.android.com/training/volley/index.html.
if you can implement a counter which increments on either success or failure call back you can use that variable to determine whento return back to your calling method.
Since the network calls are being made asynchronously you need to write a callback interface which should be triggered once your counter condition is met so that it will send a callback to the called method. you can find lot of examples on how use callback mechanism in Android. Callback functions are like Delegate functions in IOS.
I Hope this helps.