Skip to content

A Kotlin library for representing loadable content.

Notifications You must be signed in to change notification settings

jeanbarrossilva/loadable

Repository files navigation

Loadable

Download

Loadable is available at Maven Central, so make sure you add it to your project-level build.gradle:

allprojects {
    repositories {
        mavenCentral()
    }
}

Then, import the library into your module (replacing "<version>" by the latest version that's available):

dependencies {
    implementation("com.jeanbarrossilva.loadable:loadable:<version>")
}

Stages

The main interface in this library, Loadable, represents three different stages of asynchronously-loaded content.

Loading

Stage in which the content is being loaded and, therefore, is temporarily unavailable.

Loaded

Stage in which the content has been successfully loaded. Holds the content itself.

Failed

Stage in which the content has failed to load and threw an error. Holds a Throwable.

Usage

Loadable relies heavily on Kotlin's coroutines and Flows, offering functions and extensions that can, in turn, convert any (literally, Any) structure or, specifically, Flows into loadable data.

Suppose, for example, that we have an app in which we want to display a sequence of numbers fetched from the server, and also inform whether it's loading or if it has failed:

CoroutineScope(Dispatchers.IO).launch {
    loadableFlow { // this: LoadableScope<Int>
        // getNumbersFlow is a suspending network call that returns Flow<Int>.
        getNumbersFlow()
        // loadTo is a terminal operator that turns Flow<Int> into a Flow<Loadable<Int>> and emits
        // all of its Loadables to the outer loadableFlow.
            .loadTo(this)
    }
        .collect {
            withContext(Dispatchers.Main) {
                when (it) {
                    is Loadable.Loading -> display("Loading...")
                    is Loadable.Loaded -> display(it.content)
                    is Loadable.Failed -> display(it.error.message)
                }
            }
        }
}

In this scenario, loadableFlow was used purely because it has a lambda through which we can collect other Flows suspendingly and getNumbersFlow is a suspending function. If that wasn't the case and we wanted to just transform a Flow<T> into a Flow<Loadable<T>>, it could be accomplished by one of the following:

val coroutineScope = CoroutineScope(Dispatchers.IO)
val numbersFlow = flowOf(1, 2, 3, 4)

// Returns Flow<Loadable<Int>>.
numbersFlow.loadable()

// Returns StateFlow<Loadable<Int>>, started and with its value shared in the given coroutine scope.
numbersFlow.loadable(coroutineScope)