The modelling for success/failure of operations in Kotlin and KMM (Kotlin Multiplatform Mobile)

Overview

Result

Kotlin Github Action MavenCentral Codecov

This is a tiny framework for modelling success/failure of operations in Kotlin. In short, it is a model in type of Result<V: Any?, E : Throwable>.

Ideology

Result<V: Any?, E: Throwable> is to provide higher abstraction of operation that can be ended with result either success or failure. Result.Success represents value in case of success, and Result.Failure represents error in case of failure which is upper bounded with Throwable type.

Installation

Gradle

repositories {
    mavenCentral()
}

dependencies {
    // if you are working on JVM or Android only project
    implementation("com.github.kittinunf.result:result-jvm:«version»") //for JVM support
    
    // if you are working in KMM project
    implementation("com.github.kittinunf.result:result:«version»") //for Kotlin Multiplatform support
}

TL;DR

This model is highly inspired by "Railway Oriented Programming" concept.

Result allows one to express series of success/failure operations in Kotlin as;

Result.of<T, Throwable>(doOperation())
      .flatMap { normalizedData(it) }
      .map { createRequestFromData(it) }
      .flatMap { database.updateFromRequest(it) }

Work with Result is easy

//multi-declaration
val (value, error) = result

//get
val value: Int = result.get() // throw exception if error

//terminal operator
//success
result.success {
}

//failure
result.failure {
}

//fold is there, if you want to handle both success and failure
result.fold({ value ->
    //do something with value
}, { err ->
    //do something with err
})

Why

Result is suitable whenever there is a need to represent an operation that has the possibility of failure. Error handling can be cumbersome to work with. Result helps process the operations in a nice, functional way, while maintaining readability to your code.

Let's consider a need to read data from foo, and to perform some further validation

fun process(): String {
    try {
        val foo = File("/path/to/file/foo.txt").readText()
        val isSuccessful = processData(foo)
        if (!isSuccessful) {
            return "Data is corrupted and cannot be processed"
        }
    } catch (e: Throwable) {
        //do something if error
        Logger.log(ERROR, e.message())
    }
}

However, things start getting ugly when we have chain of operations being run sequentially, such as

fun process(): String {
    try {
        val foo = File("/path/to/file/foo.txt").readText()
        val isSuccessful = normalizedData(foo)
        if (!isSuccessful) {
            return "Data cannot be processable"
        }
        val request = createRequestFromData(foo)
        try {
            val result = database.updateFromRequest(request)
            if (!result) {
                return "Record in DB is not found"
            }
        } catch (dbEx: DBThrowable) {
            return "DB error, cannot update"
        }
    } catch (e: Throwable) {
        //do something if error
        Logger.log(ERROR, e.message())
    }
}

Ouch, it looks pretty bleak.

Let's see how Result can help us.

First, we break things down into a small set of model in Result.

  • Read a file
val operation = { File("/path/to/file/foo.txt").readText() }
Result.of { operation() }  // Result<String, FileThrowable>
  • Normalize a data
fun normalizedData(foo): Result<Boolean, NormalizedThrowable> {
    Result.of { foo.normalize() }
}
  • Create a request from data
fun createRequestFromData(foo): Request {
    return createRequest(foo)
}
  • Update DB with Request
fun database.updateFromRequest(request): Result<Boolean, DBThrowable> {
    val transaction = request.transaction
    return Result.of { 
        db.openTransaction {
            val success = db.execute(transaction)
            if (!success) {
                throw DBThrowable("Error")
            }
            return success
        }
    }
}

The whole operation can be chained by the following;

Result.of { doOperation() }
      .flatMap { normalizedData(it) }
      .map { createRequestFromData(it) }
      .flatMap { database.updateFromRequest(it) }

The creates a nice "happy path" of the whole chain, also handle error as appropriate. It looks better and cleaner, right?.

Never Fail Operation

In some case, one wants to model an always successful operation. Result<V: Any?, NoException> is a good idea for that. NoException is to indicate that there is no exception to throw. E.g.

// Add operation can never be failure
fun add(i: Int, j: Int) : Result<Int, NoException>

Nice thing about modelling in this way is to be able to compose it with others "fail-able" operations in Result.

High Order functions

Success

map and flatMap

