【Kotlin/Android】Result型の使い方!runCatchingとは?

【Kotlin/Android】Result型の使い方!runCatchingとは?

この記事からわかること

  • Android Studio/KotlinResult使い方
  • onSuccess/onFailure実装方法
  • runCatchingとは?

index

[open]

\ アプリをリリースしました /

みんなの誕生日

友達や家族の誕生日をメモ!通知も届く-みんなの誕生日-

posted withアプリーチ

環境

Result型

公式リファレンス:Result


inline class Result<out T> : Serializable

KotlinのResult型は操作の結果をラップして保持できるデータ型です。成功した際には指定したデータ型の値失敗した際には例外を保持することができます。

結果を返すメソッドなどで返り値として使用されることが多く、成功時の失敗時のエラーハンドリングが行いやすくコードの見通しもスッキリさせることができます。

使い方

Result型をメソッドの返り値として指定する場合はResult.successで成功した場合のデータを、Result.failureで例外を返します。

private fun fetchLocalData(): Result <String> {
    if (true) {
        return Result.success("データ")
    } else {
        return Result.failure(Error("ERROR"))
    }
}

onSuccess/onFailure

取得したResult型からはonSuccess成功した場合の処理を、onFailure失敗した場合の処理を記述することができます。

val result = fetchLocalData()
result
    .onSuccess { data ->
        println(data)
    }.onFailure { error ->
        println(error)
    }

isSuccess/isFailure

isSuccess成功しているかどうかを、isFailure失敗しているかどうかを取得することができます。

result.isSuccess
result.isFailure

getOrNull/exceptionOrNull

getOrNull値があれば取得なければnullを、exceptionOrNullエラーがあれば取得なければnullを取得することができます。

val value = result.getOrNull()
val error = result.exceptionOrNull()

getOrDefault/getOrElse

getOrDefault値があれば取得なければ指定したデフォルト値を、getOrElse値があれば取得なければデフォルト値をラムダ式で返却し取得することができます。

val value = result.getOrElse { "デフォルト値" }
val value = result.getOrDefault("デフォルト値")

getOrThrow

getOrThrow値があれば取得、エラーなら例外をスローすることができます。

try {
    // 成功している場合、値を取得
    val value = successResult.getOrThrow()
    println("Success: $value") // "Success: Hello, World!"
} catch (e: Exception) {
    // 失敗している場合、例外をキャッチ
    println("Error: ${e.message}")
}

fold

fold成功時と失敗時の処理をそれぞれ指定し、その結果を返すことができます。

val msg = result.fold(
    onSuccess = { value -> "Success: $value" },
    onFailure = { error -> "Error: ${error.message}" }
)

recover

recover失敗時に指定した値を返すことができます。

val value = result.recover { "失敗した時に返したい値" }

runCatching

公式リファレンス:runCatching

Result型を簡単に利用する方法runCatchingがあります。runCatchingメソッドは指定したラムダ式を実行しその結果をResult型で返します。ブロックが正常に完了した場合は成功としてResultにラップされ、例外が発生した場合は失敗としてResultにラップされます。

val successResult = runCatching {
    "Hello, World!"
}
println(successResult) // Result.success(Hello, World!)

// 失敗する場合
val failureResult = runCatching {
    throw Exception("Something went wrong")
}
println(failureResult) // Result.failure(java.lang.Exception: Something went wrong)

runCatchingとKotlin Coroutine

参考文献:公式リファレンス:Provide a runCatching that does not handle a CancellationException but re-throws it instead.

runCatchingはブロック内で発生した例外を補足しResult.failureにラップします。これはKotlin CoroutineのCancellationExceptionも捕捉してしまうようです。つまりスコープをキャンセルしたのにも関わらずrunCatchingに捕捉されてしまいスコープ自体が正常にキャンセルしない事象を招きます。

fun main() {
    val screenScope = CoroutineScope(Dispatchers.IO)

    println("Start")

    screenScope.launch {
        println("Launched")

        val result = runCatching {
            // 1秒停止
            delay(1000)
            4
        }.getOrElse { 0 }

        writeToDatabase(result)
    }
    // 0.5秒停止
    Thread.sleep(500)

    screenScope.cancel()

    println("Job was cancelled: ${screenScope.coroutineContext[Job]?.isCancelled}")
    println("Done")
}

suspend fun writeToDatabase(result: Int) {
    println("Writing $result to database")
}

launch内の処理ではsleepで処理より先にスコープがキャンセルするように実装しています。このコードを実行すると以下のように出力されます。(※ コードはこちらから引用しています。)

Start
Launched
Writing 0 to database
Job was cancelled: true
Done

出力を見るとわかりますがキャンセルが発生しているのにwriteToDatabaseが実行されていることがわかります。これはrunCatchingCancellationExceptionを捕捉してResultにラップしてしまうことでスコープまでキャンセルの例外が伝播しないためです。

これを防ぐにはsuspendRunCatchingのような独自の拡張関数を定義して回避する必要があります。正常にCancellationExceptionが流れるように捕捉したら再スローすることによってCancellationExceptionを閉じ込めないようにしています。

suspend fun <R> suspendRunCatching(block: suspend () -> R): Result<R> {
    return try {
        Result.success(block())
    } catch(c: CancellationException) {
        throw c
    } catch (e: Throwable) {
        Result.failure(e)
    }
}

まだまだ勉強中ですので間違っている点や至らぬ点がありましたら教えていただけると助かります。

ご覧いただきありがとうございました。

Search Box

Sponsor

ProFile

ame

趣味:読書,プログラミング学習,サイト制作,ブログ

IT嫌いを克服するためにITパスを取得しようと勉強してからサイト制作が趣味に変わりました笑今はCMSを使わずこのサイトを完全自作でサイト運営中〜

New Article

index