Search code examples
androidkotlinasynccallbackandroid-ion

Kotlin - Avoid nested callbacks when fetching multiple JSON files with ION


I have some code that 1] fetches a JSON file and 2] depending on the content of that JSON file, fetches another JSON file. It uses the ion library.

The code roughly looks like this (Sidenote - this code is on the onCreate method of my activity and e is an object of the type Exception):

Ion.with(applicationContext)
                .load("someURL")
                .asJsonObject()
                .setCallback { e, result->
                    //Do synchronous stuff with the "result" and "e" variables
                    //that determines whether boolean someCondition is true or false 

                    if(somecondition) {
                        Ion.with(applicationContext)
                                .load("https://raw.githubusercontent.com/vedantroy/image-test/master/index.json")
                                .asJsonObject()
                                .setCallback { e, result ->

                                    //Some synchronous code in here

                                }
                    } 

                }

This code is very ugly, and I'd like to improve it in order to get rid of the nesting. Is that possible, and if so, how would I do it?

I've been experimenting and it seems I can call the method .then() after setCallback like so, where .then() accepts a parameter that is a callback object:

Ion.with(applicationContext).load("someURL").setCallback { //Lambda Stuff }.then()

so maybe that will be part of the solution, but I'm not sure.


Solution

  • The Ion project has a module for Kotlin coroutines: https://github.com/koush/ion/tree/master/ion-kotlin, but it doesn't seem to be in a production state. Luckily, it is very simple to add your own bridge between any asynchronous API and Kotlin coroutines.

    Here's an example of how to do it for Ion:

    private suspend fun fetchIon(url: String): String = suspendCoroutine { cont ->
        Ion.with(this)
                .load(url)
                .asString()
                .setCallback { e, result ->
                    if (e != null) {
                        cont.resumeWithException(e)
                    } else {
                        cont.resume(result)
                    }
                }
    }
    

    Now you can have code like this:

    class MyActivity : Activity() {
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            launch(UI) {
                if (fetchIon("example.org/now") > fetchIon("example.org/deadline") {
                    ...
                }
            }
        }
    

    As you can see, you execute asynchronous IO code just like it was blocking. To handle errors, just wrap it in a try-catch, as you would any "regular" code.