본문 바로가기
Android/이것저것

[Android] 안드로이드에 MVVM 적용해보기 (DataBinding, LiveData, Koin)

by 일상 속 둔치 2020. 12. 5.

 소프트웨어 개발 패턴에는 MVC, MVP, MVVM 등이 있다! 그 중에서 안드로이드에서 가장 많이 사용하는 MVVM에 대하여 다루어보고자 한다.

 

 처음에 MVVM을 공부하려고 찾아봤는데 구현 방법들이 너무 많아서 어려웠다... 그래서 몇가지 구현 방법들에 대하여 기초적인 부분을 작성해보고자 한다. 물론 나도 공부하고 적은거라 틀린 부분이 있을 수 있다. 참고 정도만 해보자..!

 

1. MVVM이란?

 Model, View, ViewModel을 구성으로하는 패턴이다. 기존 MVC를 보완하기 위해 MVP가 나왔고 MVP를 좀 더 보완한게 MVVM이라고 하는 히스토리도 있다. MVVM의 구조는 아래와 같다.

  • View

- 기본적으로 UI를 지칭하는 구성 요소이다. 안드로이드에서 따지자면 Activity나 Fragment처럼 레이아웃을 변경하는 부분이라고 볼 수 있다.

- View에는 비즈니스 로직이 포함되지 않는다. 오직, UI 변경과 관련된 로직만 존재해야한다. 비즈니스 로직은 ViewModel에서 처리해야하고 View는 ViewModel을 Observe하다가 값이 변경되면 해당 값을 이용하여 랜더링한다.

 

  • ViewModel

- 사용자 입력에 맞는 데이터를 가공하는 비즈니스 로직을 처리하는 부분이다.

- View와 Model 사이에서 작동한다.

- View를 통해 사용자 액션이 들어오면 Model과 상호작용하여 값을 갱신한다.

 

  • Model

- 실질적인 Data를 다루는 구성 요소이며 MVC, MVP의 Model과 동일하다.

- 외부 DB, 로컬 DB, API를 이용한 데이터 획득 등 데이터 호출을 하는 부분이다.

 

 

MVVM 패턴에서는 View과 Model 사이의 의존성이 완전히 없어야한다.

그저 View는 ViewModel의 값을 관측만 하면된다. (아련,,,)

MVVM 패턴은 각 구성 요소들 간 의존성을 최대한 낮추는 것이 목적이라고 생각한다.

UI 코드와 비즈니스 로직이 분리되어 관리도 용이하나 ViewModel 설계하기가 어렵다는 단점이 있다,,,

 

2. 구현 방법

 MVVM에서 가장 핵심이 되는 부분은 View가 ViewModel의 데이터를 Observe하는 부분이라고 생각한다.

이때 Observe하는 방법에는 여러 가지가 있다. 오늘 다루어볼 내용은 3가지이다.

 

 - DataBinding

 - LiveData

 - Koin

 

 이외에도 다른 방법들이 있을 수 있고 실제로 LiveData + DataBinding 등 위의 구현 방법을 혼용해서 사용하는 법도 많다. 그러나 이번엔 가장 기본적인 것을 다루어보고자 한다.

 

■ 들어가기 전에

 

* Repository?

 간혹 인터넷에 있는 예제들을 보면 Repository 패턴을 적용하기도 한다. 이런 경우에 더 복잡해 보이는데 간략하게 설명하자면 Model을 Repository -> DataSource로 좀 더 세분화하여 Model에서 직접 DataSource를 다루기 보다 연동하는 부분을 따로 떼어 놓은 것이다. 즉, ViewModel과 Model(DataSource)사이의 Coupling을 좀 느슨하게 만들어주기 위함이라고 보면 될거 같다.

 

* Android AAC ViewModel?

 또한 Android AAC에 ViewModel이 있다. 정확히 말하자면 AAC의 ViewModel은 MVVM의 ViewModel과는 다르다. 그러나 MVVM을 구현함에 있어서 많이 사용한다. AAC의 ViewModel을 사용하면 생명주기에 맞는 클래스를 구현할 수 있고 데이터를 유지할 수 있어서 사용하는 것이다. 물론 AAC의 ViewModel과 MVVM의 ViewModel은 동명이인, 동음이의어와 같은 것이다.

 

이제는 정말 예제를 통해 코드를 확인해보자

 

1) DataBinding

그냥 DataBinding만 이용한 가~~~장 단순한 MVVM이다.

 

- build.gradle

DataBinding을 사용하기 위해 enable해주자. android {} 안에 입력하면 된다.

dataBinding {
	enabled = true
}

 

- ViewModel

 ViewModel에서는 View가 관측할 수 있는 데이터를 선언해주자. ObservableField를 사용하여 관측할 값을 선언해주자.

nameClick은 clickListener 함수로 사용할 것이다. 이렇게 선언한 값과 함수를 View에서 어떻게 연동할까? 아래 Layout에서 선언해주면 된다.

class DataBindingViewModel {
    val name = ObservableField("")

    init{
        name.set("Izone")
    }

    fun nameClick() = name.set("Click")
}

위 코드에서 굳이 ObservableField를 써야할까? 싶어서 단순 String으로 해보았다. nameClick을 TextView의 clickListener로 등록할건데 작동하지 않았다. 아마 name 데이터를 정확히 Observe하지 못하는 것으로 예측이 된다. 아래 Layout 코드에서 위의 값을 어떻게 Layout에 연동하였는지 확인해보자.

 

