View와 ViewModel

최대환·2023년 11월 19일
0

ViewModel이란?

ViewModel 개요
활동 수명 주기에 관한 이해

안드로이드 앱 개발을 배우면서, '생명 주기(LifeCycle)'라는 단어에 자주 마주치게 됩니다. 이 생명 주기는 사용자가 앱을 사용하는 동안 화면을 돌리거나, 앱을 잠시 나갔다가 다시 들어오는 등 다양한 상황에서 변하게 됩니다. 이런 변화에 따라 데이터를 관리하는 것이 중요한데, 여기서 'ViewModel'이라는 개념이 등장합니다.
ViewModel은 생명 주기가 변해도 변하지 않는, 쉽게 말해 '변치 않는 데이터 보관소' 같은 역할을 합니다.
ViewModel은 화면 회전이나, 재설정 등에도 파괴되지 않으므로 데이터를 안전하게 관리할 수 있습니다. 또한 모든 데이터 관리를 UI 컨트롤러(Activity, Fragment)가 담당하는 것이 아니기 때문에, 관리가 복잡해지는 것을 방지할 수 있습니다.

Activity에서 ViewModel 사용

안드로이드에서 ViewModel을 사용하는 방법을 버튼을 클릭하여 숫자를 증가시키거나 감소시키는 앱을 예제로 앱을 만들어봅니다.

먼저, MainActivity에서 ViewModel을 사용하는 방법을 살펴보겠습니다.

class MainActivity : AppCompatActivity() {

    lateinit var viewModel: MainViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        viewModel = ViewModelProvider(this).get(MainViewModel::class.java)

        val plusBtn : Button = findViewById(R.id.plus)
        val minusBtn : Button = findViewById(R.id.minus)
        val resultArea : TextView = findViewById(R.id.result)

        resultArea.text= viewModel.getCount().toString()

        plusBtn.setOnClickListener{
            viewModel.plus()
            resultArea.text= viewModel.getCount().toString()
        }

        minusBtn.setOnClickListener{
            viewModel.minus()
            resultArea.text= viewModel.getCount().toString()
        }
    }
}

MainActivity에서는 먼저 ViewModelProvider를 통해 MainViewModel을 가져옵니다. 그리고 버튼 클릭 이벤트에서는 ViewModel의 plus()와 minus() 메서드를 호출하여 숫자를 증가시키고 감소시키며, TextView에는 ViewModel로부터 가져온 숫자를 표시합니다.

아래는 MainViewModel입니다.

// ViewModel에서 저렇게 아래와 같이 딸랑 변수하나만 만들어서
// 사용하지는 않고 LiveData(등등)을 이용해서 함께 씀

class MainViewModel : ViewModel() {

    var countValue = 0

    init {
        Log.d("MainViewModel", "init")
    }

    fun plus(){
        countValue++
        Log.d("MainViewModel", countValue.toString())
    }

    fun minus(){
        countValue--
        Log.d("MainViewModel", countValue.toString())
    }

    fun getCount() : Int {
        return countValue
    }

}

MainViewModel에서는 countValue라는 private 변수를 가지고 있으며, 이 변수의 값을 변경하는 plus()와 minus() 메서드를 제공합니다. getCount() 메서드는 현재 countValue의 값을 반환합니다.

이렇게 ViewModel을 사용하면 화면 회전이나 다른 생명주기 이벤트가 발생해도 데이터를 안전하게 보관할 수 있습니다. 예를 들어, 화면 회전으로 인해 Activity가 재생성되더라도 ViewModel은 파괴되지 않으므로, ViewModel에 저장된 countValue는 그대로 유지됩니다.

Fragment에서 ViewModel 사용

프래그먼트는 액티비티에 종속되어 있기 때문에, 액티비티의 라이프 사이클에 영향을 받습니다. 그래서 프래그먼트에 ViewModel을 단순히 적용한 것만으로는 데이터의 지속성을 보장할 수 없습니다. 따라서 액티비티와 프래그먼트가 ViewModel을 공유하는 방식을 사용해야 합니다.

Activity / Fragment 의 ViewModel 공유

앱 수준의 build.gradle에 아래 코드를 추가해줍니다.

implementation 'androidx.fragment:fragment-ktx:1.4.1'

