#1 ViewModel 속에 LiveData가 있는 샘플 앱
위 게시글의 완성된 앱을 토대로 코드를 수정한다.
#2 명시적 LiveData.observe()
이 LiveData가 '살아있을 수' 있는 이유는 LiveData.observe() 덕분이다. LiveData.observe()는 첫번째 인자로 MainActivity, Fragment, Service의 생명주기를 받고, 두번째 인자로는 LiveData 값의 변경 시 수행할 작업을 받는다. 따라서, 프로그래머는 LiveData의 갯수만큼 LiveData.observe()를 명시적으로 정의해주어야 한다. 번거롭다. 이 observe() 정의 과정이 암시적으로 수행된다면 편할텐데 말이다.
#3 암시적 LiveData.observe()
#3-1 데이터 바인딩 라이브러리
다행히, 안드로이드 데이터 바인딩 라이브러리는 LiveData.observe()의 암시적 정의를 지원한다. 다음 코드를 보자.
#3-2 기존의 명시적 LiveData.observe() 코드
...
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private lateinit var viewModel: MainActivityViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
viewModel = ViewModelProvider(this).get(MainActivityViewModel::class.java)
binding.myViewModel = viewModel
viewModel.getCurrentCount().observe(this, Observer {
binding.countText.text = it.toString()
})
}
}
#1에 있는 샘플 앱의 코드다. 안드로이드 데이터 바인딩 라이브러리와 함께, LiveData.observe()로 '명시적 관찰'을 하고 있다.
#3-3 데이터 바인딩 객체는 ViewDataBinding 클래스다
package androidx.databinding;
...
/**
* Utility class to create {@link ViewDataBinding} from layouts.
*/
public class DataBindingUtil {
...
}
DataBindingUtil의 소스 코드 상단에는 Utility class to create ViewDataBinding from layouts라고 적혀있다. 즉 #3-2의 DataBindingUtil.setContentView(this, R.layout.activity_main)가 ViewDataBinding 클래스의 객체를 만들기 위한 코드였다는 소리다. 이는 데이터 바인딩 라이브러리가 제공하는 강력한 암시적 지원들의 구심점이 바로 ViewDataBinding 클래스라는 이야기도 된다. 그렇다면, 이제 ViewDataBinding 클래스의 소스 코드를 보겠다.
#3-4 ViewDataBinding 클래스 살펴보기
package androidx.databinding;
...
public abstract class ViewDataBinding extends BaseObservable implements ViewBinding {
...
/**
* Method object extracted out to attach a listener to a bound LiveData object.
*/
private static final CreateWeakListener CREATE_LIVE_DATA_LISTENER = new CreateWeakListener() {
@Override
public WeakListener create(
ViewDataBinding viewDataBinding,
int localFieldId,
ReferenceQueue<ViewDataBinding> referenceQueue
) {
return new LiveDataListener(viewDataBinding, localFieldId, referenceQueue)
.getListener();
}
};
...
/**
* Track the LifecycleOwner set in {@link #setLifecycleOwner(LifecycleOwner)}. Set as
* Object so that the class can be compiled without requiring the LifecycleOwner dependency.
*/
private LifecycleOwner mLifecycleOwner;
...
/**
* Sets the {@link LifecycleOwner} that should be used for observing changes of
* LiveData in this binding. If a {@link LiveData} is in one of the binding expressions
* and no LifecycleOwner is set, the LiveData will not be observed and updates to it
* will not be propagated to the UI.
*
* When using Data Binding with Fragments, make sure to use Fragment.getViewLifecycleOwner().
* Using the Fragment as the LifecycleOwner might cause memory leaks since the Fragment's
* Lifecycle outlives the view Lifecycle.
* When using Data Binding with Activities, you can use the Activity as the LifecycleOwner.
*
* @param lifecycleOwner The LifecycleOwner that should be used for observing changes of
* LiveData in this binding.
*/
@MainThread
public void setLifecycleOwner(@Nullable LifecycleOwner lifecycleOwner) {
if (lifecycleOwner instanceof Fragment) {
Log.w("DataBinding", "Setting the fragment as the LifecycleOwner might cause"
+ " memory leaks because views lives shorter than the Fragment. Consider"
+ " using Fragment's view lifecycle");
}
if (mLifecycleOwner == lifecycleOwner) {
return;
}
if (mLifecycleOwner != null) {
mLifecycleOwner.getLifecycle().removeObserver(mOnStartListener);
}
mLifecycleOwner = lifecycleOwner;
if (lifecycleOwner != null) {
if (mOnStartListener == null) {
mOnStartListener = new OnStartListener(this);
}
lifecycleOwner.getLifecycle().addObserver(mOnStartListener);
}
for (WeakListener<?> weakListener : mLocalFieldObservers) {
if (weakListener != null) {
weakListener.setLifecycleOwner(lifecycleOwner);
}
}
}
...
private static class LiveDataListener implements Observer,
ObservableReference<LiveData<?>> {
final WeakListener<LiveData<?>> mListener;
// keep this weak because listeners might leak, we don't want to leak the owner
// see: b/176886060
@Nullable
WeakReference<LifecycleOwner> mLifecycleOwnerRef = null;
public LiveDataListener(
ViewDataBinding binder,
int localFieldId,
ReferenceQueue<ViewDataBinding> referenceQueue
) {
mListener = new WeakListener(binder, localFieldId, this, referenceQueue);
}
...
@Override
public void setLifecycleOwner(@Nullable LifecycleOwner lifecycleOwner) {
LifecycleOwner previousOwner = getLifecycleOwner();
LifecycleOwner newOwner = lifecycleOwner;
LiveData<?> liveData = mListener.getTarget();
if (liveData != null) {
if (previousOwner != null) {
liveData.removeObserver(this);
}
if (newOwner != null) {
liveData.observe(newOwner, this);
}
}
if (newOwner != null) {
mLifecycleOwnerRef = new WeakReference<LifecycleOwner>(newOwner);
}
}
@Override
public WeakListener<LiveData<?>> getListener() {
return mListener;
}
@Override
public void addListener(LiveData<?> target) {
LifecycleOwner lifecycleOwner = getLifecycleOwner();
if (lifecycleOwner != null) {
target.observe(lifecycleOwner, this);
}
}
...
@Override
public void onChanged(@Nullable Object o) {
ViewDataBinding binder = mListener.getBinder();
if (binder != null) {
binder.handleFieldChange(mListener.mLocalFieldId, mListener.getTarget(), 0);
}
}
}
...
}
setLifecycleOwner의 주석을 보면, public 메소드인 setLifecycleOwner()로 ViewDataBinding 객체에 LifecycleOwner를 등록해주어야 비로소 LiveData의 '관찰(observe)'이 시작된다고 한다. 즉 LiveData.observe()가 암시적으로 수행된다는 얘기다. 그 전체적인 흐름은 다음과 같다.
#3-5 ViewDataBinding 클래스에서 LiveData.observe()가 암시적으로 설정되는 흐름
target.observe(lifecycleOwner, this)에서 this는 단일 메소드 인터페이스인 Observer의 구현 클래스다 (이 링크의 #2-3 참조). 그 구현 클래스는 말 그대로 this(LiveDataListener)다. LiveDataListener의 클래스 정의 부분을 보면 Observer를 implement하고 있으므로, LiveDataListener를 그대로 넣어도 되는 것이다. 따라서 LiveDataListener는 Observer의 단일 메소드인 onChanged()도 구현하고 있고, LiveData가 변경될 때마다 해당 onChanged()의 내용이 실행된다.
#4 암시적 LiveData.observe() 사용하기
#4-1 MainActivity.kt에서 데이터 바인딩 객체에 생명주기 설정하기
...
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private lateinit var viewModel: MainActivityViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
viewModel = ViewModelProvider(this).get(MainActivityViewModel::class.java)
binding.myViewModel = viewModel
binding.lifecycleOwner = this
/*
viewModel.getCurrentCount().observe(this, Observer {
binding.countText.text = it.toString()
})
*/
}
}
ViewDataBinding 객체인 'binding'에 LifecycleOwner를 등록해주면 #3에서 소개한 과정이 알아서 진행된다. 따라서, 명시적으로 작성했던 LiveData.observe() 구문은 제거한다.
#4-2 activity_main.kt 수정
<?xml version="1.0" encoding="utf-8"?>
<layout ...>
<data>
<variable
name="myViewModel"
type="com.example.implicitobservation.MainActivityViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout ...>
<Button ... />
<TextView
...
android:text="@{(myViewModel.getCurrentCount().toString())}"
... />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
간단한 수정.
#5 요약
(안드로이드 데이터 바인딩 객체) + (생명주기) = (알아서 작동하는 LiveData)
#6 완성된 앱
'깨알 개념 > Android' 카테고리의 다른 글
[Android] MVVM 구조 한눈에 보기 (0) | 2024.01.22 |
---|---|
[Android] LiveData - 양방향 데이터 바인딩의 3가지 방법 (0) | 2024.01.20 |
[Android] ViewModel - View에 객체(ViewModel) 전달 (0) | 2024.01.18 |
[Android] LiveData - 기초 (0) | 2024.01.16 |
[Android] ViewModel - 뷰 모델에 인자(Argument) 전달 (ViewModelFactory) (0) | 2024.01.15 |