12.1 Coil

Knižnica Coil slúži na načítanie obrázkov z internetu a ich zobrazenie v JetPack Compose aplikácii. Načítavanie obrázkov z internetu má isté zákutia, ktoré Coil rieši ako napríklad:

  1. Chýbajúce pripojenie k internetu
  2. Obrázok je nedostupný
  3. GET request pre obrázok môže vyžadovať dodatočné HTTP hlavičky alebo autentifikáciu
  4. Obrázok sa môže dlho sťahovať
  5. Obrázok môže byť priveľký na zobrazenie (Android má dosť výrazné obmedzenia na veľkosti zobrazovaných obrázkov)
  6. Caching obrázkov
  7. a podobne

Potrebuješ tieto závislosti:

dependencies {
    implementation("io.coil-kt.coil3:coil-compose:3.6.1")
    implementation("io.coil-kt.coil3:coil-network-okhttp:3.6.1") // Android only
}

a happy flow spôsob pre načítanie obrázku z internetu je:

AsyncImage(
    model = "https://example.com/image.jpg",
    contentDescription = null,
)

V Kotlin Multiplaform potrebuješ namiesto OkHttp použiť Ktor. Pozri si tento návod.

Globálna konfigurácia Coil

Ak chceš nastaviť správanie všetkých, z internetu načítaných, obrázkov, potrebuješ globálnu konfiguráciu Coil. Tu je ideálne použiť vlastnú Application triedu, ktorá bude implementovať rozhranie SingletonImageLoader.Factory.

Vytvorenie vlastnej Application triedy

  1. V balíku, kde máš svoju MainActivity triedu pridaj novú Kotlin triedu s názvom <App>Application (App nahraď krátkym názvom svojej aplikácie.).
  2. <App>Application nech rozširuje triedu Application
  3. V súbore AndroidManifest.xml nastav svoju <App>Application triedu ako android:name atribút v elemente <application>
// SsndDemoApplication.kt

import android.app.Application

class SsndDemoApplication : Application() {

}
<!-- AndroidManifest.xml -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    <application
        android:name=".SsndDemoApplication"
        android:allowBackup="true"
        android:dataExtractionRules="@xml/data_extraction_rules"
        ...

Implementácia rozhrania SingletonImageLoader.Factory

V globálnej konfigurácii sa toho dá nastaviť kopec. Zvykneme tu nastavovať prácu s disk/memory cache pre rýchlejšie znovunačítanie obrázkov a podobne. My sa teraz venujme iba obrázkom, ktoré sa zobrazia v prípade chýb (hoci toto nie je štandardný use-case):

  1. placeholder - obrázok, ktorý sa zobrazí, keď sa obrázok načítava
  2. error - obrázok, ktorý sa zobrazí, keď sa obrázok nepodarilo načítať
  3. fallback - obrázok, ktorý sa zobrazí, keď je načítavaný obrázok prázdny (null)
class SsndDemoApplication : Application(), SingletonImageLoader.Factory {

    override fun newImageLoader(context: Context): ImageLoader {
        return ImageLoader.Builder(context)
            .crossfade(true)
            .placeholder(getDrawable(R.drawable.ic_image_loading)?.asImage())
            .error(getDrawable(R.drawable.ic_image_error)?.asImage())
            .fallback(getDrawable(R.drawable.ic_image_fallback)?.asImage())
            .build()
    }

}

Vlastné handlovanie stavu načítania obrázkov

Ak chceš namiesto statickej ikony v prípade loadingu zobraziť vlastný komponent, AsyncImage ti nepomôže. Môžeš použiť rememberAsyncImagePainter, ktorý ti umožňuje získať stav aktuálne načítavaného obrázka. Zápis je pomerne zložitý, preto je vhodné vytvoriť si vlastnú Composable funkciu, ktorá toto správanie zakryje.

val sizeResolver = rememberConstraintsSizeResolver()

val painter = rememberAsyncImagePainter(
    model = ImageRequest.Builder(LocalPlatformContext.current)
        .data("https://example.com/image.jpg")
        .size(sizeResolver)
        .build()
)

val painterState by painter.state.collectAsState()

when(painterState) {
    is AsyncImagePainter.State.Loading -> Text("Loading...")
    is AsyncImagePainter.State.Success -> Image(painter = painter, contentDescription = null)
    is AsyncImagePainter.State.Error -> Text("Error")
    else -> Text("Unknown")
}

Úprava HTTP requestu

Predvolene sa každé HTTP volanie pre získanie obrázkov volá ako štandardný HTTP GET request na danú url. Ak chceme upraviť metódu alebo hlavičky, môžeme tak spraviť cez ImageRequest.Builder nasledovne:

val request = ImageRequest.Builder(LocalContext.current)
    .data("url")
    .httpMethod("POST")
    .httpBody(
        NetworkRequestBody(("{\"user\":\"id\"}".toByteArray().toByteString()))
    )
    .httpHeaders(NetworkHeaders.Builder()
        .add("Authorization", "Bearer TOKEN")
        .build())
    .build()

// pouzitie v painter
val painter = rememberAsyncImagePainter(request)

// pouzitie v AsyncImage
AsyncImage(model = request, contentDescription = null)