SkillAgentSearch skills...

Dispatcher

Dispatcher is a simple and flexible work scheduler that schedulers work on a background or UI thread correctly in the form of Dispatch using the android.os.Handler class.

Install / Use

/learn @tonyofrancis/Dispatcher

README

Download Build Status License

DispatchQueue: A simple work scheduler for Java, Kotlin and Android

DispatchQueue is a simple and flexible work scheduler that schedulers work on a background or main thread in the form of a dispatch queue.

DispatchQueue.background
    .async {
        //do background work here
        val sb = StringBuilder()
        for (i in 0..100) {
            sb.append(i)
              .append(" ")
        }
        sb.toString()
    }
    .post { data ->
        //do ui work here
        println(data)
    }
    .start()

DispatchQueue makes it very clear which thread your code is running on. An Async block will always run on a background thread. A post block will always run on the ui thread. Like what you see? Read on!

One of the many problems with offloading work to a background thread in Java and Android programming, is knowing the right time to cancel the work when it is no longer needed. DispatchQueue makes it very easy to cancel a dispatch queue. Simply call the cancel() method on the queue. If that is not good enough, allow your component's lifecycle to manage this for you. Android Activity Example:

class SimpleActivity: AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        DispatchQueue.io
            .managedBy(this)
            .async {
                //do work in background
            }
            .post {
                //handle results on ui
            }
            .start()
    }

}

In the above example, the queue is managed by the Activity’s life cycle, and it will be cancelled when the Activity is destroyed. What if you want to control the cancellation of the queue when the Activity pauses or stops? Sure you can! Like so:

class SimpleActivity: AppCompatActivity() {

    override fun onResume() {
        super.onResume()
        DispatchQueue.io
            .managedBy(this, CancelType.PAUSED)
            .async {
                //do work in background
            }
            .post {
                //handle results on ui
            }
            .start()
    }

}

In this example, the queue is canceled when the Activity’s onPause method is called. There is no need to store the queue in a variable and cancel in manually in a callback method. You can if you want. The choice is yours.

DispatchQueue uses a DispatchQueueController to manage when a queue is canceled. There are many variations of the DispatchQueueController : LifecycleDispatchQueueController and ActivityDispatchQueueController. You can extend any of those classes to create your own queue controllers and set them on a queue.

class SimpleActivity: AppCompatActivity() {

    private val dispatchQueueController = DispatchQueueController()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        DispatchQueue.background
            .managedBy(dispatchQueueController)
            .async {
                //do work in background
            }
            .post {
                //handle results on ui
            }
            .start()
    }

    override fun onDestroy() {
        super.onDestroy()
        dispatchQueueController.cancelAllDispatchQueues()
    }

}

Managing queues could not be easier.

Queue Types

DispatchQueue comes with many pre-exiting queues:

DispatchQueue.background

DispatchQueue.io

DispatchQueue.network

DispatchQueue.test - // Used specifically for testing

These queues are generated only when you need/access them. You can also create you own dispatch queues via the many static create methods on the DispatchQueue.Queue class. If using Kotlin, you can call DispatchQueue.create directly.

DispatchQueue.createDispatchQueue()

DispatchQueue.createDispatchQueue(ThreadType.NEW)

DispatchQueue.createIntervalDispatchQueue(delayInMillis = 1_000)

DispatchQueue.createTimerDispatchQueue(delayInMillis = 10_000)

These are just some of the queues you can create.

Network Queues with Retrofit

We all know and love the Retrofit library created by the wonderful people at Square. DispatchQueue works seamlessly with your Retrofit code! Let’s walkthrough a simple service example.

TestJsonData.kt

class TestJsonData {

    var id: Int = 0

    var nm: String = ""

    var cty: String = ""

    override fun toString(): String {
        return "TestJsonData(id=$id, nm='$nm', cty='$cty')"
    }

}

TestService.kt

interface TestService {

    @GET("/api/data?list=englishmonarchs&format=json")
    fun getSampleJson(): DispatchQueue<List<TestJsonData>>

}

SimpleActivity.kt