map transforms Result with given transformation (V) -> U. As a result, we are able to transform V into a new V in the case where Result is Result.Success. When Result is Result.Failure, error is re-wrapped into a new Result.

flatMap is similar to map, however it requires transformation in type of (V) -> Result<U, ...> .

Failure

mapError and flatMapError

mapError ((E) -> E2) and flatMapError ((E) -> Result<E2, ...>) are counterpart of map and flatMap. However, they are operate on Result.Failure. It is quite handy when one needs to do some transformation on given Throwable into a custom type of Throwable that suits ones' need.

More features

Please check out more features in the ResultTest

Railway Oriented Programming

If interested, here are more articles that one might enjoy.

Credit to Scott Wlaschin

Credits

Result is brought to you by contributors.

License

Result is released under the MIT license.

Comments
  • getOrNull

    getOrNull

    Hey, it would be nice if I could do result.getOrNull or result.getOrElse(null) or similar as sometimes nulls are used to show there is an error instead of an exception being thrown.

    opened by Globegitter 8
  • Opinion on stdlib `kotlin.Result<T>` in kotlin 1.3

    Opinion on stdlib `kotlin.Result` in kotlin 1.3

    Kotlin 1.3 introduces a new built in type called Result<T> (docs) in the standard library (stdlib) and in the kotlin package.

    Do you have an opinion on it? It seems to have taken a lot of inspiration from this library, but with one big difference: It doesn't have an equivalent to flatMap for operations that return a new Result<T>. It also doesn't place any constraints on errors, which are typed as Throwable.

    The immediate inconvenience after upgrading my project to kotlin 1.3 is that I now need to explicitly add the import to use com.github.kittinunf.result.Result.

    The KEEP can be seen here: https://github.com/Kotlin/KEEP/blob/master/proposals/stdlib/result.md

    opened by nieldw 8
  • :arrow_up: [4.0.0] Version 4.0.0

    :arrow_up: [4.0.0] Version 4.0.0

    Description

    This is to release the new version for Result v4.0.0.

    • Thanks sooo much for all the effort made by @Globegitter #74, this indeed a great improvement on the library.
    enhancement 
    opened by kittinunf 7
  • ✨Implemented doOnError method

    ✨Implemented doOnError method

    I've implemented doOnError method on Result which is very similar to RxJava doOnError operator.

    Example usage:

    val num = Result.of<Int, Exception> { "1a".toInt() }
                .doOnError { log.error { "Cannot parse value to Int" } }
                .get()
    
    opened by pitagoras3 7
  • Dealing with unexpected exceptions more elegantly

    Dealing with unexpected exceptions more elegantly

    Whenever an unexpected exception gets thrown while evaluating a function with Result it leads to an unintuitive ClassCastException. Here is a simple example:

    import com.github.kittinunf.result.Result
    import org.junit.jupiter.api.Test
    import kotlin.test.fail
    
    internal class ResultExceptionClassCastTest {
        class MyException: Exception()
    
        @Test
        fun `Unexpected exception leads to unintuitive ClassCastException`() {
            Result.of<String, MyException> {
                operation("something")
            }.fold({}, {
                fail("Not reached")
            })
        }
    
        private fun operation(it: String): String {
            throw IllegalArgumentException()
            return "never reached $it"
        }
    }
    
    

    Is there something that I as a library user can do to handle this better? Is this an issue with the library?

    The simple solution would be to expect an Exception (or Throwable!), and then to cast to the expected type, if it indeed is that type, but this doesn't seem like a very good solution.

    opened by nieldw 7
  • Artifact result-coroutines not found

    Artifact result-coroutines not found

    Hi,

    Firstly I would like to congratulate you for the awesome project!

    I'm trying to use result-coroutines in my project, but Gradle cannot download the artifact. Any idea?

    I have the following repositories:

    repositories {
        google()
        jcenter()
        maven { url "http://kotlin.bintray.com/kotlinx" }
    }
    
    opened by wellingtoncosta 7
  • What about Result<T?>

    What about Result

    I'm new to Kotlin, so I may be missing something obvious, but it seems to me that Result values are constrained to Any rather than Any? because of <V : Any>.

    I believe this means that I can't have a result of Result<String?, Exception>.

    What should we do when null is a legal value?

    I've been writing a JSON decoder combinator where Decoder<T> means that the decoder will return Result<T, Exception> when applied to a JSON string. Since JSON can contain nulls, I want to implement a null combinator such as Decoder.nullable(Decoder.string) which returns Result<String?, Exception>.

    However, I wasn't sure how to make this work beyond bringing in some sort of Maybe<T> : Just<T> | None monad so that I could return Result<Maybe<String>, Exception>. I'd prefer to just use Kotlin's optional here since it already exists.

    opened by danneu 7
  • Enforced nullability on get()'s return type

    Enforced nullability on get()'s return type

    I just migrated from 2.2.0 to the current latest version (5.1.0) and the main diference I saw was the nullable success, which I found just great!

    But, one thing that I didn't get was the signature change on the get() method. Given that Result.Success<V>'s V can now be a nullable type, why does the Result's abstract get() has to have an "enforced" nullable return (: V?).

    This feels somewhat counter intuitive to me, given that, even if I have a non-nullable Result value (e.g.Result<String, Exception>), its get() will always return a nullable one (String?), which just includes an (apparently) unnecessary complexity to handle the return.

    Is there a particular reason for this design? Perhaps some use case that I'm not seeing right now? If no, I would like to propose a change removing this enforced nullability on get()'s return type: https://github.com/kittinunf/Result/pull/91

    opened by lopes-felipe 6
  • :construction_worker: Added new methods and inline methods

    :construction_worker: Added new methods and inline methods

    This adds most of the methods and changes we have been using internally and we think really improves the usability of this library and fixes an imo very important bug we ran into a lot at the beginning in production and is not super easy to debug if you are not aware of it: #59

    One nice thing about having all these inline methods it basically means that we do not need a separate coroutines result but that one can work out of the box with coroutines as you can see in the .map test I added.

    I know this PR adds a lot at once, I am also happy to split it up and I guess it would require a 4.0 release. Let me know what you think @kittinunf .

    opened by Globegitter 6
  • Change Exception to Throwable

    Change Exception to Throwable

    Since Exception extends Throwable, I think the better way is to use Throwable over Exception for type and catching errors.

    Kotlin Standard Library

    image

    Tweet

    https://twitter.com/relizarov/status/1299248297849303040

    opened by seanghay 6
  • Provide an idiomatic way to flatten List<Result>

    Provide an idiomatic way to flatten List

    From https://github.com/kittinunf/fuel/issues/805:

    It'd be nice to able to do the following:

    1. Transform List<Result<Foo, FuelError>> into Result<List<Foo>, FuelError>. If any of the Results is an error, the final Result is an error too, sort of an AND.
    2. Transform List<Result<Foo, FuelError>> into Result<List<Foo>, FuelError>. If any of the Results is a success, the final Result is a success too, sort of an OR.
    opened by asarkar 4
  • Android Studio always tried to import kotlin.Result

    Android Studio always tried to import kotlin.Result

    This is more a question. Probably somebody figured out how to tell to Android Studio that I'm using com.github.kittinunf.result.Result instead of the default one. There is no issue if I'm typing Result symbol by symbol - then I can just choose appropriate import. However, if I insert code from browser or other place (not AS), then AS always uses default Result. Is there a way to substitute it for all the cases by com.github.kittinunf.result.Result?

    opened by alexzaitsev 2
