[안드로이드] ViewModel과 Livedata

LeeEunJae·2022년 9월 13일
1

Study Kotlin

목록 보기
14/20
post-custom-banner

해당 게시물은 혼자 공부하면서 배운 내용을 토대로 작성한 글이므로 틀린 내용이 있을 수 도 있습니다. 틀린 내용이 있을 경우 지적해주시면 감사하겠습니다.

ViewModel과 LiveData를 이용해 간단한 덧셈 뺄셈 연산을 하고 UI에 결과를 업데이트 하는 앱을 만들어 봤습니다.

📌 실행 화면

📌 ViewModel 이란?

ViewModel 은 Android Jetpack 의 구성요소 중 하나로 소프트웨어 개발 디자인 패턴 중 하나인 MVVM(Model-View-ViewModel) 에서 파생되었다.
ViewModel 은 View 로부터 독립적이고, View가 필요로하는 데이터를 가지고 있다.
앱 개발시 MVVM 패턴을 적용하면, UI 컨트롤러 (Activity, Fragment) 의 과도한 책임을 분담해서 코드가 방대해지는 것을 방지하고, 프로그램을 유지/보수 하는데 용이하다.


위 그림을 보면 Activity 의 생명주기와 ViewModel 의 생명주기를 같이 볼 수 있다.
ViewModel 은 Activity 나 Fragment 가 완전히 종료되기 전까지 메모리 상에 존재한다.

Activity rotated(화면이 회전 되었을 때) 앱이 onPause 상태에서 다시 시작하는 것을 확인 할 수 있다. 하지만, ViewModel 은 여전히 메모리 상에 남아 있다.

📌 LiveData 란?

LiveData 라는 데이터 홀더 클래스가 가지고 있는 데이터가 변경되는지 감시하고 변경되면 UI 컨트롤러에게 알려준다.
데이터 변경을 감시하는 역할은 LiveData가 가지고 있는 Observer(관찰자) 라는 녀석이 해준다.

📌의존성 추가

ViewModel 을 사용하기 위해서 다음 의존성을 추가 해야한다.

def lifecycle_version = "2.4.0"
implementation "androidx.lifecycle:lifecycle-viewmodel:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"

📌activity_main.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/numberTextView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="30sp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <EditText
        android:id="@+id/inputEditText"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toStartOf="@id/plusButton"
        app:layout_constraintTop_toBottomOf="@id/numberTextView"
        android:layout_marginStart="5dp"
        android:layout_marginEnd="5dp"
        android:layout_marginTop="10dp"/>

    <androidx.appcompat.widget.AppCompatButton
        android:id="@+id/plusButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="더하기"
        app:layout_constraintTop_toBottomOf="@id/numberTextView"
        app:layout_constraintEnd_toStartOf="@id/minusButton"
        android:layout_marginEnd="10dp"
        android:layout_marginTop="10dp"/>

    <androidx.appcompat.widget.AppCompatButton
        android:id="@+id/minusButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="빼기"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toBottomOf="@id/numberTextView"
        android:layout_marginEnd="10dp"
        android:layout_marginTop="10dp"/>

    <TextView
        android:id="@+id/testValueTextView"
        android:layout_width="300dp"
        android:layout_height="wrap_content"
        app:layout_constraintTop_toBottomOf="@id/plusButton"
        app:layout_constraintStart_toStartOf="parent"
        android:layout_marginTop="20dp"
        android:layout_marginStart="10dp"/>

</androidx.constraintlayout.widget.ConstraintLayout>

📌 ViewModel

ViewModel() 을 상속받는 클래스를 생성해줍니다.
이곳에서 View 의 데이터를 관리합니다.


import android.util.Log
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel

enum class ActionType{
    PLUS, MINUS
}


// 데이터의 변경
// 뷰모델은 데이터의 변경사항을 알려주는 라이브 데이터를 가지고 있음
class MyNumberViewModel: ViewModel(){
    // 뮤터블 라이브 데이터 - 수정 가능
    // 라이브 데이터 - 수정 불가능

    companion object{
        private const val TAG = "로그"
    }

    private val _currentValue = MutableLiveData<Int>()

    // LiveData 데이터로 리스트를 사용할 수도 있는지 테스트 해보기 위해서 작성했습니다.
    private val _testValue = MutableLiveData<MutableList<Int>>()
    private val testList = mutableListOf<Int>()

    // 변경되지 않는 데이터를 가져 올 때 이름을 언더바(_) 없이 설정
    // 공개적으로 가져오는 변수는 private 이 아닌 퍼블릭으로 외부에서도 접근 가능하도록 설정
    // 하지만 값을 직접 라이브데이터에 접근하지 않고 뷰모델을 통해 가져올 수 있도록 설정
    val currentValue : LiveData<Int>
        get() = _currentValue

    val testValue : LiveData<MutableList<Int>>
        get() = _testValue

    // 초기값 설정
    init{
        Log.d(TAG, "MyNumberViewModel - 생성자 호출")
        _currentValue.value = 0
    }

    // 뷰모델이 가지고 있는 값을 변경시키는 메서드
    fun updateValue(actionType: ActionType, input: Int){
        // testList 에 input 을 추가하고
        testList.add(input)
        // _testValue 를 대체합니다.
        _testValue.value = testList
        
        when(actionType){
            // 인자로 들어온 actionType 을 확인
            ActionType.PLUS ->
                _currentValue.value = _currentValue.value?.plus(input) 
            ActionType.MINUS ->
                _currentValue.value = _currentValue.value?.minus(input)

        }
    }
}

📌 MainActivity.kt

ViewModelProvider를 통해 뷰 모델을 생성하고 currentValue 에 observer 를 등록해서 데이터가 변경되는지 감시합니다.


import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import android.view.View
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import com.dldmswo1209.jetpacktest.databinding.ActivityMainBinding

class MainActivity : AppCompatActivity(), View.OnClickListener {
    companion object{
        private const val TAG = "로그"
    }

    lateinit var myNumberViewModel: MyNumberViewModel
    private val binding by lazy { ActivityMainBinding.inflate(layoutInflater) }
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(binding.root)

        // 뷰모델 가져오기
        myNumberViewModel = ViewModelProvider(this)[MyNumberViewModel::class.java]

        // LiveData 의 관찰자 observe
        myNumberViewModel.currentValue.observe(this, Observer{
            Log.d(TAG, "MainActivity - myNumberViewModel - currentValue 라이브 데이터 값 변경 : $it")
            binding.numberTextView.text = it.toString()
        })

        myNumberViewModel.testValue.observe(this, Observer {
            Log.d(TAG, "MainActivity - myNumberViewModel - testValue 라이브 데이터 값 변경 : $it")
            binding.testValueTextView.text = it.toString()
        })
        binding.plusButton.setOnClickListener(this)
        binding.minusButton.setOnClickListener(this)


    }

    override fun onClick(view: View?) {
        val userInput = binding.inputEditText.text.toString().toInt()

        when(view){
            binding.plusButton->
                myNumberViewModel.updateValue(actionType = ActionType.PLUS, userInput)
            binding.minusButton->
                myNumberViewModel.updateValue(actionType = ActionType.MINUS, userInput)
        }
    }
}

👀 출처 및 참고자료

https://www.youtube.com/watch?v=-b0VNKw_niY
https://developer.android.com/topic/libraries/architecture/viewmodel

profile
매일 조금씩이라도 성장하자
post-custom-banner

0개의 댓글