깨알 개념/Android

[Android] LiveData - 기초

interfacer_han 2024. 1. 16. 14:34

#1 개요

#1-1 LiveData

 

LiveData 개요  |  Android 개발자  |  Android Developers

LiveData를 사용하여 수명 주기를 인식하는 방식으로 데이터를 처리합니다.

developer.android.com

LiveData. 문서적인 정의는 Data의 변경을 관찰할 수 있는 데이터 홀더 클래스고, 말 그대로 해석하면 살아있는(live) 데이터(data)다. Activity, Fragment, Service에 있는 Data, 예를 들어 어떤 변수 A가 있고 그 A를 어떤 TextView에 표시해놓은 상황을 생각해 보자. A의 값이 변경되면, TextView에 변경된 A값을 다시 할당해야한다. LiveData는 그 귀찮은 할당 과정을 프로그래머가 생략할 수 있게 만들어준다. LiveData는 변경된 A의 값을 화면(View)에 암묵적으로 업데이트한다. 초기 설정만 마쳐놓으면 그 이후로는 'binding.text = getA().toString()'와 같은 코드를 자동으로 수행한다는 얘기다. 알아서 변경된 자신의 값을 View에 갱신한다니, 말 그대로 데이터가 살아있다고 표현할 만하다.
 

#1-2 LiveData를 접목시켜볼 앱

 

[Android] View Model - 기초

#1 View Model의 필요성#1-1 예제버튼을 누르면 TextView의 text가 1씩 증가하는 예시 앱이다. MainActivity.kt 코드는 다음과 같다. // package com.example.viewmodelbasics import androidx.appcompat.app.AppCompatActivity import andro

kenel.tistory.com

이제, 위 게시글의 '#3 완성된 앱'이 LiveData를 사용하게 수정해본다. 아래에 있는 모든 코드는 이 앱의 코드를 기반으로 수정한 것이다.
 

#2 LiveData 사용하기

#2-1 build.gradle.kts (Module :app) 수정

plugins {
    ...
}

android {
    ...
}

dependencies {
    ...

    val lifecycle_version = "2.5.1"
    implementation("androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version") // LiveData
}

먼저, LiveData 추상클래스를 불러오기 위한 라이브러리를 추가한다. 모듈 수준 gradle에 위와 같은 구문을 추가한다. 해당 구문은 여기에서 가져온 구문이다.
 

#2-2 MainActivityViewModel.kt 수정

...

import androidx.lifecycle.MutableLiveData

...

class MainActivityViewModel : ViewModel() {
    private var count : MutableLiveData<Int> = MutableLiveData(0)

    fun getCurrentCount(): MutableLiveData<Int> {
        return count
    }

    fun updateCount(){
        count.value = (count.value)?.plus(1)
    }
}

변수 count를 Int형이 아니라, MutableLiveData<Int>형으로 변경한다. 클래스 MutableLiveData는 추상클래스인 LiveData의 subclass다. MutableMap과 Map의 관계와 같다. 추상클래스는 객체를 생성할 수 없기에 MutableLiveData의 객체로 만든 것이다.
 
getCurrentCount()은 반환형을 수정한다. getUpdatedCount()는 updateCount()로 이름을 바꾸고 내용도 변경한다. count.value += 1과 같이 짧게 적을 수 없는 이유는 count.value가 null값일 가능성이 있기 때문이다. 따라서 null-safe 연산을 수행하는 plus() 함수를 사용한다. count.value가 null이라면 .plus(1)은 호출되지 않을 것이다.
 
MainActivity.kt를 수정하기 전에 LiveData가 어떻게 '살아있을(Live) 수 있는지' 알아본다.
 

#2-3 LiveData.observe() 살펴보기

...

public abstract class LiveData<T> {

    ...

    @MainThread
    public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) {
        assertMainThread("observe");
        if (owner.getLifecycle().getCurrentState() == DESTROYED) {
            // ignore
            return;
        }
        LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer);
        ObserverWrapper existing = mObservers.putIfAbsent(observer, wrapper);
        if (existing != null && !existing.isAttachedTo(owner)) {
            throw new IllegalArgumentException("Cannot add the same observer"
                    + " with different lifecycles");
        }
        if (existing != null) {
            return;
        }
        owner.getLifecycle().addObserver(wrapper);
    }

    ...

}