첫 번째로 MainActivity에서 ViewModel을 설정합니다.

class MainActivity : AppCompatActivity() {

    lateinit var binding : ActivityMainBinding
    lateinit var viewModel : MainViewModel

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

        binding = DataBindingUtil.setContentView(this,R.layout.activity_main)
        viewModel = ViewModelProvider(this).get(MainViewModel::class.java)

        // 초기값 설정
        binding.resultArea.text = viewModel.getCount().toString()

        binding.plus.setOnClickListener {
            viewModel.plus()
            binding.resultArea.text = viewModel.getCount().toString()
        }

        binding.minus.setOnClickListener {
            viewModel.minus()
            binding.resultArea.text = viewModel.getCount().toString()
        }

        val manager = supportFragmentManager

        binding.showFragment.setOnClickListener {
            val transaction = manager.beginTransaction()
            val fragment = TestFragment()
            transaction.replace(R.id.frameArea, fragment)
            transaction.addToBackStack(null)
            transaction.commit()
        }

    }
}

다음으로 프래그먼트에서 ViewModel을 설정합니다

class TestFragment : Fragment() {

    private lateinit var binding : FragmentTestBinding
    private val viewModel : MainViewModel by activityViewModels()

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

    }

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?,
    ): View? {

        binding = DataBindingUtil.inflate(inflater, R.layout.fragment_test, container, false)

        // 액티비티와 공유하는 ViewModel에서 countValue 값을 가져와서 표시합니다.
        binding.fragmentTest.text = viewModel.getCount().toString()

        return binding.root
    }

}

마지막으로 공유하는 ViewModel 코드입니다.

class MainViewModel : ViewModel() {

    var countValue = 0

    fun plus() {
        countValue++
    }

    fun minus() {
        countValue--
    }

    fun getCount() : Int {
        return countValue
    }

}

ViewModel Factory

ViewModelProvider는 ViewModel 인스턴스를 만들 때 사용하는 팩토리 메서드 패턴을 사용합니다. 이 패턴을 사용하는 이유는 인스턴스 생성 과정을 캡슐화하고, 생성된 인스턴스의 재사용을 용이하게 하기 위함입니다.
이때, ViewModel이 기본 생성자 이외에 추가적인 파라미터를 필요로 하는 경우에는 ViewModel Factory를 사용합니다. ViewModel Factory는 인터페이스를 구현한 클래스로, 필요한 파라미터를 가진 ViewModel 인스턴스를 생성하는 역할을 합니다.

아래는 ViewModel Factory를 사용하는 예제입니다.

먼저, MainActivity를 작성합니다. 이때 ViewModelFactory를 생성하고 이를 Provider의 파라미터로 추가합니다.

// ViewModelFactory

class MainActivity : AppCompatActivity() {

    lateinit var viewModel : MainViewModel
    lateinit var viewModelFactory: MainViewModelFactory

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // ViewModelFactory를 초기화하고, 이를 ViewModelProvider의 인자로 전달합니다.
        viewModelFactory = MainViewModelFactory(5000)
        viewModel = ViewModelProvider(this, viewModelFactory).get(MainViewModel::class.java)
    }
}

다음은 ViewModel인 MainViewModel의 코드입니다.

class MainViewModel(num : Int) : ViewModel() {

    init {
        Log.d("MainViewModel", num.toString())
    }

    // Repository
    // 네트워크 통신을 하거나

    // LocalDB
    // Room SQLite

}

마지막으로 MainViewModelFactory의 코드입니다.
인터페이스의 create 메서드를 오버라이드하여 ViewModel의 인스턴스를 생성하는 로직을 정의합니다. 이때, 필요한 파라미터를 함께 전달하여 ViewModel을 생성합니다.

class MainViewModelFactory(private val num : Int) : ViewModelProvider.Factory {

    override fun <T : ViewModel?> create(modelClass: Class<T>): T {

        if(modelClass.isAssignableFrom(MainViewModel::class.java)) {
            return MainViewModel(num) as T
        }
        throw IllegalArgumentException("Unknown ViewModel class")

    }
}

이와같이 MainActivity에서 ViewModel을 생성할때 파라미터를 넘겨줄 수 있습니다.

profile
나의 개발지식 output 공간

0개의 댓글