Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

robolectric runOnUiThread on a Thread does NOT work

Tags:

runOnUiThread() does not seem to work when being executed within a thread. Anybody knows of a workaround?

Note: I filed a ticket here - https://github.com/robolectric/robolectric/issues/2479

import android.app.Activity;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.Robolectric;
import org.robolectric.RobolectricGradleTestRunner;
import org.robolectric.annotation.Config;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;

import static org.junit.Assert.assertTrue;

@RunWith(RobolectricGradleTestRunner.class)
@Config(constants = BuildConfig.class, sdk = 21)
public class RunOnUiThreadTest {

    /**
     * Works
     * @throws Exception
     */
    @Test
    public void inside_the_main_thread() throws Exception {
        final Activity activity = Robolectric.setupActivity(Activity.class);
        final CountDownLatch latch = new CountDownLatch(1);
        final AtomicBoolean didRun = new AtomicBoolean(false);

        activity.runOnUiThread(new Runnable() {
            @Override
            public void run() {
                didRun.set(true);
                latch.countDown();
            }
        });

        latch.await(20, TimeUnit.SECONDS);
        assertTrue(didRun.get());
    }

    /**
     * Fails
     * @throws Exception
     */
    @Test
    public void inside_a_new_thread() throws Exception {
        final Activity activity = Robolectric.setupActivity(Activity.class);
        final CountDownLatch latch = new CountDownLatch(1);
        final AtomicBoolean didRun = new AtomicBoolean(false);

        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                activity.runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        didRun.set(true);
                        latch.countDown();
                    }
                });
            }
        });
        thread.start();

        latch.await(20, TimeUnit.SECONDS);
        assertTrue(didRun.get());
    }

}
like image 455
Franz See Avatar asked May 31 '16 15:05

Franz See


1 Answers

The results to the tests are likely as expected since the second test blocks the UI thread which is required to run the target Runnable.

The first test will pass because the whole method is executed synchronously. Since you are already on the UI thread, when calling activity.runOnUiThread(...) the Runnable will be executed immediately. Since the latch is then 0 latch.await(20, TimeUnit.SECONDS) returns immediately and the assert is true.

In the second example you start a new thread and call activity.runOnUiThread(...) from within it. Since you are not on the UI thread the Runnable is posted to a Handler on the UI thread to be executed in the future asynchronously. At this point you proceed to call latch.await(20, TimeUnit.SECONDS); from the UI thread which means the Runnable cannot run during this time since you are blocking the UI thread.

After 20 seconds the thread resumes but the Runnable has never had the chance to run so the assert fails since didRun() was never set to true.

Below is an updated test that allows the Runnable posted using runOnUiThread() to run.

@Test
public void inside_a_new_thread() throws Exception {
    final Activity activity = Robolectric.setupActivity(Activity.class);
    final CountDownLatch latch = new CountDownLatch(1);
    final AtomicBoolean didRun = new AtomicBoolean(false);

    Thread thread = new Thread(new Runnable() {
        @Override
        public void run() {
            activity.runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    didRun.set(true);
                    latch.countDown();
                }
            });
        }
    });
    thread.start();

    // sleep current thread to give the new thread a chance to run and post the runnable
    Thread.sleep(1000);

    // This method will cause the runnable to be executed
    Robolectric.flushForegroundThreadScheduler();

    // Should immediately return since the runnable has been executed
    latch.await(20, TimeUnit.SECONDS);
    assertTrue(didRun.get());
}
like image 103
George Mulligan Avatar answered Sep 28 '22 03:09

George Mulligan