14.2 Ktor Client - prípady použitia

CoroutineScope a GET request

Všetky volania HttpClient-a potrebujeme volať v takzvanom coroutine scope, aby neblokovali používateľské rozhranie. Pozrime si ukážku priamo pre GET request na IP adresu zo stránky https://api.ipify.org:

// ocakavana odpoved z BE
@Serializable
data class IpResponse(val ip: String)

// ziskaj coroutine scope, pre pracu na samostatnom threade
val coroutineScope = rememberCoroutineScope()

Button(onClick = {
    // spusti pracu na pozadi
    coroutineScope.launch {
        val response = client                         // pouzi globalne definovaneho klienta
            .get("https://api.ipify.org?format=json") // zavolaj GET request na tuto url
            .body<IpResponse>()                       // odpoved preved na IpResponse objekt
    }
}) { Text("Click me") }

// response = IpResponse(ip=123.123.123.123)

POST request

Pre testovanie použijeme globálne dostupné API, ktoré simuluje pridanie poznámky do databázy.

@Serializable
data class AddTodoRequest(
    val todo: String,
    val completed: Boolean,
    val userId: Int
)

@Serializable
data class AddTodoResponse(
    val id: Int,
    val todo: String,
    val completed: Boolean,
    val userId: Int,
)

val response = client.post("https://dummyjson.com/todos/add") {
    setBody(AddTodoRequest(todo = "Click me", completed = false, userId = 1))
    contentType(ContentType.Application.Json)
}.body<AddTodoResponse>()

// response = AddTodoResponse(id=255, todo=Click me, completed=false, userId=1)

Základné ošetrovanie chýb

Chyby samozrejme môžu nastať a vyriešiť ich môžeme klasickým try-catch blokom:

try {
    val response = client.get(/**/)
} catch (e: ClientRequestException) {
    // chyba requestu na strane klienta (400vky)
} catch (e: ServerResponseException) {
    // server odpovedal chybou (500vky)
} catch (e: SerializationException) {
    // mas zle nastavenu serializaciu
} catch (e: IOException) {
    // pravdepodobne chyba internetoveho pripojenia
}

Pokročilé techniky odchytávania chýb

Ja mám radšej vlastnú sealed triedu a pomocnú funkciu:

sealed interface Error : ApiResult<Nothing> {
    data class ServerError(val error: ServerResponseException) : Error
    data class ClientError(val error: ClientRequestException) : Error
    data class SerializationError(val error: SerializationException) : Error
    object NoInternet : Error
    data class Unknown(val error: Throwable) : Error
}

fun Throwable.toApiError(): Error = when (val e = this) {
    is CancellationException -> throw e
    is ClientRequestException -> Error.ClientError(e)
    is ServerResponseException -> Error.ServerError(e)
    is SerializationException -> Error.SerializationError(e)
    is IOException -> Error.NoInternet
    else -> Error.Unknown(e)
}

try {
    val response = client.get(/**/)
} catch (e: Exception) {
    val error = e.toApiError()
}