Android-Programmierung: LiveData
Willemers Informatik-Ecke
Wenn sich die Daten einer App verändern, hat das meist auch Auswirkungen auf die Elemente des Displays. Beispielsweise könnte ein Device wie etwa ein GPS-Sensor die Position ändern, die dann natürlich sofort Auswirkungen auf das Display haben soll, in diesem Fall ein Fragment.

Das Fragment könnte natürlich dauernd fragen, ob es eine Änderung der Variablen gibt. Schöner wäre es, wenn sich die Variable bei dem Fragment meldet, wenn sie geändert wird. Dazu muss sich das Fragment in einer Liste eintragen, die die Setter-Methode der Variablen für Interessierte führt.

Ein solches Vorgehen ist als Observer-Muster bekannt.

Im folgenden Beispiel wird die Änderung einer ganzzahligen Variable durch einen Button simuliert.

class MainViewModel(application: Application) : AndroidViewModel(application)  {
    var ticks = MutableLiveData<Int>(8)
    fun increment() {
        (ticks.value!! + 1).also { ticks.value = it }
    }
}
Das Fragment meldet sich mit dem Aufruf von observe bei der LiveData-Komponente an. Der Observer implementiert das Setzen der TextView.
class GameFragment : Fragment() {
   ...
    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        viewModel.ticks.observe(viewLifecycleOwner, Observer {
            binding.txtTime.text = "Es sind ${it} Ticks"
        })
        _binding = FragmentGameBinding.inflate(inflater, container, false)
        val view = binding.root
        binding.btLeave.setOnClickListener {
            viewModel.increment()
        }
        return view
    }
Etwas komplizierter wird es, wenn die LiveData im Hintergrund verändert werden. Sie können aus dem Scope der Coroutine (der aus dem laufenden Thread heraus) nicht einfach Variablen verändern. Dazu verwenden Sie den Aufruf von postValue, das für eine Synchronisation sorgt.
class MainViewModel(application: Application) : AndroidViewModel(application)  {
    ...
    var ticks = MutableLiveData<Int>(8)
    ...
    // Die Coroutine für den Timertick
    private var timerJob: Job? = null

    fun startTimerTick() {
        timerJob = CoroutineScope(Dispatchers.IO).launch {
            while (true) {
                ticks.postValue(ticks.value!! + 1)
                delay(1000) // 1 Sekunde warten
            }
        }
    }
Löst nun der Button ein Click-Ereignis aus, ruft er startTimerTick. Das wird eine Coroutine starten, die den Wert von tichs dauernd hochzählt. Damit das nicht zu schnell passiert, wird mit der Funktion delay 1000 Millisekunden gewartet, was genau einer Sekunde entspricht.