본문 바로가기
Jetpack Library

[Android JetPack] ViewModel 이란? ( + ViewModelFactory)

by JongSeok 2023. 1. 3.

이번에 알아볼 ViewModel은 이전 포스팅에서 공부했던 MVVM 디자인 패턴의 구성요소 중 하나인 ViewModel입니다.

이해를 돕기 위해 예제와 함께 작성하겠습니다.

이전 포스팅

 

[안드로이드] MVVM 디자인 패턴

디자인 패턴 디자인 패턴이란 개발을 보다 체계적이고 효율적인 유지·보수를 위해 큰 틀에서 '이러이러한 형식으로 작성하자'라는 일종의 약속으로 볼 수 있습니다. 안드로이드 개발에 사용되

develop-oj.tistory.com


ViewModel을 적용하지 않은 경우

class MainActivity : AppCompatActivity() {
    private lateinit var binding: ActivityMainBinding
    var count = 0

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        binding.numberOutput.text = count.toString()
        // 증가 버튼 클릭
        binding.plusButton.setOnClickListener {
            count++
            binding.numberOutput.text = count.toString()
        }
        // 감소 버튼 클릭
        binding.minusButton.setOnClickListener {
            count--
            binding.numberOutput.text = count.toString()
        }
    }
}

ViewModel을 적용하지 않고 프로그램을 구현하더라도 정상적으로 작동합니다.

하지만, 액티비티에 UI, 데이터, 이벤트를 처리하는 모든 로직이 들어가기 때문에 프로그램 규모가 커지면서 기능을 추가·수정하기 매우 어려워지게 됩니다.


ViewModel을 적용한 경우

먼저 app수준의 build.gradle에 종속성을 추가합니다.

// by viewModels()를 이용하기 위한 종속성
implementation 'androidx.activity:activity-ktx:1.4.0'
implementation 'androidx.fragment:fragment-ktx:1.4.1'
// ViewModel
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1'

 

그리고 증가, 감소에 따른 count를 관리하는 MainViewModel 클래스를 생성합니다.

MainViewModel

class MainViewModel : ViewModel() {
    var count : Int = 0
}

MainActivity

class MainActivity : AppCompatActivity() {
    private lateinit var binding: ActivityMainBinding
    private val viewModel: MainViewModel by viewModels()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        binding.numberOutput.text = viewModel.count.toString()
        // 증가 버튼 클릭
        binding.plusButton.setOnClickListener {
            viewModel.count++
            binding.numberOutput.text = viewModel.count.toString()
        }
        // 감소 버튼 클릭
        binding.minusButton.setOnClickListener {
            viewModel.count--
            binding.numberOutput.text = viewModel.count.toString()
        }
    }
}

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/number_output"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="-"
        android:textAppearance="@style/TextAppearance.AppCompat.Large"
        android:textSize="30sp"
        android:textStyle="bold"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <Button
        android:id="@+id/minus_button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="100dp"
        android:layout_marginTop="32dp"
        android:text="감소"
        android:textSize="18sp"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/number_output" />

    <Button
        android:id="@+id/plus_button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="32dp"
        android:layout_marginEnd="100dp"
        android:text="증가"
        android:textSize="18sp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/number_output" />

</androidx.constraintlayout.widget.ConstraintLayout>

ViewModel을 적용한 MainActivity입니다.

 

액티비티에서 viewModel 객체를 생성하여 액티비티의 ViewModel이 해당 액티비티의 생명주기를 따르도록 합니다.

이후, count 값에 접근할 때에는 viewModel.count 를 이용합니다.


ViewModelFactory

위의 예시에서는 ViewModel객체를 생성할 때 매개변수가 없는 경우를 공부했습니다.

하지만 ViewModel을 사용하다 보면 인스턴스나 매개변수를 넘겨주면서 객체를 생성하는 경우가 많습니다.

이러한 경우에 ViewModelFactory를 사용합니다.

ViewModelFactory

class MainViewModelFactory(private val count : Int) : ViewModelProvider.Factory {
    override fun <T : ViewModel> create(modelClass: Class<T>): T {
        if (modelClass.isAssignableFrom(MainViewModel::class.java)) {
            return MainViewModel(count) as T
        }
        throw IllegalArgumentException("ViewModel class not found")
    }
}

파라미터를 건내받을 MainViewModelFactory 클래스를 생성합니다.

 

cf. 본인같은 경우 MainViewModelFactory 클래스에서

"Inheritance from an interface with '@JvmDefault' members is only allowed with -Xjvm-default option"

오류가 발생했습니다. dependencies의 버전 오류 추정하나 app수준 build.gradle에 

android {
    kotlinOptions {
        freeCompilerArgs += [
                "-Xjvm-default=all",
        ]
    }
}

을 추가하여 해결했습니다.

 

MainActivity

class MainActivity : AppCompatActivity() {
    private lateinit var binding: ActivityMainBinding
    val factory = MainViewModelFactory(10)  // viewModel에 건내줄 초기값
    private val viewModel: MainViewModel by viewModels() { factory }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        binding.numberOutput.text = viewModel.count.toString()
        // 증가 버튼 클릭
        binding.plusButton.setOnClickListener {
            viewModel.count++
            binding.numberOutput.text = viewModel.count.toString()
        }
        // 감소 버튼 클릭
        binding.minusButton.setOnClickListener {
            viewModel.count--
            binding.numberOutput.text = viewModel.count.toString()
        }
    }
}

이용할 파라미터로 Factory를 먼저 생성한 후 ViewModel 객체를 생성할 때 Factory를 건내주는 방식입니다.

실행 시 Factory를 생성할 때 설정한 10으로 count가 초기화된 것을 볼 수 있습니다. 

728x90
반응형