요약하자면 이렇다. 앱하나 만드는데에 기능이 엄청 많이 들어간다. 그런데 개발자마다 개발하는 스타일이 다르다.
이로 인해 코드 유지보수가 어려워지고, 잘못된 구조로 인해 전체 프로젝트를 다시 작성해야 하는 상황이 발생하기도 한다.
이러한 문제를 해결하기 위해 개발 패턴을 하나로 통일하자 해서 만들어진게 MVVM 구조이다. (Model - View - ViewModel 패턴)
코드를 유지보수 하기 쉽다는 건 무슨 의미일까? 쉽게 말해 기능을 추가하거나 수정할 때 코드 전체를 건드리지 않아도 되는 상태를 의미한다.
때문에 각 부분이 독립적으로 수정 가능한 상태를 유지하는 것이 핵심이다.
왜 MVVM 패턴을 사용하면 특정 기능을 수정하거나 추가할 때 다른 코드에 미치는 영향을 최소화할 수 있다는건지, 그게 어떻게 가능한건지 조금 더 자세히 살펴보자.
MVVM의 목표 중 하나는 액티비티나 프래그먼트에서 XML에 있는 뷰(View)들을 직접 사용하는 것을 최소화하는 것이다.
예를 들어, 버튼의 id가 변경된다고 가정해보자. 만약 뷰와 로직이 밀접하게 연결되어 있다면, id를 수정할 때마다 그와 관련된 모든 코드를 수정해야 한다.
하지만 MVVM에서는 뷰의 속성 값이 ViewModel과 바인딩되어 있기 때문에, XML에서 버튼 id가 변경되더라도 ViewModel에서 해당 값을 유지하고 있기 때문에 .kt 파일을 수정할 필요가 없다.
아래 두 코드를 비교해 보면 이해가 빠를 것 같다
(1) view와 로직이 밀접하게 연결된 경우 - MVVM 미적용
1) activity_main.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<!-- 버튼의 id가 buttonSubmit -->
<Button
android:id="@+id/buttonSubmit"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Submit" />
</LinearLayout>
2) MainAcitivty.kt
// MainActivity.kt
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// XML에서 뷰 직접 참조
val button = findViewById<Button>(R.id.buttonSubmit)
button.setOnClickListener {
// 버튼 클릭 시 로직 실행
submitData()
}
}
private fun submitData() {
// 로직이 여기서 처리됨
Log.d("MainActivity", "Data submitted")
}
}
xml 파일에서 버튼의 id(buttonSubmit)가 변경되면, 해당 id를 참조하고 있는 모든 곳에서 수정을 해야 한다.
아래 코드에서는 findViewById 코드와 클릭 리스너를 수정해야 한다.
만약 참조가 1000번 되는 버튼의 id 가 바뀌면 모두 찾아서 수정해줘야 한다는 뜻...
이처럼 뷰(View)와 로직이 밀접하게 연결되어 있으면 유지보수가 번거로워진다.
이런 것을 종속성을 가진다고 부른다. (툴바 ID가 바뀌면 프로퍼티 이름도 바꿔줘야 하는 것)
반면 아래 코드는 XML 파일에서 버튼의 id가 변경되어도 ViewModel과 연결된 로직은 그대로 유지되는 것을 볼 수 있다. (종속성이 아예 1도 없을 순 없으니 종속성을 최소화시킴)
(2) view와 로직을 분류한 경우 - MVVM 적용
1) View = XML 파일 (activity_main.xml)
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="viewModel"
type="com.example.app.MainViewModel" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<!-- ViewModel과 바인딩된 버튼 -->
<Button
android:id="@+id/buttonSubmit"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="@{() -> viewModel.submitData()}"
android:text="Submit" />
</LinearLayout>
</layout>
2) ViewModel (MainViewModel.kt)
class MainViewModel : ViewModel() {
// 데이터 처리 로직은 ViewModel에 작성
fun submitData() {
Log.d("MainViewModel", "Data submitted via ViewModel")
}
}
3) Model (MainActivity.kt)
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private val viewModel: MainViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
// ViewModel과 XML을 연결
binding.viewModel = viewModel
binding.lifecycleOwner = this
}
}
참고로 구글은 MVVM을 화면과 관련된 작업에만 적용하는 것을 권장한다고 한다. 이벤트 처리까지 MVVM 구조에 포함시키면 코드가 너무 복잡해질 수 있기 때문. 따라서, UI 로직과 데이터 로직을 분리하는 선에서 MVVM을 사용하는 것이 좋은 방법인 것 같다.