ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 안드로이드 LiveData
    안드로이드 학습/Android 기술면접 대비 2024. 2. 6. 11:56

    안드로이드 공부를 하면서 LiveData를 봤던 것이 JetPack과 아키텍처 패턴이다. 

     

    LiveData는 JetPack의 AAC에 포함되어 있어서 봤었고, 아키텍처 패턴에서는 MVVM에서 LiveData와 DataBinding과 함께 유용하게 사용된다고 해서 봤었다. 

     

    그래서 한번 LiveData에 대해 공부해보았다. 

    LiveData란 ?

    LiveData는 Android JetPack 중 observable data holder class. 일반적인 observable 과는 다르게, LiveData는 안드로이드의 4대 컴포넌트들에 포함되어 있는 Activity, Fragment, Service의 생명주기를 인지한다. 

     

    LiveData는 활성상태(active)일때만 데이터를 업데이트(update)한다. 활성상태란 STARTED 또는 RESUMED를 의미한다.

     

    비활동 상태에는 LiveData는 데이터의 변화를 알리지 않습니다. (Destroyed 된 상태의 lifecycle 에서는 LiveData 객체를 Observe하지 않습니다.)

     

    또한 LiveData 객체는 Observer 객체와 함께 사용된다. LiveData가 가지고 있는 데이터에 어떠한 변화가 일어날 경우, LiveData는 등록된 Observer 객체에 변화를 알려주고, Observer의 onChanged() 메서드가 실행되게 된다.

     

    observable이란??

    더보기
    observable을 찾아보니 주로 RxJava에서 사용하는 개념같았다.

     

    RxJava의 가장 핵심적인 요소는 Observable이다. Observable은 데이터 흐름에 맞게 알림을 보내 Observer가 데이터를 사용할 수 있도록 한다. 즉, Observable을 이용해 데이터를 회수하고 변환하는 메커니즘을 정의하고, Observer는 이를 구독해 데이터가 준비되면 이에 반응한다.

     

    안드로이드에서 Observable을 사용하면 데이터의 변화를 감지하고, 이에 반응하여 UI를 업데이트하거나 다른 작업을 수행할 수 있습니다. 주로 네트워크 호출, 데이터베이스 쿼리, 사용자 입력 등과 같은 비동기적인 작업을 처리하는 데 유용합니다.

     

    LiveData 장점

    • Data와 UI간 동기화
      • LiveData는 Observer 패턴을 따릅니다. 그에따라 LiveData는 안드로이드 생명주기에 데이터 변경이 일어날 때마다 Observer 객체에 알려줍니다. 그리고 이 Observer 객체를 사용하면 데이터의 변화가 일어나는 곳마다 매번 UI를 업데이트하는 코드를 작성할 필요 없이 통합적이고 확실하게 데이터의 상태와 UI를 일치시킬 수 있습니다.
    • 메모리 누수(Memory Leak)가 없습니다.
      • Observer 객체는 안드로이드 생명주기 객체와 결합되어 있기 때문에 컴포넌트가 Destroy 될 경우 메모리상에서 스스로 해제합니다.
    • Stop 상태의 액티비티와 Crash가 발생하지 않습니다.
      • Activity가 Back Stack에 있는 것처럼 Observer의 생명주기가 inactive(비활성화) 일 경우, Observer는 LiveData의 어떤 이벤트도 수신하지 않습니다.
    • 생명주기에 대한 추가적인 handling을 하지 않아도 됩니다.
      • LiveData가 안드로이드 생명주기에 따른 Observing을 자동으로 관리를 해주기 때문에 UI 컴포넌트는 그저 관련 있는 데이터를 "관찰"하기만 하면 됩니다.
    • 항상 최신 데이터를 유지합니다.
      • 화면 구성이 변경되어도 데이터를 유지합니다.
        예를 들어, 디바이스를 회전하여 세로에서 가로로 화면이 변경될 경우에도 LiveData는 회전하기 전의 최신 상태를 즉시 받아옵니다.
    • 자원(Resource)를 공유할 수 있습니다.
      • LiveData를 상속하여 자신만의 LiveData클래스를 구현할 수 있고 싱글톤 패턴을 이용하여 시스템 서비스를 둘러싸면(Wrap) 앱 어디에서나 자원을 공유 할 수 있습니다.

     

    LiveData 사용법 :

    1. 특정 유형의 데이터를 보유할 LiveData의 인스턴스를 생성합니다. 이 작업은 일반적으로 ViewModel 클래스 내에서 이루어집니다.

    class NameViewModel : ViewModel() {
        private val _currentName: MutableLiveData<String> = MutableLiveData("천재")
        val currentName: LiveData<String> = _currentName
    }

     

    위에 보면 MutableLiveData와 LiveData를 사용한다.

    • MutableLiveData : 값의 get/set 모두를 할 수 있다.
    • LiveData : 값의 get()만을 할 수 있다.

    Mutable은 코틀린 언어에서 흔하게 볼 수 있는 내용인데, 값의 get/set 모두를 허용하기에, 값의 변경과 읽기 모두 가능하다. 반대로 LiveData는 읽기 전용의 데이터를 만들기에 값의 변경은 불가능하다.

     

    이런 기법은 ViewModel과 View의 역할을 분리하기 위함이라 이렇게 사용한다. ViewModel은 언제나 새로운 값의 변경이 일어나고, 다시 읽을 수 있는 형태로 사용하는 것이고, View는 값의 입력이 아닌 읽기만을 허용하는 것이다.

     

    -> 그래서 View에서는 LiveData<T>로 된 데이터만 접근해서 수정이 안되게 막고, ViewModel에서는 MutableLiveData<T>로 접근해서 수정과 읽기 모두 가능하게 사용하는 것 같다. 

     

    그렇다고 해서 항상 View에서는 LiveData만 봐야 하는 것은 아니다. 필요에 따라 View에서도 값의 변경이 일어나야 하는 케이스라면 허용할 수 있는데, checkBox의 값을 변경하는 케이스라면 가능하다. 상황에 잘 맞게 써야 한다는 말이다.

     

     

    2. onChanged() 메서드를 정의하는 Observer 객체를 만듭니다. 이 메서드는 LiveData 객체가 보유한 데이터 변경 시 발생하는 작업을 제어합니다. 일반적으로 Activity이나 Fragment 같은 UI 컨트롤러에 Observer 객체를 만듭니다.

    object : Observer<String> {
        override fun onChanged(value: String) {
            textView.text = value
        }
    }

     

    3. observe() 메서드를 사용하여 LiveData 객체에 Observer 객체를 연결합니다. observe() 메서드는 LifecycleOwner 객체를 사용합니다. 이렇게 하면 Observer 객체가 LiveData 객체를 구독하여 변경사항에 관한 알림을 받습니다. 일반적으로 활동이나 프래그먼트와 같은 UI 컨트롤러에 Observer 객체를 연결합니다.

    class MainActivity : AppCompatActivity() {
    
        private lateinit var viewModel: NameViewModel
    
    	... 생략 ...
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_main)
    
    		... 생략 ...
    
            viewModel = ViewModelProvider(this)[NameViewModel::class.java]
    
            viewModel.currentName.observe(this,  object : Observer<String> {
                override fun onChanged(value: String) {
                   textView.text = value
                }
            })
    
        }
    }

     

    위에 예제에서는 MainActivity의 LifeCycleOwner를 넘겨줘서 LiveData의 생명주기가 MainActivity의 생명주기를 따른다. 

     

    LiveData는 항상 UI로 처리한다.

     

    LiveData는 항상 MainThread로 값을 처리한다. UI를 업데이트하지 않으면서 LiveData를 활용하는 경우는 좋은 방법이 아니다.(X)

    • ViewModel보다 더 안쪽인 Repository(UseCase)와 같이 내부에서 LiveData로 값을 가져오는 경우
    • View 업데이트가 없는 코드에서 LiveData를 활용하는 경우

     

    LiveData에서 데이터를 업데이트 할때 하는 방식이 2가지가 있다. 

    • setValue
    • postValue

     

    setValue()

    setValue()는 Main Thread에서 LiveData의 값을 변경해준다. setValue() 호출로 데이터 변경후 getValue() 함수로 값을 읽어오면 변경된 값을 가져올 수 있다.

    setValue()는 Main Thread 에서 값을 dispatch 하기 때문에 백그라운드에서 setValue()를 호출한다면 오류가 나게 된다. setValue()가 동작하지 않는다면 호출되고 있는 Thread를 살펴봐야한다.

     

    postValue()

    postValue() background에서 값을 변경한다. background thread(worker thread)에서 동작하다가 main thread에 값을 post 하는 방식으로 사용된다. 함수 내부적으로는 아래와 같은 코드가 실행된다. 

     

     

    내가 이해한 바로는 네트워크 통신 worker thread 같은 환경에서 데이터를 바꿔야 한다면 postValue()

    그냥 main thread에서 작업을 하고 있다면 setValue()를 사용하라는 것 같다. 

     

     

     

    테스트 코드 예제 :

     

    1. MainActivity.kt

    더보기
    class MainActivity : AppCompatActivity() {
    
        private lateinit var viewModel: NameViewModel
    
        lateinit var textView: TextView
        lateinit var editText: EditText
        private lateinit var changeBtn: Button
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_main)
    
            textView = findViewById(R.id.textView)
            editText = findViewById(R.id.editText)
            changeBtn = findViewById(R.id.changeBtn)
    
            viewModel = ViewModelProvider(this)[NameViewModel::class.java]
    
            viewModel.currentName.observe(this,  object : Observer<String> {
                override fun onChanged(value: String) {
                   textView.text = value
                }
            })
    
            changeBtn.setOnClickListener {
                viewModel.changeName(editText.text.toString())
            }
    
        }
    }

    2.  NameViewModel.kt

    더보기
    class NameViewModel : ViewModel() {
    
        // String 타입의 LiveData 생성
    
        /**
         * MutableLiveData는 mutable하고, LiveData는 immutable하기 떄문이다.
         * 즉, LiveData는 immutable하기에 thread-safe 하지만 값을 변경할 수 없어서 MutableLiveData를 통해 값을 변경하고 LiveData는 해당 값을 immutable하게 받는 것이다.
         */
        private val _currentName: MutableLiveData<String> = MutableLiveData("천재")
        val currentName: LiveData<String> = _currentName
    
    
        fun changeName(name: String){
            if(name == ""){
                return
            }
            _currentName.value = name
        }
    }

     

Designed by Tistory.