# Error Handling

> Error handling flow from data layer to UI. Use when implementing AppError handling in ViewModels with showErrorToast, adding new error types to AppError sealed class, extending ExceptionMapper for new infrastructure exceptions, extending ErrorHandler for new UI messages, or understanding the complete Exception to AppError to Toast flow.

- Skill: `thetruong1099/error-handling` (Agent Skill)
- Install (CLI): `npx skillmds@latest add thetruong1099/error-handling`
- Raw SKILL.md: https://api.skillmd.com/api/skills/thetruong1099/error-handling/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: thetruong1099 (https://skillmd.com/u/thetruong1099)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/thetruong1099/error-handling

---


# Error Handling

## Architecture

```
Data Layer              Domain Layer              Presentation Layer
ExceptionMapper  -->    AppError (sealed)  -->    ErrorHandler
(Exception -> domain)   (in DataState)           (AppError -> string res)
```

## AppError (domain/core)

```kotlin
sealed class AppError {
    abstract val loggableMessage: String?   // For logging ONLY, never show to users

    // Network
    data class NoInternetConnection(...) : AppError()
    data class NetworkTimeout(...)       : AppError()
    data class ServerError(val statusCode: Int, ...) : AppError()

    // Data
    data class DataParsingError(val exception: Exception, ...) : AppError()

    // Auth
    data class Unauthorized(...)         : AppError()

    // Generic
    data class Unknown(val exception: Throwable? = null, ...) : AppError()
}
```

## ExceptionMapper (data/core)

Maps infrastructure exceptions to AppError. Used internally by BaseDataSource strategies — do NOT call from presentation
layer.

```
SocketTimeoutException   -> AppError.NetworkTimeout
IOException              -> AppError.NoInternetConnection
HttpException(401)       -> AppError.Unauthorized
HttpException(400-499)   -> AppError.ServerError(statusCode)
HttpException(500-599)   -> AppError.ServerError(statusCode)
JsonEncodingException    -> AppError.DataParsingError
JsonDataException        -> AppError.DataParsingError
Other                    -> AppError.Unknown
```

## ErrorHandler (feature/core)

```kotlin
fun getErrorMessageResId(error: AppError): Int = when (error) {
    is AppError.NoInternetConnection -> R.string.no_connect_internet
    is AppError.NetworkTimeout       -> R.string.network_timeout
    is AppError.Unauthorized         -> R.string.unauthorized
    is AppError.ServerError          -> R.string.server_error
    is AppError.DataParsingError     -> R.string.data_error
    // ...
}
```

## Error Flow Patterns

### Pattern 1: DataState Flow (most common)

```kotlin
// Data layer handles automatically via strategy classes

// ViewModel:
viewModelScope.launch {
    collectDataStateWithInternet(
        callFlow = useCase(params),
        onSuccess = { data -> setState { copy(detail = data) } },
        onError = { error -> showErrorToast(error) },
    )
}
```

### Pattern 2: PagingData Flow

```kotlin
val result = callPagingDataWithInternet(
    callFlow = { useCase() },
    onError = { error -> showErrorToast(error) },
).cachedIn(viewModelScope)
```

### Pattern 3: Suspend operations

```kotlin
viewModelScope.launch {
    callSuspendWithInternet(
        operation = { useCase(params) },
        onSuccess = { setState { copy(saved = true) } },
        onError = { error -> showErrorToast(error) },
    )
}
```

## When to Use Which Function

| Scenario                    | Function                          | Internet Check |
|-----------------------------|-----------------------------------|----------------|
| PagingData from network     | `callPagingDataWithInternet()`    | Yes            |
| PagingData from local       | `callPagingDataWithoutInternet()` | No             |
| DataState flow from network | `collectDataStateWithInternet()`  | Yes            |
| DataState flow from local   | `collectDataState()`              | No             |
| One-shot network operation  | `callSuspendWithInternet()`       | Yes            |
| One-shot local operation    | `callSuspendWithoutInternet()`    | No             |
| Regular flow collection     | `collectFlowWithInternet()`       | Yes            |

## Adding a New Error Type

1. Add to `AppError` in `domain/core`:

```kotlin
data class NewErrorType(
    override val loggableMessage: String? = "Description"
) : AppError()
```

2. Map in `ExceptionMapper` (if from infrastructure exception):

```kotlin
is MyCustomException -> AppError.NewErrorType(...)
```

3. Map in `ErrorHandler`:

```kotlin
is AppError.NewErrorType -> R.string.new_error_message
```

4. Add string resource in `feature/core/src/main/res/values/strings.xml`

## Toast Display

```kotlin
// In ViewModel - automatically maps AppError -> localized string -> toast
showErrorToast(appError)             // Red toast
showSuccessToast(R.string.saved)     // Green toast
showWarningToast(R.string.warning)   // Yellow toast
showInfoToast(R.string.info)         // Blue toast
```

Toast displayed via `ToastHost` provided by `TemplateTheme`.

