안녕하세요.
이번 포스팅에서는 소프트웨어 디자인 패턴 중 하나인 mvvm 패턴을 안드로이드 앱을 통해 알아보겠습니다.
MVVM 패턴에서 M은 Model, V는 View, VM은 ViewModel을 의미합니다.
쉽게 말해 V는 UI, Model은 UI가 그려지는데 필요한 데이터 혹은 UI에 표시되어야 하는 데이터입니다.
이 model과 view의 사이에서 view model이 중간 다리 역할을 하여 view와 model을 분리시켜 주는 것입니다.
view(ui) - view model - model(data)
위와 같은 형태가 될 수 있으며, view model은 view에서 버튼 클릭 등의 사용자의 동작을 받아 필요한 기능을 수행하고 view에 알려줄 데이터를 가공합니다.
예시를 통해 알아보겠습니다.
언어: 코틀린
sdk vsersion
- compile: 33
- min: 21
- target: 33
강아지와 고양이 버튼이 있는 PetFragment에서 버튼을 클릭하면 그에 맞는 정보가 표시되도록 하겠습니다.
예제 작성 시 hilt를 통해 의존성을 주입해 주었습니다. hilt에 관한 건 아래 포스팅을 확인해 주세요.
https://it-of-fortune.tistory.com/26
Model 입니다.
이름과 나이를 포함하고 있는 데이터 클래스를 생성합니다.
Pet.kt
data class Pet(
val name: String,
val age: Int
)
각각의 데이터를 return 해주는 가상의 DataSource를 생성하겠습니다.
ExampleDataSource.kt
class ExampleDataSource @Inject constructor() {
fun getCatData(): Pet {
return Pet(
name = "키티",
age = 7
)
}
fun getDog(): Pet {
return Pet(
name = "빙고",
age = 4
)
}
}
이 데이터 소스(로컬 혹은 서버)에서 강아지와 고양이의 정보를 가져올 수 있습니다.
ViewModel입니다.
AAC ViewModel을 상속받은 PetViewModel 클래스를 MVVM의 view model로 사용하겠습니다.
두 뷰모델은 다른 종류의 뷰모델인데, 이에 관해서는 다른 포스팅에서 다루겠습니다.
PetViewModel.kt
@HiltViewModel
class PetViewModel @Inject constructor(
private val exampleDataSource: ExampleDataSource
): ViewModel() {
private val _pet = MutableLiveData<Pet>()
val pet: LiveData<Pet> get() = _pet
fun onCatClicked() {
setCatName()
}
fun onDogClicked() {
setDogName()
}
private fun setCatName() {
_pet.postValue(exampleDataSource.getCatData())
}
private fun setDogName() {
_pet.postValue(exampleDataSource.getDog())
}
}
View입니다.
fragment_pet.xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<variable
name="viewModel"
type="com.example.laboratory.PetViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".PetFragment">
<Button
android:id="@+id/button_cat"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="고양이"
android:onClick="@{() -> viewModel.onCatClicked()}"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@id/button_dog" />
<Button
android:id="@+id/button_dog"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="강아지"
android:onClick="@{() -> viewModel.onDogClicked()}"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/button_cat" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="5dp"
android:text="이름:"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{viewModel.pet.name}"/>
</LinearLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="5dp"
android:text="나이:"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{Integer.valueOf(viewModel.pet.age).toString()}"/>
</LinearLayout>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
레이아웃에 data binding을 적용해 주었습니다.
https://it-of-fortune.tistory.com/23
PetFragment.kt
@AndroidEntryPoint
class PetFragment : Fragment() {
private var _binding: FragmentPetBinding? = null
private val binding get() = _binding!!
private val viewModel: PetViewModel by viewModels()
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentPetBinding.inflate(layoutInflater, container, false).apply {
viewModel = this@PetFragment.viewModel
lifecycleOwner = viewLifecycleOwner
}
return binding.root
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}
viewBinding을 통해 view를 참조합니다.
https://it-of-fortune.tistory.com/21
에뮬레이터에서 동작을 확인하겠습니다.
의도한 대로 동작을 수행합니다.
동작의 순서를 간략하게 표현해 보겠습니다.
1. 버튼 클릭
2. PetViewModel에 클릭 이벤트 전달
3. 이벤트에 맞는 model 요청
4. 받아온 model을 live data에 post
5. 이를 관찰하던 view가 변화된 데이터를 ui에 표시
이상 포스팅을 마칩니다.
감사합니다.
'Android Application > 앱 설계' 카테고리의 다른 글
안드로이드 컴포즈(jetpack compose) - state hoisting (0) | 2023.04.07 |
---|---|
안드로이드 DI, DIP 예시(with hilt) (0) | 2023.04.04 |
안드로이드 Repository pattern 간단 예시(with MVVM pattern) (0) | 2023.04.03 |
댓글