▩ 목 차 ▩
1. Live Data
1-1. Observer
1-2. LiveData
1-3. LiveData의 장점
1-4. LiveData 사용시 주의 할 점
1-5. LiveData 사용법
1-5-1. gradle
1-5-2. LiveData 객체 생성
1-5-3. Observer 객체 생성
2. LifeCycleOwner
3. observeForever
4. LiveData에 대한 생각
mvvm 디자인 패턴을 알기 위해서 LiveData를 알아야 하고 사용을 한다고 한다. 그래서 무엇인지도 모르고 일단 공부를 해본 내용을 적어보겠다.
■ 1. Live Data ■
Android JetPack 라이브러리의 하나의 기능이다. 쉽게 말해서 LiveData는 Data의 변경을 관찰 할 수 있는 Data Holder 클래스 이다.
일반적인 Observalble과 다르게 LiveData는 안드로이드 생명주기를 알고 있다.
■ 1-1. Observer
LiveData를 이용하지 않고 개발을 한다면, 데이터를 불러오거나, 추가, 삭제할때마다 매번 일일이 UI를 업데이트해줘야 한다는 문제가 있다.
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
// activity 설정 관련 소스 생략
DB에서 초기 아이템 목록 호출
UI 업데이트
추가 버튼 클릭 리스너 {
아이템 추가
UI 업데이트
}
삭제 버튼 클릭 리스너 {
아이템 삭제
UI 업데이트
}
}
}
또한 DB에서 아이템을 삭제하는 경우에, 삭제 후 UI 업데이트가 이루어져야 하는데 삭제 작업은 메인 스레드에서 이루어지지 않는 데다가 상대적으로 오래 걸리는 작업이기 때문에 UI 업데이트가 이루어진 후(업데이트가 되지 않은 후 ) 아이템 삭제가 되어 실제 화면에서는 삭제가 도지 않는 것처럼 보이는 문제가 있는것을 겪어 봤을 것이다.
==> 즉, 아이템 추가나 삭제 같은 데이터 변경을 요청했을때, 데이터 변경이 되는것까지 기다렸다가 데이터 변경이 완료되었다면 나한테 알려주는 것은 없을까 ?
Observer라는 목차를 썼는데 여기서 쓴 이유가 나온다.
==> 위에서 말한 내용 중 Observer의 역할은 데이터가 변경되는지 감시하고 있다가 UI 컨트롤러(Activity)에게 알려준다. 이 알려준 내용을 들은 UI 컨트롤러(Activity)는 그 데이터를 가지고 UI를 업데이트하는 그런 개념이다.
여기서 중요한 내용이 있는데, Obsever는 아무 데이터나 감시할 수 있는 게 아니라, LiveData라는 데이터 홀더 클래스가 가지고 있는 데이터만 감시 할 수 있다.
■ 1-2. LiveData
만약 키를 저장하는 변수 height의 값이 변경되는지 관찰하고 싶다고 하자.
var height = 183
MutableLiveData 객체를 생성하고 값을 넣을 때는 .value를 통해 넣어주면 된다.
이제부터 이 height 데이터는 Observer가 관찰 할 수 있는 데이터가 되는 것이다.
var height = MutableLiveData<Int>() // int형 값을 넣을 거라는 걸 명시해주어야 한다.
height.value = 183
우리는 LiveData를 배우려고 했지, MutableLiveData 클래스를 배우는 것은 아니라고 생각 할 것이다.
==> MutableLiveData 클래스는 LiveData를 상속받는 클래스이다.
public class MutableLiveData<T> extends LiveData<T> {
public MutableLiveData(T value) {
super(value);
}
public MutableLiveData() {
super();
}
@Override
public void postValue(T value) {
super.postValue(value);
}
@Override
public void setValue(T value) {
super.setValue(value);
}
}
MutableLiveData 클래스에 직접 들어가서 살펴보면 실제로 LiveData를 상속받는 클래스라는 것을 알 수 있고 postValue와 setValue 메서드를 추가한 클래스 인 것을 볼 수 있다.
==> 즉, MutableLiveData는 말 그대로 값의 수정이 가능하고 LiveData는 값의 수정이 불가능하다.
근데 이건 좀 아니지 않나..? 분명 LiveData를 사용하면 값의 변화를 감지할 수 있다고 했는데, 값의 수정이 불가능하다고 하면 애초에 값이 변화하질 않지 않나..?
==> 의존성에 초점을 둔 클린 아키텍처에 대해 알아보면 쉽게 파악할 수 있다고 한다.(사실 아직 배우질 않아서 그렇구나 하면서 넘어갔다. 추후에 배울 예정이다.)
예를 들어 아키텍처 패턴 중 하나인 mvvm 패턴을 사용한다고 하면 아래와 같은 구조를 가지게 될 것이다.
1. 웹에서 데이터를 가져오는 등(API 사용) 등의 값 변경이 일어난다.
2. MutableLiveData와 LiveData가 연결되어 있다.
3. 관찰자는 MutableLiveData가 아닌 LiveData를 관찰한다.
==> 즉, 데이터 변경이 일어나면 1-2-3 순서를 거쳐 관찰자가 변경되었다는 사실을 알게 되고 UI를 업데이트 한다.
여기서 MutableLiveData와LiveData와 연결안하고 바로 MutableLiveData에서 Observer로 바로 가도 되는데 이렇게 하는 이유는 UI 컨트롤러(액티비티, 프래그먼트)가 값을 직접 수정하지 못하게 하기 위해서이다.
또한 LiveData는 액티비티나, 프레그먼트 등과 같은 안드로이드 컴포넌트 생명주기(LifeCycle)를 알고 있다.
==> 그렇기에 LiveData는 활성상태(active)일때만 데이터를 업데이트(update)한다. 활성상태란 STARTED 또는 RESUMED를 의미한다.
또한 LiveData 겍체는 Observer 객체와 함께 사용된다.
==> LiveData가 가지고 있는 데이터에 어떠한 변화가 일어날 경우, LiveData는 등록된 Observer 객체에 변화를 알려주고, Observer의 onChanged()메소드가 실행한다.
■ 1-3. LiveData의 장점
- 데이터와 UI 상태 일치 보장 : LiveData는 데이터가 변경될 때마다 Observer 객체에게 알려주고, Obsever는 알림을 받을 때마다 대신 UI를 업데이트하므로 데이터와 UI 상태 일치를 보장한다.
- 메모리 누수 없음 : Observer는 Activity나 Fragment의 수명주기를 따르며 수명 주기가 끝나면 자동으로 삭제된다. 즉, 따로 메모리를 해제하거나 하는 작업을 하지 않아도 된다는 뜻이다.
- 중지된 활동으로 인한 비정상 종료 없음 : Activity나 Fragment가 백 스택에 있을 때 Observer는 비활성 상태가 되며, 이 때는 어떤 LiveData 이벤트도 수신받지 않는다.
- 수명주기를 수동으로 처리하지 않음 : UI 구성요소는 데이터를 관찰할 수 만 있고 관찰을 중지하거나 다시 시작하지 않는다. 대신 수명주기의 상태의 변경을 인식하기 때문에 이를 통해 자동으로 관리한다.
- 최신 데이터 유지 : 수명 주기가 비활성 -> 활성으로 다시 돌아올 때 최신 데이터를 수신한다.
- 적절한 구성 변경 : 기존 방식은 기기를 회전하기 전 savelnstanceState를 이용해 기존 데이터를 보관해두었다가 회전 후 데이터를 가져와 다시 쓰는 방식이었으나 LiveData를 사용하면 이런 작업을 하지 않아도 최신 데이터를 즉시 받게 된다.
- 리소스 공유 : LiveData 객체가 시스템 서비스에 한 번 연결되면 LiveData가 필요한 모든 곳에서(모든 Observer) LiveData 객체를 관찰할 수 있다.
■ 1-4. LiveData 사용시 주의 할 점
- Generic을 사용해 관찰하고자 하는 데이터의 타입(Type)을 갖는 LiveData 인스턴스를 생성한다.
(보통 LiveData 객체는 안드로이드 아키텍쳐 패턴의 ViewModel 클래스 내에서 함께 사용된다.) - LiveData 클래스의 observe() 메소드를 사용해 Observer 객체를 LiveData 객체에 "결합"한다.
이때, observer() 메소드는 LifecycleOwner 객체를 필요로 하며 보통 Activity를 전달한다.
LiveData에 저장된 데이터에 어떠한 변화가 일어난 경우 결합된 LifecycleOwner에 의해서 상태가 active(활성)인 한 모든 데이터에 대해 Trigger가 발생한다. - Observer 객체를 생성한다.
생성시 LiveData가 들고 있는 데이터가 변화가 일어났을 때 수행해야 할 로직이 들어있는 onChanbed() 메서드를 정의해야 한다.
보통은 액티비티나 프레그먼트 같은 UI Controller 내에서 해당 메서드를 생성한다. - observeForever(Observer)를 통해 LifeCycleOwner 없이 Observer를 생성하여 등록할 순 있지만, 이 경우에는 Observer는 항상 activie(활성) 상태이므로 데이터 변화에 대해 항상 전달을 받는다.
단, removeObserver(Observer)메소드를 통해 Observer를 제거 할 수 있다.
■ 1-5. LiveData 사용법
■ 1-5-1. gradle
ViewModel과 LiveData를 사용하기 위해 코드를 추가해준다.
( ViewModel은 LiveData와 같이 사용한다고 한다.
버전 정보는 https://developer.android.com/jetpack/androidx/releases/lifecycle?hl=ko#kts 에서 참고하자.)
def lifecycle_version = "2.3.0"
// ViewModel
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
// LiveData
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
■ 1-5-2. LiveData 객체 생성
객체를 생성하고 값을 변경하는 코드이다.
class BaeminRepository {
var _baeminNotice = MutableLiveData<자료형>() // MutableLiveData 객체 생성
// ViewModel에서 이 메서드를 호출하면 다음 페이지 공지사항을 불러온다.
fun loadBaeminNotice() {
.
.
.
(웹 사이트에서 공지사항을 불러오는 코드)
.
.
.
_baeminNotice.value = (불러온 공지사항) // 값의 변경이 일어난다.
}
}
ViewModel에서는 Repository 객체를 생성하고 MutableLiveData를 LiveData에 넣는다.
이때 보통 MutableLiveData 변수명 앞에는 _를 붙이고 LiveData는 붙이지 않는다.
( 위 예제에서는 _baeminNotice(MutableLiveData)와 baemeinNotice(LiveData)로 정해줌 )
==> 이렇게 이름을 짓는 이유는 같은 LiveData지만 값 수정이 가능한 라이브 데이터인지 아닌지 쉽게 구별하기 위한 것 같다.
class MainViewModel : ViewModel() {
// repository 객체 생성
private val baeminRepository = BaeminRepository()
// repository에 있는 MutableLiveData를 ViewModel의 LiveData에 넣는다.
private val baeminNotice: LiveData<자료형>
get() = baeminRepository._baeminNotice
fun loadBaeminNotice(){
baeminRepository.loadBaeminNotice() // repository에 있는 메서드를 호출함으로써 다음 공지사항을 불러온다.
}
fun getAll(): LiveData<자료형> {
return baeminNotice
}
}
■ 1-5-3. Observer 객체 생성
class MainActivity : AppCompatActivity() {
private val model: MainViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
.
.
.
// Observer를 생성해서 LiveData와 연결하는 작업
model.getAll().observe(this, Observer{ notice ->
// notice에 공지사항이 들어있음
})
.
.
.
// 버튼 누를때마다 ViewModel의 loadBaeminNotice가 호출하도록 해놓음
binding.btnLoadNextPage.setOnClickListener {
model.loadBaeminNotice()
}
}
}
데이터 변경을 감시하는 관찰자를 생성할 차례이다.
// Observer를 생성해서 LiveData와 연결하는 작업
model.getAll().observe(this, Observer{ notice ->
// notice에 공지사항이 들어있음
// 데이터의 변경이 이루어졌을 때 실행할 작업
})
위 코드를 보게되면, 생성한 관찰자 안에다가 데이터의 변경이 이루어질 때마다 하고 싶은 작업을 적어주면 된다.
==> 변경이 이루어질때마다 반복적으로 위의 람다 안에 코드가 실행될 것이다.
아까 맨 처음에 LiveData를 이용하지 않고 개발을 한다면, 데이터를 불러오거나, 추가, 삭제할때마다 매번 일일이 UI를 업데이트해줘야 한다는 문제가 있다고 했다.
==> LiveData를 이용한다면 '아이템 변경'된다면 이 코드가 실행되니 UI 업데이트 코드를 단 한곳에만 적어주면 되는 것이다.
또한 다른 스레드에서 오래 걸리는 작업을 하더라도 작업이 끝나면 알아서 알려주니 LiveData와 Observer를 사용함으로써 모든 문제가 해결한다.
또한 데이터 변경을 감시하는 관찰자를 생성할 때 쓰이는 this는 현재 Observer를 생성한 액티비티를 뜻한다.
==> Observer의 생명주기는 UI 컨트롤러(액티비티, 프래그먼트)의 생명주기를 따른다. 즉, 액티비티가 실행되면 관찰자도 감시를 시작하고 액티비티가 정지되면 관찰자도 감시를 중단한다.
데이터 변경을 감시하는 관찰자를 생성할 때의 코드를 보게 되면 notice 변수 안에 감시하고 있는 그 데이터 값이 들어있다.
람다식으로 표현했기 때문에 낯설 수 있다. 그냥 '원하는 변수 이름 ->' 이렇게 적으면 되는것이다.
■ 2. LifeCycleOwner ■
위에서 말했듯이 Observer는 UI 컨트롤러의 생명주기를 따른다.
==> 즉, Observer가 어떤 액티비티 혹은 어떤 프래그먼트의 수명주기를 따를 지 정해주어야 한다.
액티비티 같은 경우는 this 키워드를 이용해 현재 액티비티를 지정해주면 되는데 프래그먼트 경우에느는 this를 사용하면 안된다.
==> 프래그먼트에서 this를 사용하면 안되는 이유에 대해서 정리를 잘 정리해놓은 블로그가 있기 때문에 읽어보길 추천한다. 근데 너무 양이 많고 어렵긴 하다.. ( https://pluu.github.io/blog/android/2020/01/25/android-fragment-lifecycle/ )
간단하게 말해보자면,
- 기존의 프래그먼트 생명주기를 사용하면 복수의 Observer가 호출될 가능성이 있다.
- 이를 개선하기 위해 새로운 프래그먼트 생명주기가 도입되었다.
- this 대신에 viewLifecycleOwner를 사용하면 된다.
■ 3. observeForever ■
위에서 말했듯이 Observer는 UI 컨트롤러의 생명주기를 따른다.
- 액티비티가 실행되면 관찰자도 감시를 시작하며 알림을 받을 수 있는 상태가 되고,
- 액티비티가 정지되면 관찰자도 감시를 중단하며 알림을 받을 수 없는 상태가 된다.
신기하게 위의 LifeCycleOwner와 상관없이 항상 알림을 받을 수 있는 방법이 있는데 그게 바로 observerForever 메서드이다.
==> 이 observerForever 메서드를 사용하면 lifeCycleOwner가 없어도 관찰자를 생성하고 LiveData와 연결할 수 있으며 UI 컨트롤러의 생명주기와 상관없이 항상 알림을 받을 수 있는 상태가 된다.
// 일반적인 Observer 생성
model.getAll().observe(this, Observer{ notice ->
})
// observerForever를 통한 생성
model.getAll().observeForever(Observer{ notice ->
})
==> 이렇게 observe() 함수 대신 observerForever() 함수를 사용하면 되고 this를 넣어주지 않아도 된다.
대신 관찰자를 삭제할 때는 removeObserver 메서드를 사용하여 직접 삭제해주어야한다.
■ 4. LiveData에 대한 생각 ■
우리가 개발을 할 때 회전을 할 때는 우리가 지정한 값 말고 그 해당하는 액티비티 혹은 프래그먼트 값의 초기값으로 돌아가게 된다. 이것을 해결하기 위해서 onSaveInstanceState() 메소드가 호출이 되고 destroy되기 전에 Bundle에 저장을 하고 넘겨주는 방법을 썼다.
==> LiveData를 이용한다면 더 편리하게 이러한 문제를 해결 할 수 있을 것이다.
또한 데이터 변경과 화면 처리를 한번에 처리하는 것 보다 LiveData를 통해 이를 분리하게 되면 전체 로직이 간단해져 유지보수가 쉬워진다고 생각이 되었고, 이런 것들 때문에 viewmodel과 함께 mvvm 패턴에서 필수로 사용되는지 어느정도 감이 잡히었다.
'디자인패턴(MVC,MVP,MVVM)' 카테고리의 다른 글
Android MVVM 패턴 적용하기( feat. AAC[DataBinding, LiveData, ViewModel, RoomDB], Coroutine ) 적용해 메모장 만들기 (0) | 2022.12.14 |
---|---|
AAC의 ViewModel 이란? (0) | 2022.11.18 |
데이터 바인딩(Data binding) (0) | 2022.11.16 |
뷰 바인딩(view binding) (0) | 2022.11.16 |
MVC vs MVP vs MVVM (0) | 2022.11.10 |