How are Kotlin's Coroutines different from Java's Executor in Android?

#1

Hello Kotlin community !

I’m an Android developer switching from Java to Kotlin, and I am planning to use Coroutines to handle asynchronous code as it looks very promising.

Back in Java, to handle asynchronous code I was using the Executor class to execute a time-consuming piece of code in another thread, away from the UI thread. I had an AppExecutors class that I injected in my xxxRepository classes to manage a set of Executor. It looked like this :

public class AppExecutors
{
    private static class DiskIOThreadExecutor implements Executor
    {
        private final Executor mDiskIO;

        public DiskIOThreadExecutor()
        {
            mDiskIO = Executors.newSingleThreadExecutor();
        }

        @Override
        public void execute(@NonNull Runnable command)
        {
            mDiskIO.execute(command);
        }
    }

    private static class MainThreadExecutor implements Executor
    {
        private Handler mainThreadHandler = new Handler(Looper.getMainLooper());

        @Override
        public void execute(@NonNull Runnable command)
        {
            mainThreadHandler.post(command);
        }
    }

    private static volatile AppExecutors INSTANCE;

    private final DiskIOThreadExecutor diskIo;
    private final MainThreadExecutor mainThread;

    private AppExecutors()
    {
        diskIo = new DiskIOThreadExecutor();
        mainThread = new MainThreadExecutor();
    }

    public static AppExecutors getInstance()
    {
        if(INSTANCE == null)
        {
            synchronized(AppExecutors.class)
            {
                if(INSTANCE == null)
                {
                    INSTANCE = new AppExecutors();
                }
            }
        }
        return INSTANCE;
    }

    public Executor diskIo()
    {
        return diskIo;
    }

    public Executor mainThread()
    {
        return mainThread;
    }
}

Then I was able to write some code like this in my xxxRepository :

executors.diskIo().execute(() ->
        {
            try
            {
                LicensedUserOutput license = gson.fromJson(Prefs.getString(Constants.SHAREDPREF_LICENSEINFOS, ""), LicensedUserOutput.class);

                /**
                 * gson.fromJson("") returns null instead of throwing an exception as reported here :
                 * https://github.com/google/gson/issues/457
                 */
                if(license != null)
                {
                    executors.mainThread().execute(() -> callback.onUserLicenseLoaded(license));
                }
                else
                {
                    executors.mainThread().execute(() -> callback.onError());
                }
            }
            catch(JsonSyntaxException e)
            {
                e.printStackTrace();

                executors.mainThread().execute(() -> callback.onError());
            }
        });

It worked very good and Google even has something similar in their many Github Android repo examples.

So I was using callbacks. But now I am tired of the nested callbacks and I want to get rid of them. To do so, I could write in my xxxViewModel for example :

executors.diskIo().execute(() -> 
        {
            int result1 = repo.fetch();
            String result2 = repo2.fetch(result1);
            
            executors.mainThread().execute(() -> myLiveData.setValue(result2));
        });

How is that USAGE different from Kotlin’s coroutines’ usage ? From what I saw, their biggest advantage is to be able to use asynchronous code in a sequential style. But I am able to do just that with Executor, as you can see from the code sample right above.
So what am I missing here ? What would I gain to switch from Executor to Coroutines ?

Note : I get the “coroutine are lightweight” argument, but in Android we don’t have to run 10 000 simultaneous tasks. 4-5 are generally enough.

#2

I had a same question before, but my English is poor.
https://discuss.kotlinlang.org/t/12097