당연히, LiveData가 정말로 살아있을리는 없다. 그렇다면 LiveData는 어떻게 변경된 자신의 값을 View에 갱신하는가? 바로 LiveData.observe() 메소드에 의해서다. 'observe'라는 단어 정의 그대로, LiveData의 변화를 '관찰'하는 메소드다. 이 메소드는 첫번째 인자로 LifecycleOwner, 두번째 인자로 Observer를 받는다.
 
LifecycleOwner 인터페이스는 단일 메소드 인터페이스다. 이는 Activity, Fragment, Service와 같이 생명주기(Lifecycle)를 소유한(Owner) 클래스 전부가 구현(implement)하고 있는 인터페이스다. 이 인터페이스의 단일 메소드는 LifecycleOwner.getLifecycle(): LifeCycle로 생명주기를 반환한다. 이 인자의 존재 이유는, LivaData의 메모리 누수 방지를 위함이다. LiveData의 목적은 변경된 자신의 값을 View에 갱신하는 것이다. 따라서, View에 LiveData가 노출되는 상태가 아니라면, 굳이 갱신을 하며 자원낭비를 할 필요가 없다. 마치 숨겨진 TextView에 값을 표시하는 것과 같은 행위기 때문이다. 그리고 View에 LiveData가 노출되고있는지 아닌지는 생명주기를 참조(Lifecycle.getCurrentState())해 알 수 있다.
 
두번째 인자인 Observer 인터페이스 또한 단일 메소드 인터페이스다. 그 단일 메소드는 Observer<T>.onChanged(value: T)로, LiveData 객체의 데이터 변경 시 발생하는 작업을 정의한다.
 
이 LiveData.observe()를 MainActivity.kt에서 구현해보겠다.
 

#2-4 MainActivity.kt 수정

...

import androidx.lifecycle.Observer

...

class MainActivity : AppCompatActivity() {

    ...
    
    override fun onCreate(savedInstanceState: Bundle?) {
    
        ...
        
        val countObserver = object : Observer<Int> {
            override fun onChanged(newValue: Int) {
                binding.countText.text = newValue.toString()
            }
        }
        viewModel.getCurrentCount().observe(this, countObserver)
        binding.countButton.setOnClickListener {
            viewModel.updateCount()
        }
    }
}

LiveData.observe()의 첫번째 인자로 LifecycleOwner를 이미 구현하고 있는 MainActivity 자체(this)를 넣는다. 두번째 인자로는 Observer의 구현 클래스를 넣으면 된다. getUpdatedCount() 메소드는 updateCount() 메소드로 이름과 기능을 바꿨으므로, binding.countButton의 setOnClickListener { ... } 블록의 내용도 그에 맞춰 수정한다.
 
Observer 구현 클래스 부분의 가독성이 약간 거슬리므로, 

/*
val countObserver = object : Observer<Int> {
    override fun onChanged(newValue: Int) {
        binding.countText.text = newValue.toString()
    }
}
viewModel.getCurrentCount().observe(this, countObserver)
*/

viewModel.getCurrentCount().observe(this, Observer {
    binding.countText.text = it.toString()
})

와 같이 익명 클래스(클래스 정의와 객체화를 동시에 하는 클래스)를 사용해 코드를 줄인다.
 
이제 count의 값이 변경되어도 그 값을 내가 View에 명시적으로 재할당할 필요가 없다. LiveData.observe()가 암시적으로 재할당해줄 것이기 때문이다. 프로젝트의 크기가 작은 현재 게시글의 앱에서는 크게 와닿지 않지만, 프로젝트의 크기가 커질수록 LiveData의 이점이 체감될 것이다.
 

#3 요약

LiveData는 onChangeListener()의 세련된 사용이다.

#4 완성된 앱

https://github.com/Kanmanemone/android-practice/tree/master/live-data/LiveDataBasics