개발/Android

ViewModel, LiveData

Debin 2022. 6. 1.
반응형

ViewModel

  • ViewModel은 Activity 및 Fragment의 생명주기를 고려하여 UI 관련 데이터를 저장하고 관리한다.
  • Activity는 Activity가 끝날 때까지 그리고 Fragment는 Fragment가 분리될 때까지 데이터를 메모리에서 유지한다.
  • Activity 및 Fragment의 생명 주기와 관계없이 데이터를 유지한다.
  • 화면 회전과 같이 구성을 변경 할때도 데이터를 유지할 수 있다.

ViewModel lifecycle

  • Activity의 onCreate()메서드에 처음 호출할 때 ViewModel을 요청 및 생성한다.
  • 시스템은 액티비티 기간 내내(예: 기기 화면이 회전될 때) onCreate() 메서드를 여러 번 호출한다.
  • ViewModel은 액티비티가 소멸될 때까지 존재한다.

ViewModel LifeCycle

LiveData

  • LiveData는 Observable data holder class, 즉 데이터를 저장하고 변화를 관찰할 수 있는 객체다.
  • UI객체는 LiveData에 옵저버(observer)를 등록할 수 있으며, 데이터가 변경될 때 UI 갱신한다.
  • LiveData가 보유하고 있는 데이터에 변화가 일어나면, LiveData는 등록된 옵저버 객체에 변화를 알려주고, 옵저버 객체의 onChanged() 메서드를 실행한다.
  • LiveData는 컴포넌트의 생명주기를 알고 있기 때문에 액티비티가 onStart(), onResume() 상태일 때는 콜백을 실행하고, 다른 액티비티로 넘어가는 onStop()일 때는 실행하지 않는다.
  • 즉, 컴포넌트들의 생명주기 상태가 activity(활성화) 상태일 때만 data에 대한 update를 제공한다.

livedata update 시기

LiveData 객체 생성

  • LiveData는 객체 뿐만 아니라 모든 데이터와 함께 사용할 수 있는 래퍼(wrapper) 객체로서, ViewModel 객체 에 저장되며, getter/setter 메서드를 통해 액세스를한다.

LiveData 형식

  1. MutableLiveData: 값의 변경/읽기 모두 가능한 LiveData (게터, 세터 모두 사용 가능)
    • MutableLiveData 형식의 LiveData를 변경할 때는 setValue(), postValue() 메서드를 사용한다.
    • setValue(): Main 스레드에서 즉시 값을 변경하고, 옵저버로 데이터 변경을 알려준다.
    • postValue(): 백그라운드 스레드에서 라이브데이터를 변경하고 싶을 때 사용한다. 
      내부적으로 핸들러를 통해 Main Looper 보내기 때문에 실제 변경은 Main 스레드에서 처리한다고 한다.
  2. LiveData: 값의 변경이 불가능하여 읽기만 가능한 LiveData (게터만 사용 가능)

LiveData 객체 Observe

  • LiveData는 데이터가 변경될 때만 액티비티의 관찰자에게 업데이트를 전달한다.
  1. ViewModel 가져오기
  2. LiveData에 변화가 발생했을 LiveData를 가져와서 처리할 로직을 정의하는 Observer 객체를 생성한다.
  3. observe() 메서드를 사용하여 LiveData 객체에 Observer 객체를 연결한다.
    • observe(LifecycleOwner, Observer)
    • LifeCycle: 컴포넌트의 생명주기 상태정보를 가지는 객체
    • LifeCycleOwner: LifeCycle 객체에 컴포넌트의 생명주기 상태정보를 제공하는 객체
      Activity에서는 곧 자신이기 때문에 this, Fragment는 viewLifecycleOwner로 전달,
      LiveData는 LifecycleOwner를 통해 해당 View의 Lifecycle을 관찰한다.
    • Observer: 변경된 LiveData를 수신하여 처리할 로직을 정의하는 인터페이스로 LiveData 객체를 구독하여 변경사항의 알림을 받는 객체다.

예제코드

이제 예제 코드로 본격적인 사용법을 살펴보자.

아래는 layout viewmodel_examl.xml 코드다.

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/counter_tv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="80sp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <com.google.android.material.floatingactionbutton.FloatingActionButton
        android:id="@+id/fab_add"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginEnd="16dp"
        android:layout_marginBottom="16dp"
        android:clickable="true"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:srcCompat="@drawable/ic_add_black_24dp" />

    <com.google.android.material.floatingactionbutton.FloatingActionButton
        android:id="@+id/fab_remove"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginBottom="16dp"
        android:clickable="true"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:srcCompat="@drawable/ic_remove_black_24dp" />

</androidx.constraintlayout.widget.ConstraintLayout>

다음은 사용할 ViewModel 클래스다.

class ViewModelMainActivity : AppCompatActivity() {

    private val binding by lazy{
        ViewmodelExamBinding.inflate(layoutInflater)
    }

    //ViewModel 가져오기
    //ViewModelProvider(context).get(ViewModel class)
    private val viewModel by lazy{
        ViewModelProvider(this).get(CounterViewModel::class.java)
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(binding.root)

        val counterObserver = Observer<Int>{
            binding.counterTv.text = it.toString()
        }

        viewModel.getCounter().observe(this,counterObserver)

        init()
    }

    fun init(){
        with(binding){
            binding.fabAdd.setOnClickListener{
                viewModel.increaseOrDecreaseValue(1)
            }
            binding.fabRemove.setOnClickListener{
                viewModel.increaseOrDecreaseValue(-1)
            }
        }
    }
}

마지막은 액티비티 클래스다.

class ViewModelMainActivity : AppCompatActivity() {

    private val binding by lazy{
        ViewmodelExamBinding.inflate(layoutInflater)
    }

    //ViewModel 가져오기
    //ViewModelProvider(context).get(ViewModel class)
    private val viewModel by lazy{
        ViewModelProvider(this).get(CounterViewModel::class.java)
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(binding.root)

        val counterObserver = Observer<Int>{
            binding.counterTv.text = it.toString()
        }

        viewModel.getCounter().observe(this,counterObserver)

        init()
    }

    fun init(){
        with(binding){
            binding.fabAdd.setOnClickListener{
                viewModel.increaseOrDecreaseValue(1)
            }
            binding.fabRemove.setOnClickListener{
                viewModel.increaseOrDecreaseValue(-1)
            }
        }
    }
}

그럼 이제 아래와 같이 에뮬레이터가 동작하고 에뮬레이터의 화면을 좌우로 돌려도 값이 변하지 않는 것을 확인할 수 있다.

 

이상으로 포스팅을 마칩니다. 감사합니다!

 

참고자료

https://developer.android.com/topic/libraries/architecture/viewmodel?hl=ko

반응형

댓글