Esito

Additional

Language
Kotlin
Version
N/A
Created
Nov 3, 2021
Updated
Nov 4, 2021 (Retired)
Owner
Subito (Subito-it)
Contributor
Daniele Campogiani (dcampogiani)
1
Activity
Badge
Generate
Download
Source code

Esito

Coroutines are great for Asynchronous and non-blocking programming, but exceptions could be hard to handle, especially in a coroutine. [1, 2, 3]

Exceptions in a coroutine could cancel their parents, and each canceled parent cancels all its children, and this is not always the desired behavior.

While this could be changed using a SupervisorJob, it's still easy to shoot yourself in the foots throwing exceptions in Coroutines, so Esito is proposing an alternative approach making explicit the computations that could fail.

Installation

repositories {
 maven { url 'https://jitpack.io' }
}

dependencies {
    implementation("com.github.Subito-it.Esito:core:(insert latest version)")
}

Getting started

The main class is the sealed class Result, with two subtypes:

  • Success: it contains the result of a successful computation
  • Failure: it contains the cause of an unsuccessful computation

The goal when using Esito is to avoid throwing exceptions and use Result as the return type of a function that can fail:

sealed class ConversionError
object EmptyInput : ConversionError()
object NotNumericInput : ConversionError()

fun fromStringToInt(input: String): Result<Int, ConversionError> = when {
    input.isBlank() -> Result.failure(EmptyInput)
    else -> runCatching { input.toInt() }.mapError { NotNumericInput }
}

In this example we are defining all the possible failures in ConversionError, then we are applying some logic to build our result.

If the input is not blank we are using the runCatching method to wrap a method throwing an exception and mapping the eventual error in our desired type.

Operators

Esito result has several operators, such as map and flatmap, for examples see Recipes.

Retrofit Integration

Esito ships an integration with retrofit, after a one-line setup you can start to use Result return type in your Retrofit interfaces:

interface GitHubService {
    
    @GET("users/{user}/repos")
    suspend fun listRepos(@Path("user") user: String?): Result<List<Repo>, Throwable>
}

For additional info and to learn how to use your own Error instead of Throwable have a look at this documentation.

Async Utilities

Esito offers some utilities for suspending methods returning Result. For example, suppose we have the following functions:

suspend fun getUserFullName(userId: Int): Result<String, FeatureError>

suspend fun getUserStatus(userId: Int): Result<UserStatus, FeatureError>

And we have to return an instance of DataForUI running in parallel getUserFullName and getUserStatus.

data class DataForUI(
 val userFullName: String,
 val userStatus: UserStatus
)

Esito exposes a zip method to compute two independent execution in parallel:

suspend fun fetchIfo(userId: Int): Result<DataForUI, FeatureError> =
 zip(
  { getUserFullName(userId) },
  { getUserStatus(userId) },
  ::DataForUI //syntactic sugar for constructor
  ).invoke()

For additional info have a look at this documentation.

Testing

Esito is providing two extension methods to facilitate testing: assertIsSuccess and assertIsFailure, here is an example of usage:

val success = Result.success<Int, Throwable>(42)
success.assertIsSuccess {
    assertEquals(42, value)
}

val failure = Result.failure<Int, RuntimeException>(RuntimeException("Ops"))
failure.assertIsFailure {
    assertEquals("Ops", error.message)
}

For additional info have a look at this documentation.