본문 바로가기
학습(삽질) 노트/Android

Android MVVM 구현하기

by -MAYDAY- 2022. 5. 1.

Android MVVM 구현하기

 Android 앱 개발하는 트렌드는 매해 발빠르게 변화하고 있다. 그 중 디자인 패턴은 몇 년 전만 해도 MVC 패턴이 대세였지만 많은 기업들이 MVVM 패턴으로 넘어가면서 개발 트렌드가 바뀌게 되었다. 최근 구글에서 Android Jetpack Compose를 정식 출시하여 이와 어울리는 효율적인 패턴을 적용하려는 움직임이 보이고 있어 기존에 자주 사용되던 패턴의 한계를 극복하려는 시도가 계속되고 있다. MVVM 패턴에 대한 개념은 알고 있었으나 이를 Android에서 어떻게 구현하는지 잘 모르고 있었다. 이미 대세인 MVVM 패턴인 만큼 Android 실습을 통해 알아보고자 한다.

 

Android MVVM 패턴의 구조도

 

build.gradle(Module) 설정

android {
	dataBinding {
        enabled = true
    }
}

 Android MVVM을 구현하는데 dataBinding을 사용하려면 gradle 설정이 필요하다. 모듈 수준의 build.gradle 파일로 이동하여 아래와 같은 설정을 진행해주고 'Sync Now'를 클릭한다.

 

dependencies {

	...
    implementation 'androidx.activity:activity-ktx:1.4.0'
    implementation 'androidx.fragment:fragment-ktx:1.4.1'
}

 viewModels()로 초기화하려면 아래와 같은 추가 gradle 설정이 필요하다. (다른 방식으로 초기화 시 필요 없음)

 

dataBinding을 활용한 View 구현

 

(1) XML 코드

<?xml version="1.0" encoding="utf-8"?>
<layout 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">

    <data>
        <variable
            name="main"
            type="com.example.android_mvvm_practice.MainActivity"/>
    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">

        <TextView
            android:id="@+id/name"
            android:text="Default"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintBottom_toBottomOf="parent"/>

        <TextView
            android:id="@+id/age"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="2dp"
            android:text="00"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/name" />

        <Button
            android:id="@+id/button"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Text Change!"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/age" />

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

 DataBinding 형식으로 View를 구성하는 경우, XML 코드에 전체를 layout 태그로 감싸고, data 태그 내에 변경이 필요한 데이터를 정의하여 데이터가 들어갈 XML 내의 View 태그에 해당 데이터를 호출하는 방식으로 넣어주면 된다. (이번 XML 예제에선 별도 데이터를 정의하여 저장하도록 구성하지 않았으므로 MainActivity만 DataBinding을 사용했다.)

 

(2) Activity 코드

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.activity.viewModels
import androidx.databinding.DataBindingUtil
import com.example.android_mvvm_practice.databinding.ActivityMainBinding

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

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

        binding = DataBindingUtil.setContentView(this,R.layout.activity_main)
        binding.mainActivity = this

        dataObserver()
        clickChangeData()
    }

    private fun dataObserver(){
        mainViewModel.name.observe(this) {
            binding.name.text = it
        }
        mainViewModel.age.observe(this) {
            binding.age.text = it.toString()
        }
    }

    private fun clickChangeData(){
        binding.button.setOnClickListener{
            mainViewModel.getData()
        }
    }
}

 Activity에는 View가 데이터를 Observe 하기 위한 목적인 코드를 작성하고, Button 클릭할 때마다 ViewModel이 Model에 데이터 변경을 요청하여 데이터가 실시간으로 변화될 때마다 View가 이를 감지할 수 있도록 하여 사용자에게 업데이트된 데이터를 보여줄 수 있도록 한다.

 

AAC ViewModel를 사용한 MVVM ViewModel 구현

import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel

class MainViewModel : ViewModel() {
    private val mainRepository = Repository()
    private val nameData = MutableLiveData<String>()
    private val ageData = MutableLiveData<Int>()
    
    val name: LiveData<String> = nameData
    val age: LiveData<Int> = ageData

    fun getData() {
        mainRepository.getData()

        nameData.value = mainRepository.name
        ageData.value = mainRepository.age
    }
}

 AAC의 ViewModel을 활용하여 MVVM의 ViewModel를 구성할 수 있다. 데이터 변경사항을 View가 Observe 할 수 있도록 ViewModel을 작성하고, LiveData를 사용하여 실시간으로 변한 데이터의 값을 View에 반영될 수 있도록 코드를 작성한다.

 

Model 구현

class Repository {

    private val nameList = listOf("홍길동", "김철수", "안영희")

    var name: String = nameList[0]

    private var cnt: Int = 0
    var age: Int = 10

    fun getData() {
        if (cnt == 2) {
            cnt = 0
        }
        name = nameList[cnt++]
        age++
    }
}

 View에서 보여줄 Data를 Model Class를 통해서 정의하고, 데이터의 변화가 Model 안에서 이루어지는 로직을 정의한다. Model 내의 데이터 변화와 관련된 모든 로직은 Model Class 내에서 모두 이루어지도록 코드를 작성하여 MVVM 패턴의 원칙을 준수하도록 한다.

 

에뮬레이터 실행 결과

MVVM 패턴 적용한 코드의 실행 결과

 

 AAC의 ViewModel를 활용하여 Android의 MVVM 패턴을 소스 코드를 통해 직접 적용해보았다. MVVM 패턴의 개념에 대해 어느 정도 알고 있었지만 Android에서 어떻게 구현하고 어떻게 동작하는지 원리에 대해 제대로 이해하지 못했었다. 하지만 이번 계기로 Android에서 직접 MVVM 패턴을 구현하여 더 이상 MVC 패턴을 쓰지 않고 MVVM 패턴을 활용한 프로젝트를 진행할 수 있겠다는 자신감을 가지게 되었다. MVC 패턴은 Activity에 부하를 주는 패턴이기에 앞으로 프로젝트에서는 자제하고, MVVM 패턴을 활용한 프로젝트 진행으로 좀 더 효율적인 코딩을 고려하는 앱 개발자가 되겠다는 다짐을 해본다.

 

 

참고

- https://velog.io/@haero_kim/Android-깔쌈하게-MVVM-패턴과-AAC-알아보기

 

[Android] 깔쌈하게 MVVM 패턴과 AAC 알아보기

⚠️ 이 글은 MVC 패턴에 대한 이해도가 있다면 더욱 읽기 수월합니다

velog.io

- https://leveloper.tistory.com/216

 

[Android] MVVM 패턴과 AAC에서의 ViewModel

 안드로이드 오픈 톡방을 보다 보면 주기적으로 올라오는 질문이 몇 가지 있습니다. 그중 하나가 MVVM 패턴에서의 ViewModel과 AAC(Android Architecture Components)에서 제공하는 ViewModel이 다른 것인가에 대

leveloper.tistory.com

 

'학습(삽질) 노트 > Android' 카테고리의 다른 글

AutoCompleteTextView를 활용한 Custom Filtered Adapter  (0) 2022.08.20
Clean Architecture  (0) 2022.07.30
REST API와 Retrofit  (0) 2022.03.26