[android/kotlin] viewmodel 이해하기

남윤희·2023년 10월 16일

Design Pattern

목록 보기
2/3
post-thumbnail

#Android에서의 ViewModel 무엇이고 왜 사용하는가

ViewModel의 이점

인스턴스 상태 저장 메커니즘을 사용하여 데이터를 저장하지 않을 경우 해당 데이터가 소멸되는데 ViewModel은 데이터 지속성을 위한 편리한 API를 제공하여 이 문제를 해결한다고한다.

  • UI 상태를 유지 할 수 있다.
  • 비즈니스 로직에 대한 액세스 권한을 제공한다.
  • ViewModel은 ViewModel이 보유하는 상태와 ViewModel이 트리거하는 작업에서 모두 지속성을 허용한다. 이러한 캐싱을 통해 화면 회전과 같은 일반적인 구성 변경에도 데이터를 다시 가져올 필요가 없다.

예제를 통해 공부해보자..

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"
    android:paddingHorizontal="10dp"
    >

    <TextView
        android:id="@+id/number_textview"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="0"
        android:textSize="30dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <EditText
        android:id="@+id/number_input_edittext"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:inputType="number"
        app:layout_constraintHorizontal_weight="1"
        app:layout_constraintEnd_toStartOf="@+id/plus_btn"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/number_textview"
        android:layout_marginTop="30dp"
        />

    <Button
        android:id="@+id/plus_btn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="더하기"
        app:layout_constraintEnd_toStartOf="@+id/minus_btn"
        app:layout_constraintStart_toEndOf="@+id/number_input_edittext"
        app:layout_constraintTop_toTopOf="@+id/number_input_edittext"
        android:layout_marginHorizontal="10dp"
        />

    <Button
        android:id="@+id/minus_btn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="빼기"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toEndOf="@+id/plus_btn"
        app:layout_constraintTop_toTopOf="@+id/number_input_edittext" />


</androidx.constraintlayout.widget.ConstraintLayout>
  • RecyclerView 의 아이템으로 들어갈 data class.

ViewModel


enum class ActionType {
  PLUS, MINUS
}

//데이터의 변경
// 뷰모델은 데이터의 변경사항을 아렬주는 라이브 데이터를 가지고있다.

class MyNumberViewModel(
  private val coroutineScope: CoroutineScope =
      //SupervisorJob(): SupervisorJob은 부모-자식 관계의 코루틴 설정
      // 만약 자식 코루틴 중 하나가 실패시, 다른 자식 코루틴은 영향을 받지 않고 계속 실행.
      CoroutineScope((SupervisorJob() + Dispatchers.Main.immediate))
) : ViewModel() {

  //mutablelivedata - 수정 가능
  //livedata - 값 변경X

  //내부에서 설정하는 변경가능한 자료형은 Mutable로
  private val _currentValue = MutableLiveData<Int>()

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

  //사용자의 입력값
  private val _currentUserInput = MutableLiveData<String>()

  val currentUserData : LiveData<String>
      get() = _currentUserInput

  //ViewModel이 시작될 때 초기값 설정
  init {
      Log.d("ViewModel", "nyh 생성자 호출")
      _currentValue.value = 0
  }

  fun updateValue(actionType: ActionType, input: Int) {
      when (actionType) {
          ActionType.PLUS ->
              _currentValue.value = _currentValue.value?.plus(input)
          ActionType.MINUS ->
              _currentValue.value = _currentValue.value?.minus(input)
      }
      this._currentUserInput.value = ""
  }

  override fun onCleared() {
      coroutineScope.cancel()
  }
}

Activity




class MainActivity : AppCompatActivity(), View.OnClickListener {

    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).get(MyNumberViewModel::class.java)
        myNumberViewModel.currentValue.observe(this, Observer {
            Log.d("Activity", "nyh 라이브 데이터 값 변경 $it")
           binding.numberTextview.text = it.toString()
        })
        myNumberViewModel.currentUserData.observe(this, Observer { changeUserInput ->
            binding.numberInputEdittext.setText(changeUserInput)
            Log.d("Activity", "nyh 라이브 데이터 값 변경 $changeUserInput")
        })
        binding.plusBtn.setOnClickListener(this)
        binding.minusBtn.setOnClickListener(this)
    }

    //클릭
    override fun onClick(view: View?) {
        // 비동기 작업을 lifecycleScope에서 실행
        lifecycleScope.launch {

            val userInputString = binding.numberInputEdittext.text.toString()
            if (userInputString.isEmpty()) {
                return@launch
            }
            val userInputNumber =
                binding.numberInputEdittext.text.toString().toInt()

            when (view) {
                binding.plusBtn ->
                    myNumberViewModel.updateValue(actionType = ActionType.PLUS, userInputNumber)

                binding.minusBtn ->
                    myNumberViewModel.updateValue(actionType = ActionType.MINUS, userInputNumber)
            }
        }

    }
}
profile
안드로이드 주니어 개발자

1개의 댓글

comment-user-thumbnail
2023년 10월 17일

정리가 잘되어있어서 보기 편리했어요!

답글 달기