Releases(5.3.0)
Owner
Kittinun Vantasin
Android/iOS Enthusiast
Kittinun Vantasin
AppToDo is a simple Android project that performs the basic database CRUD operations that creates a todo task list

AppToDo is a simple Android project that performs the basic database CRUD operations that creates a todo task list

Rafiul Hye 1 Mar 13, 2022
Ksp-di-library - Small library for DI in KMM apps

DI-KSP Small library for DI in KMM apps. Uses KSP for processing DI annotations:

Anna Zharkova 3 Feb 6, 2022
Mobile client for official Nextcloud News App written as Kotlin Multiplatform Project

Newsout Android and iOS mobile client for Nextcloud news App. The Android client is already available to download in the Play Store. F-Droid and Apple

Simon Schubert 118 Oct 3, 2022
KMMT : Kotlin Multiplatform Mobile Template

Kotlin Multiplatform Mobile App Template

Jitty Andiyan 206 Dec 22, 2022
HelloKMM - Hello World in Kotlin Multiplatform Mobile (new empty project)

Hello KMM! Hello World in Kotlin Multiplatform Mobile (new empty project) Gettin

Blake Barrett 1 Feb 2, 2022
Framework for Mobile test automation (Native app and Browser) on Android and IOS devices

Appium Mobile Automation Framework Framework for Mobile test automation (Native app and Browser) on Android and IOS devices ?? ?? Quick Start - Appium

