깨알 개념/Android

[Android] LiveData - 암시적으로 '관찰'하기

interfacer_han 2024. 1. 19. 15:11

#1 ViewModel 속에 LiveData가 있는 샘플 앱

 

[Android] ViewModel - View에 객체(ViewModel) 전달

#1 개요 #1-1 Data Binding과 ViewModel [Android] Data Binding - View에 객체 전달 #1 객체 전달의 필요성 #1-1 이전 글 Data Binding - 기초 #1 데이터 바인딩 사용 전 #1-1 예시 앱 위과 같은 간단한 앱이 있다. Button을

kenel.tistory.com

위 게시글의 완성된 앱을 토대로 코드를 수정한다.
 

#2 명시적 LiveData.observe()

 

[Android] LiveData - 기초

#1 개요 #1-1 LiveData LiveData 개요 | Android 개발자 | Android Developers LiveData를 사용하여 수명 주기를 인식하는 방식으로 데이터를 처리합니다. developer.android.com LiveData. 문서적인 정의는 Data의 변경을 관

kenel.tistory.com

이 LiveData가 '살아있을 수' 있는 이유는 LiveData.observe() 덕분이다. LiveData.observe()는 첫번째 인자로 MainActivity, Fragment, Service의 생명주기를 받고, 두번째 인자로는 LiveData 값의 변경 시 수행할 작업을 받는다. 따라서, 프로그래머는 LiveData의 갯수만큼 LiveData.observe()를 명시적으로 정의해주어야 한다. 번거롭다. 이 observe() 정의 과정이 암시적으로 수행된다면 편할텐데 말이다.
 

#3 암시적 LiveData.observe()

#3-1 데이터 바인딩 라이브러리

 

아키텍처 구성요소에 레이아웃 뷰 결합  |  Android 개발자  |  Android Developers

아키텍처 구성요소에 레이아웃 뷰 결합 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. AndroidX 라이브러리에는 성능이 뛰어나고 테스트와 유지관리가 쉬운

developer.android.com

다행히, 안드로이드 데이터 바인딩 라이브러리는 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 완성된 앱

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