Robolectric Cannot invoke setValue on a background thread - android

I am testing an AsyncTask that onPostExecute calls setValue of a LiveData instance. Since I am invoking setValue from onPostExecute no issues were expected regarding the invocation being done by the UI thread.
Yet running this in a Robolectric unit test I got: java.lang.IllegalStateException: Cannot invoke setValue on a background thread
To make this unit test wait for background and foreground tasks completion I take advantage of awaitility tool in the following way:
var cf = new CompletableFuture<T>();
livedata.observe(ctrl.get(), it -> cf.complete(it));
// ... perform the AsyncTask that will update livedata in onPostExecute
await().until(() -> {
flushBackgroundThreadScheduler()
flushForegroundThreadScheduler()
cf.isDone
});
This is throwing an IllegalStateException: Cannot invoke setValue on a background thread on flushForegroundThreadScheduler() call!!!!
Why I am getting this exception? And how can I have the onPostExecute being performed like in the UI thread?
UPDATE
Logging threads it seems that both flushBackgroundThreadScheduler() and flushForegroundThreadScheduler() are executed synchronously inline. I can observe:
LiveData created on thread 763324286
Background thread 1519527121
LiveData updated on thread 1519527121
Since the lambda passed to await.until runs on another thread, then both flushBackgroundThreadScheduler() and flushForegroundThreadScheduler() are performed on that thread 1519527121.
Thus, I can solve my problem with the following workaround running in the test thread corresponding to UI Thread. Yet, I need that Thread.sleep() to succeed and I don't like it.
Thread.sleep(1000)
flushBackgroundThreadScheduler()
flushForegroundThreadScheduler()
cf.isDone

We must have the following considerations into account regarding:
Robolectric: flushForegroundThreadScheduler() is executed synchronously inline.
Awaitility: await.until(<Callable>) evaluates the until condition in a background thread.
From these two statements we observe that invoking flushForegroundThreadScheduler() from the Callable passed to await.until(<Callable>) results in the invocation of scheduled foreground tasks in a background thread, which answers the first OP question: Why I am getting this exception?
Answering the second OP question:
how can I have the onPostExecute being performed like in the UI thread?
Since Robolectric shares a single thread for both UI operations and Test code, then we must use pollInSameThread to instruct that the until condition should be evaluated on the same thread as the test case that starts Awaitility. The OP sample code should be fixed to:
await().pollInSameThread().until(() -> {
flushBackgroundThreadScheduler()
flushForegroundThreadScheduler()
cf.isDone
});

Related

Accessing View on Dispatchers.IO on coroutine does not crash the app, why ? But UI can only be accessed via MAIN Thread in Android