- Layout

<?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">

    <data>
        <variable
            name="vm"
            type="com.dunchi.android_mvvm.databinding.DataBindingViewModel" />
    </data>

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

        <TextView
            android:id="@+id/text"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{vm.name}"
            android:onClick="@{()->vm.nameClick()}"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintBottom_toBottomOf="parent"/>
    </androidx.constraintlayout.widget.ConstraintLayout>

</layout>

 data 태그 안에 사용할 변수를 선언해주자. vm이라는 이름으로 Layout 파일에서 접근할 것이고 type은 DataBindingViewModel의 값을 사용할 것이라고 선언해주었다.

 data 태그로 선언한 값을 사용하고 싶으면 @{}로 감싸 사용할 수 있다. 아래 TextView에 text는 vm의 name으로 지정하였고 onClick 메소드에는 vm.nameClick()을 지정하였다.

 

- View (Activity)

class DataBindingActivity : AppCompatActivity() {

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

        val binding: ActivityDataBindingBinding = DataBindingUtil.setContentView(this,
            R.layout.activity_data_binding
        )
        binding.vm = DataBindingViewModel()
    }
}

Activity에서는 DataBindingUtil을 사용하여 setContentView를 해주면서 binding 객체를 생성해준다.

 위에 보면 이름이 좀 이상하지만... ActivityDataBindingBinding은 DataBinding을 이용하면 자동으로 생성해주는 Class이다. 지금은 Activity 이름이 DataBindingActivity라 ActivityDataBindingBinding이라고 되었지만 예를들어 IzoneActivity면 ActivityIzoneBinding이라는 바인딩 클래스가 생성될 것이다.

 이후 이 바인딩 객체를 사용하여 vm (layout에서 variable 지정해준 name)에 ViewModel을 지정해주자.

 

 

2) LiveData

 LiveData는 기본적으로 Observe 할 수 있는 Data Holder 클래스이다. 위의 예제에서 ObservableField와 같은 역할을 한다고 보면 된다.

 

 그렇다면 왜 LiveData를 사용하는가? LiveData는 안드로이드 컴포넌트의 생명주기를 인식하면서 active한 상태일 때만 데이터를 업데이트한다.(onStarted, onResume) 즉, 생명주기를 고려하면서 사용하지 않을 때에는 Observe하지 않아 메모리 누수를 방지할 수 있다는 것이다. 또한 화면 구성이 변경되어도 데이터를 유지한다. 싱글톤으로 구현되어 있다는 것을 본적이 있다. 이외에도 다양한 장점이 있다. 사용 방법은 아래와 같다.

 

- ViewModel

class LiveDataViewModel : ViewModel() {
    private val user: MutableLiveData<String> by lazy {
        MutableLiveData<String>().also{
            loadUser()
        }
    }

    fun getUser(): LiveData<String> {
        return user
    }

    private fun loadUser(){

    }
}

LiveData 객체를 선언해준다. ViewModel을 상속 받았는데 상속 받은 ViewModel 클래스가 위에서 언급한 AAC ViewModel이다. 상속만 시켜주면 된다. 물론 더 다양한 쓰임새가 있을 수 있는데 다음에 다루어보자,,,

 

- View (Activity)

class LiveDataActivity : AppCompatActivity() {

    private val model: LiveDataViewModel by viewModels()

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

        val userObserver = Observer<String> {
            it -> text.text = it
        }

        model.getUser().observe(this,userObserver)
    }
}

 여기서 Observer로 위에서 생성한 ViewModel를 매핑해주자. 여기서 눈치 빠른 분들은 이런 생각이 들것이다.

여기에 DataBinding을 이용하면 더 코드가 단순해지지 않을까? 맞다. 그래서 위에서 잠깐 언급했지만 LiveData + DataBinding으로 MVVM을 많이 구현한다. DataBinding 예제에서 observableField를 LiveData로 바꾸어 사용해주자. 우리는 메모리를 아끼고 데이터가 유지되는 ViewModel을 이용할 수 있을 것이다!

 

3) Koin

- build.gradle

    // android{} 안에
    repositories {
        google()
    }
    
    // dependencies{} 안에
	// Android KTX (by viewModels())
    implementation "androidx.core:core-ktx:1.3.2"
    implementation "androidx.fragment:fragment-ktx:1.2.5"

    // Koin for Kotlin
    implementation "org.koin:koin-core:2.1.0-alpha-7"
    // Koin extended & experimental features
    implementation "org.koin:koin-core-ext:2.1.0-alpha-7"
    // Koin for Unit tests
    testImplementation "org.koin:koin-test:2.1.0-alpha-7"
    // Koin AndroidX Scope features
    implementation "org.koin:koin-androidx-scope:2.1.0-alpha-7"
    // Koin AndroidX ViewModel features
    implementation "org.koin:koin-androidx-viewmodel:2.1.0-alpha-7"
    // Koin AndroidX Fragment features
    implementation "org.koin:koin-androidx-fragment:2.1.0-alpha-7"
    // Koin AndroidX Experimental features
    implementation "org.koin:koin-androidx-ext:2.1.0-alpha-7"

 


소스 코드 : github.com/dunchi-develop-blog/android-mvvm

 

dunchi-develop-blog/android-mvvm

안드로이드 MVVM 예제. Contribute to dunchi-develop-blog/android-mvvm development by creating an account on GitHub.

github.com

 

댓글