5.2 MutableState a rekompozícia

Všimol si si, že Composable funkcie sa volajú funkcie? Kto ale túto funkciu vlasne spustí? Odpoveďou je samozrejme JetPack Compose. Problém je, že my chceme aby JetPack Compose zavolal funkciu, keď niečo spravíme my. Existovať tak musí mechanizmus, akým JetPack Compose poprosíme o znovuprekreslenie obrazovky s novými hodnotami.

Znovuprekresleniu hovoríme rekompozícia. Tú JetPack Compose vykoná automaticky, keď sa zmení stav (State) aplikácie. State si môžeš jednoducho predstaviť ako špeciálne označenú premennú, ktorú JetPack Compose rešpektuje. Ukážme si rovno, ako vytvoriť takúto premennú:

setContent {
    
    // premenna intState je typu MutableState
    // MutableState je podtriedou triedy State
    // premennu intState inicializujeme s hodnotou 0
    val intState: MutableState<Int> = mutableStateOf(0)
    
    Button(onClick = { intState.value++ }) {
        Text("Click me ${intState.value}")
    }
}
  1. Defaultná hodnota premennej intState je na začiatku 0 (mutableStateOf(0))
  2. Po kliknutí na tlačidlo sa hodnota (value) premennej intState inkrementuje z 0 na 1
  3. Keďže sa zmenil State (premenná typu MutableState), JetPack Compose znovu zavolá funkciu setContent
  4. Premenná intState je 1 preto sa text na tlačidle zmení na Click me 1

Skoro správne, ale pri zavolaní funkcie setContent sa premenná intState v skutočnosti vytvorí odznova a opäť bude mať hodnotu 0. Preto potrebujeme ešte JetPack Compose označiť, ktoré premenné nám nemá prepisovať. To zabezpečíme pomocou funkcie remember nasledovne:

setContent {
    val intState = remember { mutableStateOf(0) }
    
    Button(onClick = { intState.value++ }) {
        Text("Click me ${intState.value}")
    }
}

Zbavme sa .value

Kotlin poskytuje tzv. delegáty, ktoré nám v tomto konkrétnom prípade pomôžu zbaviť sa .value zápisov. Stačiť nám bude slovíčko by a správne importy:

import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue

// ...
setContent {
    
    // zmena "val" na "var" a pouzitie "by"
    var intState by remember { mutableStateOf(0) }
    
    Button(onClick = { intState++ }) {
        Text("Click me $intState")
    }
}

Kedysi sa mi stávalo, že Android Studio nedokázalo doplniť importy a musel som ich pridávať manuálne, preto som ich sem radšej pridal. Za normálnych okolností by malo stačiť

  1. nabehnúť kurzorom klávesnice na riadok s intState
  2. stlačiť Alt + Enter a vybrať Import operator State.getValue
  3. stlačiť Alt + Enter druhýkrát a vybrať Import operator State.setValue

Level Expert

Ak nechceš dookola zapisovať by remember { mutableStateOf(???) }, môžeš si napísať vlastnú globálnu funkciu, ktorá obe tieto operácie (remember a mutableStateOf) zhrnie do jednej funkcie:

@Composable
fun<T> rememberMutableStateOf(initialValue: T): MutableState<T> = remember { mutableStateOf(initialValue) }

a potom ju použiť takto:

var intState by rememberMutableStateOf(0)

Button(onClick = { intState++ }) {
    Text("Click me $intState")
}

Bežne to nerobím, ale je pekné myslieť na skrátenie často používaných zápisov.