Here is the code I am using :
(application as TestApp).applicationScope.launch(Dispatchers.IO) {
println("Thread 2 "+Thread.currentThread().name)
binding.username.setText("text2")
}
(application as TestApp).applicationScope.launch(Dispatchers.Default) {
println("Thread 3 "+Thread.currentThread().name)
binding.username.setText("text3")
}
Here I am accessing the TextView on Dispatchers.IO and Dispatchers.Default thread from an Activity, which is not a main thread. Still the app runs properly and does not throw any exception.
The printed thread names are :
Thread 3 DefaultDispatcher-worker-4
Thread 2 DefaultDispatcher-worker-2
Why is that ?
It throws for me (using lifecycleScope in a Fragment):
CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
What's your output for those println statements, are the thread names different? What thread are you inflating binding on?
It's possible that under the hood, in your particular environment, those coroutines aren't being executed on different threads. Like when I run it, both the IO and Default dispatchers end up printing a thread name beginning with Defaultdispatcher-worker- which implies they're starting with the Default dispatcher's thread pool. And there's such a thing as an unconfined dispatcher:
The Dispatchers.Unconfined coroutine dispatcher starts a coroutine in the caller thread, but only until the first suspension point. After suspension it resumes the coroutine in the thread that is fully determined by the suspending function that was invoked.
Since your coroutines don't suspend (e.g. with delay) it would be possible that they'd hit the UI updates before execution is moved to another thread. Like it says there, Dispatchers.Unconfined is an edge-case dispatcher not recommended for use in general, but it's possible that something like that is happening behind the scenes for you, handling the dispatch more efficiently. (Not for me though!) You could try adding a delay to each before the UI touching, and see if it changes anything
(I don't know much at all about the underlying management of coroutines, I could be completely off base. I'm just pointing this out as a possible line of investigation since you're seeing weird behaviour!)

Why can kotlin coroutines operate UI elements on another thread

I'm trying kotlinx.coroutines (version: 1.2.0). Here is a simple test code block:
GlobalScope.launch {
Logger.i("${Thread.currentThread()}, ${Looper.myLooper() == Looper.getMainLooper()}")
text_view.text = "test"
}
The printed log is:
Thread[DefaultDispatcher-worker-2,5,main], false
As the log shows, we are not on the Android main thread, i.e UI thread. However, the code above won't throw an exception after we set text to text_view on this worker thread, and "test" is set to text_view correctly. What's the reason?
Update 1:
Adding delay(10000L) before setText() will cause the exception while shorter time (like 1000L in my test for a debug run with cold startup) won't. So it seems like an Android issue. But still that question, what's the reason?
Update 2:
Now I realized this behavior is related to Android instead of kotlinx.coroutines. The code above is executed in onCreate() when ViewRootImpl may not have called performTraversals() or initialize all Views. In this situation, checkThread() before UI operation isn't called, either.
The default dispatcher, that is used when coroutines are launched in GlobalScope, is represented by Dispatchers.Default and uses shared background pool of threads, so launch(Dispatchers.Default) { ... } uses the same dispatcher as GlobalScope.launch { ... }.
Hence, When launch { ... } is used without parameters, it inherits the context (and thus dispatcher) from the CoroutineScope that it is being launched from.
In this case, it inherits the context of the main thread.
So, unless we define context & dispatcher, Coroutine will work on main thread creating new worker thread from DefaultDispatcher (in our case is main again).
Nothing to do with Kotlin coroutines really.
Even though you should not be calling UI functions from non-UI threads, not every Android UI function actually checks that you're on the UI thread. TextView#setText() is one of them and you can get away with calling it from a background thread without getting an exception.
GlobalScope.launch(Dispatchers.Main) {
mTvText?.text = "text" // exemple: set text in main thread
... // write also your code here
}

Is await blocking the UI thread on android?

I have been wrapping my head around coroutines and I was wondering about the following code.
I have the following operation on my onCreate().
asyncJob = GlobalScope.launch(Dispatchers.Main) {
val name = async(Dispatchers.Default) { queryDevices() }.await()
mDeviceName.text = deviceName
}
Printing this out the order of execution seems to be before "name" is
on UI thread and after name is set, it is on the UI thread as well.
The queryDevicesMethod() is in a background thread as expected.
But I wanted to know what await() is actually doing when calling it on the UI thread?
Is it blocking theUI thread until await returns?
Coroutines will not block the thread on suspending. The Kotlin compiler generates a state machine that detaches and attaches the coroutines from the thread, see https://github.com/Kotlin/kotlinx.coroutines/blob/master/ui/coroutines-guide-ui.md.
In your case GlobalScope.launch(Dispatchers.Main) starts a new coroutine confined to the UI thread. Then async() starts a new coroutine confined to another dispatcher. The invocation of await() is a suspending function and will detach the first coroutine from the UI thread, waiting for the completion of the async-coroutine.
BTW:
You should not use async and await in one statement. That makes no sense.
What you really want is to run the queryDevices()-function from another dispatcher, but not asynchronously from the perspective of coroutines. In this case you should use withContext()

Android Instrumentation testing an AsyncTask

I am new to instrumentation testing in Android. Can someone show me how i can start an async task and wait for the result to be computed ? Many of my network calls rely on AsynTasks and i need to know how we can test it ? For activities we have a getInstrumentation().waitForMonitor , what can we do for AsyncTasks ?
The AsyncTask class has a blocking get() method that will basically wait for the task to finish before it returns. It's the synchronous version of calling execute(), which means you could think of it as the background work being executed on the main thread - hence it blocks until finished.
public final Result get ()
Waits if necessary for the computation to complete, and then retrieves
its result.

What is the Android UiThread (UI thread)

Can someone explain to me what exactly the UI thread is?
On developer.android.com it says about the runOnUiThread function
public final void runOnUiThread (Runnable action)
Since: API Level 1 Runs the specified action on the UI thread. If the
current thread is the UI thread, then the action is executed
immediately. If the current thread is not the UI thread, the action is
posted to the event queue of the UI thread.
Does the UI thread mean that this will be run everytime the activity is pushed the the background by some ui activity like incoming call or screen dimming etc.? If not, what exactly does the UI thread include ?
Thank you
The UIThread is the main thread of execution for your application. This is where most of your application code is run. All of your application components (Activities, Services, ContentProviders, BroadcastReceivers) are created in this thread, and any system calls to those components are performed in this thread.
For instance, let's say your application is a single Activity class. Then all of the lifecycle methods and most of your event handling code is run in this UIThread. These are methods like onCreate, onPause, onDestroy, onClick, etc. Additionally, this is where all of the updates to the UI are made. Anything that causes the UI to be updated or changed HAS to happen on the UI thread.
For more info on your application's Processes and Threads click here.
When you explicitly spawn a new thread to do work in the background, this code is not run on the UIThread. So what happens if this background thread needs to do something that changes the UI? This is what the runOnUiThread is for. Actually you're supposed to use a Handler (see the link below for more info on this). It provides these background threads the ability to execute code that can modify the UI. They do this by putting the UI-modifying code in a Runnable object and passing it to the runOnUiThread method.
For more info on spawning worker threads and updating the UI from them click here
I personally only use the runOnUiThread method in my Instrumentation Tests. Since the test code does not execute in the UIThread, you need to use this method to run code that modifies the UI. So, I use it to inject click and key events into my application. I can then check the state of the application to make sure the correct things happened.
For more info on testing and running code on the UIThread click here
If you execute blocking code (e.g. a Http-Request) in a separate Thread, consider using AsyncTask. Its doInBackground-Method runs on a separate Thread. AsyncTask provides you with methods onProgressUpdate and onPostExecute which are guaranteed to run on the UI thread.
If you need GUI-progress updates (e.g. via a progressbar) call publishProgress inside doInBackground. This leads to a subsequent call of onPublishProgress which is also guaranteed to run on the UI thread.
onPostExecute is automatically called after doInBackground returns.
All UI drawings etc. happen in a separate thread. Its called the UIThread. If you want to make any change to UI u must use make sure it happens in UIThread's context.
Easiest way of doing it is to make use of runOnUiThread

Categories

Resources