Search code examples
kotlingenericskotlin-coroutinesbuilder-pattern

Builder pattern with infix for Coroutines


I am trying to write a class to easily chain code run in different coroutine contexts.

Ideally, I would like to use it like this:

        io {
            // Run IO code that returns an object (nullable)
        } ui { ioResult->
            // Run UI code using the returned object (non-nullable)
        } ifNull {
            // Run UI code when the returned object is null
        }

What I have so far works like this:

        GlobalScope.launch {
            CoroutineLinker(null).io {
                // Run IO code
            } ui { ioResult ->
                ioResult?.also {
                    // Run UI code after null check
                } ?: run {
                    // Run UI code when null
                }
            } ifNull {
                // Redundant block
            }
        }

As you can see there is still quite some work left but I am stuck, so I share this with you:

    class CoroutineLinker<T> (
        private val value: T?
    ) {

        suspend infix fun <K> io (block: suspend () -> K?): CoroutineLinker<K?> {
            return withContext(Dispatchers.IO) {
                CoroutineLinker(block())
            }
        }

        suspend infix fun ui (block: suspend (value: T) -> Unit): CoroutineLinker<T> {
            return withContext(Dispatchers.Main) {

                if (value != null ) {
                    block(value)
                }

                this@CoroutineLinker
            }
        }

        suspend infix fun ifNull (block: suspend () -> Unit) {
            return withContext(Dispatchers.Main) {
                if (value == null) {
                    block()
                }
            }
        }
    }

Any input is welcome! :)


Solution

  • I think this will do what you need:

    suspend fun <K : Any> io (block: suspend () -> K?) = CoroutineLinker(null).io(block)
    
    class CoroutineLinker<T : Any> (
        private val value: T?
    ) {
    
        suspend infix fun <K : Any> io (block: suspend () -> K?): CoroutineLinker<K> {
            return withContext(Dispatchers.IO) {
                CoroutineLinker(block())
            }
        }
    
        suspend infix fun ui (block: suspend (value: T) -> Unit): CoroutineLinker<T> {
            if (value != null ) {
                withContext(Dispatchers.Main) {
                    block(value)
                }
            }
    
            return this
        }
    
        suspend infix fun ifNull (block: suspend () -> Unit) {
            if (value == null) {
                withContext(Dispatchers.Main) {
                    block()
                }
            }
        }
    }
    

    I changed 3 things:

    • Added upper bounds for CoroutineLinker to Any.
    • Added io function.
    • Changed the order of if and withContext in both functions - this is just optimization, it wasn't required.