Thangaraj 40 Nov 18, 2022
This Kotlin Multiplatform library is for accessing the TMDB API to get movie and TV show content. Using for Android, iOS, and JS projects.

Website | Forum | Documentation | TMDb 3 API Get movie and TV show content from TMDb in a fast and simple way. TMDb API This library gives access to T

Moviebase 37 Dec 29, 2022
Native-Blur: a C++/Kotlin library for blur bitmaps and activity, mobile-ready, android compatible

Native-Blur The Native-Blur is a C++/Kotlin libraray for blur bitmaps and activity, mobile-ready, android compatible, powered by Java Native Interface

Abolfazl Abbasi 26 Dec 13, 2022
Mocking for Kotlin/Native and Kotlin Multiplatform using the Kotlin Symbol Processing API (KSP)

Mockative Mocking for Kotlin/Native and Kotlin Multiplatform using the Kotlin Symbol Processing API (KSP). Installation Mockative uses KSP to generate

Mockative 121 Dec 26, 2022
Taskify is a mobile application used to create and schedule tasks in your TODO list

Taskify is a mobile application used to create and schedule tasks in your TODO list. It is built upon the new Maaterial 3 UI components with the MVVM pattern and the latest Jetpack components.

Robert Muriithi 2 Jun 25, 2022
Integration Testing Kotlin Multiplatform Kata for Kotlin Developers. The main goal is to practice integration testing using Ktor and Ktor Client Mock

This kata is a Kotlin multiplatform version of the kata KataTODOApiClientKotlin of Karumi. We are here to practice integration testing using HTTP stub

Jorge Sánchez Fernández 29 Oct 3, 2022
FlowExt is a Kotlin Multiplatform library, that provides many operators and extensions to Kotlin Coroutines Flow

FlowExt | Kotlinx Coroutines Flow Extensions | Kotlinx Coroutines Flow Extensions. Extensions to the Kotlin Flow library | kotlin-flow-extensions | Coroutines Flow Extensions | Kotlin Flow extensions | kotlin flow extensions | Flow extensions

Petrus Nguyễn Thái Học 151 Jan 1, 2023
SSU u-saint parser with Kotlin-Multiplatform and Ktor.

kusaint Soongsil University(SSU) u-Saint Parser with Kotlin Multiplatform. Prerequisites JVM !!IMPORTANT!! To run kusaint as a library in JVM environm

Hyomin Koo 3 Mar 23, 2022
Simple and extendable code highlighter in Kotlin Multiplatform

Kighlighter Simple and extendable code highlighter in Kotlin Multiplatform with a composable output to display the code highlighted on Android and Des

Gérard Paligot 21 Dec 2, 2022
A simple MVI framework for Kotlin Multiplatform and Android

Orbit Multiplatform Get in touch What is Orbit Orbit is a Redux/MVI-like library - but without the baggage. It's so simple we think of it as MVVM+. Si

null 521 Jan 1, 2023
Create libraries for all types of Kotlin projects: android, JVM, Multiplatform, Gradle plugins, and so on.

JavierSC Kotlin template Create libraries for all types of Kotlin projects: android, JVM, Multiplatform, Gradle plugins, and so on. Features Easy to p

Javier Segovia Córdoba 2 Dec 14, 2022
HackerNews with Kotlin Multi-platform mobile technology

KNews The goal of this project is to build mobile apps that consumes HackerNews API with Kotlin Multi-Platform technology. About My idea is to build 2

Kittinun Vantasin 43 Oct 3, 2022
A kotlin based server for the milky store mobile application

Milky Store backend server A kotlin ?? ?? based backend server to power up the milky store android application FEATURES : Secure serialisation ?? lead

The X 3 Oct 21, 2021
A Kotlin Mobile Application to help solo Travellers on the same route during the trip

TravelMate ?? ??️ ?? A Kotlin Mobile Application to help solo Travellers on the same route during the trip. Like what you see ? Give the repo a ⭐ Trav

Rhea Sera Rodrigues 0 Nov 18, 2021