class SimpleActivity: AppCompatActivity() {

    private lateinit var retrofit: Retrofit
    private lateinit var service: TestService

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        val dispatchQueueCallAdapterFactory = DispatchQueueCallAdapterFactory.create()

        retrofit = Retrofit.Builder()
            .addConverterFactory(GsonConverterFactory.create())
            .addCallAdapterFactory(dispatchQueueCallAdapterFactory)
            .baseUrl("http://mysafeinfo.com")
            .build()
        service = retrofit.create(TestService::class.java)

        runTestService()
    }

    private fun runTestService() {
        service.getSampleJson()
            .managedBy(this)
            .post { data ->
                for (testJsonData in data) {
                    println(testJsonData)
                }
            }
            .start()
    }

}

See how super easy it is to integrate DispatchQueue with Retrofit? All you need is a DispatchQueueCallAdapterFactory instance, and set your Service methods to return the data wrapped in a DispatchQueue object.

Zipping Queues

There will be times when you would like to combine the results of two or more queues. Call the zip methods to do this.

class SimpleActivity: AppCompatActivity() {

    private val lifecycleDispatchQueueController = LifecycleDispatchQueueController()

    override fun onResume() {
        super.onResume()
        DispatchQueue.background
            .managedBy(lifecycleDispatchQueueController, CancelType.PAUSED)
            .async {
                mapOf(0 to "cat", 1 to "bat")
            }
            .zip(getDataDispatchQueue()) // combine two queue results
            .async { results ->
                for ((key, value) in results.first) {
                    println("$key:$value")
                }
                for (string in results.second) {
                    println(string)
                }
            }
            .start()
    }

    private fun getDataDispatchQueue(): DispatchQueue<List<String>> {
        return Dispatcher.background
            .async {
                listOf("hat", "sat")
            }
    }

    override fun onPause() {
        super.onPause()
        lifecycleDispatchQueueController.cancelAllPaused()
    }

}

Handling Errors

DispatchQueue allows you to handles errors in many ways. One way is setting an error handler for the queue by passing it to the start method.

DispatchQueue.createDispatchQueue()
    .async {
        //do work
        val number = 66
        throw Exception("silly exception")
        number
    }
    .post { number ->
        println("number is $number")
    }
    .start(DispatchQueueErrorCallback { error ->
        //handle queue error here.
        Log.e("errorTest",
            "queue with id ${error.dispatchQueue.id} throw error:", error.throwable)
    })

Note in this example the post block is never executed. It can’t because the async block was not able to provide it with the data need. So the queue calls the error handler and then cancels.

Another way to handle errors more elegantly, is to provide a doOnError block that can return a default or valid data for the preceding async or post block and allowing the execution of the following async or post blocks.

DispatchQueue.createDispatchQueue()
    .async {
        //do work
        val number = 66
        throw Exception("silly exception")
        number
    }
    .doOnError { throwable ->
        if (throwable.message == "silly exception") {
            100
        } else {
            0
        }
    }
    .post { number ->
        println("number is $number")
    }
    .start()

In the above example, the doOnError block handles the exception for the preceding async block allowing the post block to be called and the queue terminates normally. It is always good practice to provide a queue with an error handler via the start method to handle errors that were not caught in the doOnError blocks.

If an error handler is not provided for the queue, the exception will be thrown causing the application to crash. To prevent this, the library allows you to provide a global error handler that will catch all exceptions thrown when using any dispatch queue. It is best practice to handle errors locally close to the location where they originated. Set the global error handler like this:

 DispatchQueue.globalSettings.dispatchQueueErrorCallback = DispatchQueueErrorCallback { error ->
            //handle errors
}

Debugging

Figuring out where an error occurred is not always easy. This is one of the areas DispatchQueue shines. DispatchQueue allows you to set the block label for each post and async block via the `setBlockLabel(label)

Related Skills

View on GitHub
GitHub Stars21
CategoryDevelopment
Updated5mo ago
Forks4

Languages

Kotlin

Security Score

92/100

Audited on Oct 18, 2025

No findings