Result Wrappers

Coroutines, Error Handling and Result Wrappers

A suspend Retrofit 43,944 call returns the body or throws: HttpException for a 4xx or 5xx status, IOException when no answer arrives, SerializationException when the body doesn't match the DTO. Rather than scatter try/catch through every caller, BookNest converts them once into a sealed type, like LoadState (UI State with Sealed Classes):

data/remote/ApiResult.kt: every way a call can end, as a valueKotlin
sealed interface ApiResult<out T> {
  data class Success<T>(val data: T) : ApiResult<T>
  data class HttpError(val code: Int) : ApiResult<Nothing>            // the server said no
  data class NetworkError(val cause: IOException) : ApiResult<Nothing> // no answer at all
}
suspend fun <T> apiCall(block: suspend () -> T): ApiResult<T> = try {
  ApiResult.Success(block())
} catch (e: HttpException) {                     // a 4xx or 5xx status
  ApiResult.HttpError(e.code())
} catch (e: IOException) {                       // offline, refused, timed out
  ApiResult.NetworkError(e)
}                                                // CancellationException passes through

The compiler makes every when over the result handle all three cases. Never catch Exception here: cancellation is a CancellationException, and swallowing it keeps a cancelled screen's work running (runCatching has the same flaw). Serialization errors crash on purpose: they are DTO bugs, not user conditions. With the test server answering 503, and then stopped, the repository logged:

Output of 156
W/BookNestNet: refresh failed: HTTP 503
...
W/BookNestNet: offline: java.net.ConnectException: Failed to connect to /10.0.2.2:8616

Put retries where the user or system asks for them (a Retry button, WorkManager 234 's backoff in Constraints and Chaining), not in a hidden loop inside every call.