728x90
๋ฐ์ํ
MVVM (Model-View-ViewModel) ์ด๋?
MVVM์ ์ ํ๋ฆฌ์ผ์ด์ ์ ์ฑ ์์ ์ธ ๋ถ๋ถ์ผ๋ก ๋ถ๋ฆฌํ์ฌ ์ฝ๋๋ฅผ ๋ ์ฝ๊ฒ ํ ์คํธํ๊ณ , ์ ์ง๋ณด์ํ๊ณ , ํ์ฅํ ์ ์๋๋ก ๋๋ ๋์์ธ ํจํด์ ๋๋ค.
- Model (๋ชจ๋ธ):
- ์ญํ : ๋ฐ์ดํฐ์ ๋น์ฆ๋์ค ๋ก์ง์ ๋ด๋นํฉ๋๋ค. ๋ฐ์ดํฐ๋ฒ ์ด์ค, ๋คํธ์ํฌ API, ๋ฐ์ดํฐ ์กฐ์ ๋ฑ ๋ฐ์ดํฐ ๊ด๋ จ๋ ๋ชจ๋ ๊ฒ์ด ์ฌ๊ธฐ์ ํฌํจ๋ฉ๋๋ค.
- ์๋๋ก์ด๋ ๊ตฌํ: Repository ํด๋์ค์ Data Class (Kotlin)๋ฅผ ์ฌ์ฉํ์ฌ ๊ตฌํํฉ๋๋ค.
- View (๋ทฐ):
- ์ญํ : ์ฌ์ฉ์์๊ฒ ๋ณด์ด๋ UI (ํ๋ฉด)๋ฅผ ๋ด๋นํ์ฌ, ์ฌ์ฉ์์ ์ ๋ ฅ์ ๋ฐ๊ณ ViewModel์ ์ ๋ฌํฉ๋๋ค. Model์ด๋ ๋น์ฆ๋์ค ๋ก์ง์ ๋ํด ์์ง ๋ชปํ๊ณ , ๋จ์ํ ViewModel์ด ์ ๊ณตํ๋๋ฐ์ดํฐ๋ฅผ ํ์ํฉ๋๋ค.
- ์๋๋ก์ด๋ ๊ตฌํ: Activity ๋๋ Fragment (XML ๋ ์ด์์ ํฌํจ) ์ ๋๋ค.
- ViewModel (๋ทฐ๋ชจ๋ธ):
- ์ญํ : View์ Model ์ฌ์ด์ ์ค๊ฐ์(Mediator) ์ญํ ์ํฉ๋๋ค. View๊ฐ ํ์ํ ๋ฐ์ดํฐ๋ฅผ ์ค๋นํ๊ณ , View์ ์๋ช ์ฃผ๊ธฐ(Lifecycle)๋ฅผ ๊ณ ๋ คํ์ง ์๊ณ ๋ฐ์ดํฐ๋ฅผ ์์ ํ๊ฒ ๋ณด์กดํฉ๋๋ค.
- ์๋๋ก์ด๋ ๊ตฌํ: JetPack ViewModel ํด๋์ค๋ฅผ ์ฌ์ฉํฉ๋๋ค.
MVVM์ ์ฌ์ฉํ๋ ์ด์ ์ ์์
1. MVVM์ ์ฌ์ฉํด์ผ ํ๋ ์ด์
| ์ฅ์ | ์ค๋ช |
| ๊ด์ฌ์ฌ์ ๋ถ๋ฆฌ | UI ์ฝ๋(`View`)์๋น์ฆ๋์ค ๋ก์ง/๋ฐ์ดํฐ ์ฒ๋ฆฌ(Model, ViewModel)๊ฐ ๋ช ํํ๊ฒ ๋ถ๋ฆฌ๋์ด ์ฝ๋์ ๊ฐ๋ ์ฑ์ด ๋์์ง๊ณ ํน์ ๋ถ๋ถ์ ๋ณ๊ฒฝํ ๋ ๋ค๋ฅธ ๋ถ๋ถ์ ๋ฏธ์น๋ ์ํฅ์ด ์ต์ํ๋ฉ๋๋ค. |
| ํ ์คํธ ์ฉ์ด์ฑ | `ViewModel`์ Android Framework์ ์์กดํ์ง ์์ผ๋ฏ๋ก, UI๋ `Activity`์์ด ์์Kotlin/Java ์ฝ๋๋ก ๋ก์ง์ ๋จ์ ํ ์คํธํ๊ธฐ๊ฐ ๋งค์ฐ ์ฝ์ต๋๋ค |
| ์๋ช ์ฃผ๊ธฐ ์์ ์ฑ | ViewModel์ `Activity`๊ฐ ํ๋ฉด ํ์ ๋ฑ์ผ๋ก ์ฌ์๋๋๋ผ๋ ๋ฐ์ดํฐ๋ฅผ ์์ ํ๊ฒ ์ ์งํ๋ฉฐ, ์ฌ์ฉ์ ๊ฒฝํ์ ์ ํดํ๋ ๋ฐ์ดํฐ ์์ค์ ๋ฐฉ์งํฉ๋๋ค. |
2. MVVM์ ์ฌ์ฉํด์ผ ํ๋ ์์
- ์ค๊ท๋ชจ ์ด์์ ์ฑ ๊ฐ๋ฐ ์: ๋จ์ํ 'Hello World' ์ฑ์ ๋์ด ๋ฐ์ดํฐ ์ฒ๋ฆฌ, ๋คํธ์ํฌ ํต์ , ์ฌ์ฉ์ ์ ๋ ฅ ์ฒ๋ฆฌ ๋ฑ ๋ณต์กํ ๋ก์ง์ด ํ์ํ ๊ฒฝ์ฐ, MVVM์ ํ์์ ์ ๋๋ค.
- ํ์ ํ๋ก์ ํธ ์: ๋ช ํํ ์ญํ ๋ถ๋ด์ผ๋ก ์ฌ๋ฌ ๊ฐ๋ฐ์๊ฐ ๋์์ ์์ ํ๊ธฐ ์ฉ์ดํฉ๋๋ค.
- ์ฅ๊ธฐ์ ์ผ๋ก ์ ์ง๋ณด์๊ฐ ํ์ํ ์ฑ: ์ฑ์ ๊ท๋ชจ๊ฐ ์ปค์ง๊ฑฐ๋ ์๊ตฌ์ฌํญ์ด ๊ณ์ ๋ณ๊ฒฝ๋ ๋, MVVM ๊ตฌ์กฐ๋ ํ์ฅ์ ์ ๋ฆฌํฉ๋๋ค.
ํต์ฌ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ๋ฐ ๊ตฌํ
์๋๋ก์ด๋์์ MVVM์ ๊ตฌํํ ๋ ์ฌ์ฉํ๋ Jetpack ๋ผ์ด๋ธ๋ฌ์ ๋๋ค.
1. ViewModel (ํต์ฌ)
- ๊ธฐ๋ฅ: `Activity`๋ `Fragment`์ ์๋ช ์ฃผ๊ธฐ๋ฅผ ์ธ์ํ์ฌ, UI๊ฐ ํ๊ดด๋๊ณ ๋ค์ ์์ฑ๋ ๋ (์: ํ๋ฉด ํ์ ) ๋ฐ์ดํฐ๊ฐ ์์ค๋์ง ์๋๋ก UI ๊ด๋ จ ๋ฐ์ดํฐ๋ฅผ ์ ์ฅํ๊ณ ๊ด๋ฆฌํฉ๋๋ค.
- ์์กด์ฑ ์ถ๊ฐ (build.gradle.kts - app ๋ชจ๋):
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.2") // ์ต์ ๋ฒ์ ์ฌ์ฉ ๊ถ์ฅ
2. LiveData / SataeFlow (๋ฐ์ดํฐ ๊ด์ฐฐ)
- ๊ธฐ๋ฅ: `ViewModel`์ ๋ฐ์ดํฐ๊ฐ ๋ณ๊ฒฝ๋ ๋ `View`์ ์๋์ผ๋ก ์๋ฆผ์ ๋ณด๋ด UI๋ฅผ ์ ๋ฐ์ดํธํ๋๋ก ๋๋ ๊ด์ฐฐ ๊ฐ๋ฅํ ๋ฐ์ดํฐ ํ๋์ ๋๋ค. `View`์ ์๋ช ์ฃผ๊ธฐ(Lifecycle)์ ์ธ์ํ์ฌ ๋ฉ๋ชจ๋ฆฌ ๋์๋ฅผ ๋ฐฉ์งํฉ๋๋ค.
- ์์กด์ฑ ์ถ๊ฐ: ๋ณดํต `ViewModel` ์์กด์ฑ์ ํจ๊ป ํฌํจ๋ฉ๋๋ค.
3. Hilt / Koin (์์กด์ฑ ์ฃผ์ - DI)
- ๊ธฐ๋ฅ: Repository๋ ๋ค๋ฅธ ํด๋์ค๋ค์ ViewModel์ ๊ฐํธํ๊ฒ ์ ๊ณตํ๊ณ ๊ด๋ฆฌํฉ๋๋ค. ์ด๋ ๋๊ท๋ชจ ์ฑ์์ ๊ฐ์ฒด ์์ฑ์๊น๋ํ๊ฒ ์ฒ๋ฆฌํ๋๋ฐ ํ์์ ์ ๋๋ค.
MVVM ์์ : ๊ฐ๋จํ ์นด์ดํฐ ์ฑ
์ฌ์ฉ์๊ฐ ๋ฒํผ์ ๋๋ฅผ ๋ ๋ง๋ค ์ซ์๊ฐ ์ฆ๊ฐํ๊ณ , ํ๋ฉด ํ์ ์์๋ ์ซ์๊ฐ ์ ์ง๋๋ ์นด์ดํฐ ์ฑ์ MVVM์ผ๋ก ๊ตฌํํด ๋ณด๊ฒ ์ต๋๋ค.
1. Model (Data Class & Repository)
์ด ์์์์๋ ๊ฐ๋จํ ์ซ์ ๋ฐ์ดํฐ๋ง ๋ค๋ฃจ๋ฏ๋ก `Repository`๋ ์๋ตํ๊ณ `ViewModel`์์ ์ง์ ์ฒ๋ฆฌํฉ๋๋ค.
2. ViewModel (kotlin)
`CounterViewModel.kt`
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
class CounterViewModel : ViewModel() {
// 1. MutableLiveData: ViewModel ๋ด๋ถ์์ ๋ฐ์ดํฐ๋ฅผ ๋ณ๊ฒฝ(write)ํ ์ ์๋๋ก ํฉ๋๋ค.
private val _count = MutableLiveData<Int>()
// 2. LiveData: View(Activity/Fragment)์์ ๋ฐ์ดํฐ๋ฅผ ๊ด์ฐฐ(read)๋ง ํ ์ ์๋๋ก ๋
ธ์ถํฉ๋๋ค.
// ์ด๋ ์ธ๋ถ์์์ ์์์ ์ธ ๋ฐ์ดํฐ ๋ณ๊ฒฝ์ ๋ง์์ค๋๋ค.
val count: LiveData<Int>
get() = _count
init {
// ViewModel์ด ์ฒ์ ์์ฑ๋ ๋ ์ด๊ธฐ๊ฐ์ ์ค์ ํฉ๋๋ค.
_count.value = 0
}
// 3. ๋น์ฆ๋์ค ๋ก์ง: ์ซ์๋ฅผ ์ฆ๊ฐ์ํค๋ ๋ฉ์๋๋ฅผ ์ ๊ณตํฉ๋๋ค.
fun incrementCount() {
// value ์์ฑ์ ํตํด LiveData์ ๊ฐ์ ๋ณ๊ฒฝํฉ๋๋ค.
_count.value = (_count.value ?: 0) + 1
}
}
3. View (Activity - Kotlin + XML)
`MainActivity.kt`
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.activity.viewModels // by viewModels() ๋ธ๋ฆฌ๊ฒ์ดํธ๋ฅผ ์ฌ์ฉํ๊ธฐ ์ํ ํ์ฅ ํจ์
import com.example.myapplication.databinding.ActivityMainBinding
class MainActivity : AppCompatActivity() {
// View Binding ์ค์
private lateinit var binding: ActivityMainBinding
// ViewModel ์ด๊ธฐํ: Android Jetpack์ 'by viewModels()'๋ฅผ ์ฌ์ฉํ์ฌ ViewModel์ ๊ฐ์ ธ์ต๋๋ค.
// ํ๋ฉด ํ์ ์์๋ ๋์ผํ ์ธ์คํด์ค๋ฅผ ์ ์งํฉ๋๋ค.
private val viewModel: CounterViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
// 1. LiveData ๊ด์ฐฐ (Observing):
// viewModel.count์ ๋ณํ๋ฅผ ๊ด์ฐฐํฉ๋๋ค. ๋ณํ๊ฐ ์๊ธธ ๋๋ง๋ค { it: Int -> ... } ์ฝ๋๊ฐ ์คํ๋ฉ๋๋ค.
viewModel.count.observe(this) { newCount ->
// UI ์
๋ฐ์ดํธ ๋ก์ง (View์ ์ญํ )
binding.textViewCounter.text = "Count: $newCount"
}
// 2. ์ฌ์ฉ์ ์
๋ ฅ ์ฒ๋ฆฌ (View์ ์ญํ )
binding.buttonIncrement.setOnClickListener {
// ์ฌ์ฉ์ ์ก์
์ ViewModel์ ์ ๋ฌํฉ๋๋ค. (ViewModel์ ๋ก์ง ํธ์ถ)
viewModel.incrementCount()
}
}
}
`activity_main.xml`
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center">
<TextView
android:id="@+id/textView_counter"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="32sp"
android:text="Count: 0"/>
<Button
android:id="@+id/button_increment"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Increment"/>
</LinearLayout>
728x90
๋ฐ์ํ