0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Kotlin】sealed Result × Flow × Coroutine で実現する安全な非同期処理(safeApiFlow)

Posted at

はじめに

Kotlin では、非同期処理の標準として Coroutine + Flow が使われています。
しかし、ネットワークやDBアクセスなどの実行中に
「例外処理」や「Loading状態管理」が複雑になりがちです。

そこで登場するのが:

sealed Result × Flow × Coroutine の三位一体構成

これを使うと、
API呼び出しやデータ取得の全ライフサイクルを
型安全かつ宣言的 に扱うことができます。


Step 1. Result を sealed class で定義

まずは成功・失敗・読み込み状態を表す Result<T>

sealed class Result<out T> {
    data class Success<out T>(val data: T) : Result<T>()
    data class Failure(val exception: Throwable) : Result<Nothing>()
    object Loading : Result<Nothing>()
}
  • Success:処理成功
  • Failure:例外保持
  • Loading:実行中状態

Step 2. inline + reified で安全な例外ラップ

例外を安全に包む safeCall 関数を定義します。

inline fun <T> safeCall(block: () -> T): Result<T> {
    return try {
        Result.Success(block())
    } catch (e: Exception) {
        Result.Failure(e)
    }
}

使用例

val result = safeCall { api.getUser() }

Step 3. Flow で非同期処理を統一化(safeApiFlow)

ここが本記事の核心です。
API呼び出しなどを Flow でラップし、
「Loading → Success / Failure」までの流れを自動化します。

import kotlinx.coroutines.flow.*

inline fun <T> safeApiFlow(
    crossinline block: suspend () -> T
): Flow<Result<T>> = flow {
    emit(Result.Loading)
    try {
        val data = block()
        emit(Result.Success(data))
    } catch (e: Exception) {
        emit(Result.Failure(e))
    }
}

使用例

val userFlow = safeApiFlow {
    api.getUser() // suspend関数
}

userFlow.collect { result ->
    when (result) {
        is Result.Loading -> println("読み込み中...")
        is Result.Success -> println("成功: ${result.data}")
        is Result.Failure -> println("失敗: ${result.exception.message}")
    }
}

これで「try-catch不要」かつ「状態管理をFlowで自動化」できます。


Step 4. reified 型でエラー型ごとの処理も可能に

reified を使えば、特定の例外型を安全にハンドリング可能です。

inline fun <reified E : Throwable> Result<*>.onErrorOfType(action: (E) -> Unit): Result<*> {
    if (this is Result.Failure && exception is E) {
        action(exception as E)
    }
    return this
}

使用例

safeApiFlow { api.getUser() }
    .collect { result ->
        result.onErrorOfType<HttpException> {
            println("HTTPエラー: ${it.code()}")
        }
    }

Step 5. safeApiFlow の拡張: リトライ / タイムアウト対応

Flow のオペレータと組み合わせて、再試行や制限も容易です。

val resultFlow = safeApiFlow { api.getUser() }
    .retry(3) { e -> e is IOException }   // 3回まで再試行
    .catch { e -> emit(Result.Failure(e)) }
    .onCompletion { println("完了しました") }

→ Coroutine Flow の組み合わせで、
リトライ・再購読・キャンセル にも対応。


Step 6. ViewModel 層での利用(MVI 構成例)

class UserViewModel(
    private val repository: UserRepository
) : ViewModel() {

    private val _uiState = MutableStateFlow<Result<User>>(Result.Loading)
    val uiState = _uiState.asStateFlow()

    fun loadUser() {
        viewModelScope.launch {
            repository.getUserFlow().collect {
                _uiState.value = it
            }
        }
    }
}

class UserRepository(private val api: UserApi) {
    fun getUserFlow(): Flow<Result<User>> = safeApiFlow {
        api.getUser()
    }
}

→ UI層では when(result) で状態を安全に切り替えるだけ。

viewModel.uiState.collect { result ->
    when (result) {
        is Result.Loading -> showLoading()
        is Result.Success -> showUser(result.data)
        is Result.Failure -> showError(result.exception.message)
    }
}

拡張パターン: 汎用 ResultHandler

複数のAPI結果を統一処理するヘルパーも作れます。

inline fun <T> Result<T>.handle(
    onLoading: () -> Unit = {},
    onSuccess: (T) -> Unit = {},
    onError: (Throwable) -> Unit = {}
) {
    when (this) {
        is Result.Loading -> onLoading()
        is Result.Success -> onSuccess(data)
        is Result.Failure -> onError(exception)
    }
}

使用例

safeApiFlow { api.getUser() }
    .collect { result ->
        result.handle(
            onLoading = { println("ロード中...") },
            onSuccess = { println("成功: $it") },
            onError = { println("失敗: ${it.message}") }
        )
    }

まとめ

要素 役割 効果
sealed class Result 成功・失敗・読み込みを型で表現 網羅性・安全性
inline パフォーマンス最適化・tryブロック内展開 高速 & 明確
reified 例外型を実行時に識別 型安全なエラーハンドリング
Flow 非同期データストリーム Coroutine連携・リアクティブ処理
safeApiFlow 非同期処理の統一パターン 再利用性・シンプル設計

終わりに

sealed Result × Flow × Coroutine の組み合わせは、
「try-catch地獄」から脱却し、
宣言的で安全な非同期アーキテクチャ を実現するための最強トリオです。

  • 状態遷移を型で保証
  • エラー処理を統一
  • ViewModel と UI の接続を簡潔化

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?