๊นจ์•Œ ๊ฐœ๋… ๐Ÿ“‘/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 ์™„์„ฑ๋œ ์•ฑ

 

android-practice/live-data/ImplicitObservation at master · Kanmanemone/android-practice

Contribute to Kanmanemone/android-practice development by creating an account on GitHub.

github.com