지난번 포스팅에서 2가지 의문점을 가졌었는데, 그 중 2번째 의문점에 대해서 작성해보려고 한다.
지난번 의문점은 위의 사진을 보면 알 수 있다.
포스팅은 여기!
상황 설명
설정 화면에서 변경한 '관련 알고리즘 보이기/숨기기'의 값에 따라 문제 화면에서도 해당 부분이 보여져야한다!
그렇다면 문제 화면에서 해당 설정 값을 가져오려면 SettingViewModel 에 접근해서 데이터를 가져와야 하는가?
아니면 ProblemViewModel 에서 데이터를 가져와서 화면에 보여주어야하는가?
문제 화면, 설정 화면이라고 얘기를 해서 어떤 부분에 대해서 포스팅하는 것인지에 대한 이미지가 필요할 것 같아 화면에 대한 이미지도 만들어보았다.
클래스간의 의존도
코드
SettingViewModel.kt
// SettingViewModel.kt
class SettingViewModel @Inject constructor(
private val changeTagStateUseCase: ChangeTagStateUseCase,
private val getTagStateUseCase: GetTagStateUseCase
) : ViewModel() {
val tagState: Flow<Boolean> = getTagStateUseCase.invoke()
fun changeTagState(isChecked: Boolean) {
viewModelScope.launch {
changeTagStateUseCase.invoke(isChecked)
}
}
}
SettingFragment.kt
// SettingFragment.kt
class SettingFragment : Fragment() {
private var _binding: FragmentSettingBinding? = null
private val binding: FragmentSettingBinding get() = _binding!!
private val viewModel: SettingViewModel by viewModels()
...
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewLifecycleOwner.lifecycleScope.launch {
viewModel.tagState.collectLatest {
binding.switchTag.isChecked = it
}
}
}
}
ProblemFragment.kt
// ProblemFragment.kt
class ProblemFragment : Fragment() {
private var _binding: FragmentProblemBinding? = null
private val binding: FragmentProblemBinding get() = _binding!!
private val problemViewModel: ProblemViewModel by viewModels()
private val settingViewModel: SettingViewModel by viewModels()
...
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewLifecycleOwner.lifecycleScope.launch {
settingViewModel.tagState.collectLatest { state ->
binding.iBtnAlgorithm.tag = state
if (state) {
binding.layoutChip.visibility = View.VISIBLE
binding.iBtnAlgorithm.setImageDrawable(resources.getDrawable(R.drawable.ic_arrow_drop_down_24))
} else {
binding.layoutChip.visibility = View.INVISIBLE
binding.iBtnAlgorithm.setImageDrawable(resources.getDrawable(R.drawable.ic_arrow_drop_up_24))
}
}
}
...
}
}
왜 SettingViewModel 에 접근하는 것으로 작성했을까?
그렇다면 왜 문제화면(ProblemFragment) 에서 SettingViewModel 에 접근해서 사용하는 것으로 작성했을까?
'설정'에 초점을 맞춰서 코드를 작성했던 것 같다.
설정값이니 SettingViewModel 내에서 값을 쓰고/읽어오고하는 역할을 해야한다고 생각했다.
문제화면에서도 설정값을 사용해야 했을 때, '설정' 에 대한 값인데 '문제' 에 대한 데이터를 가지고 있는 ProblemViewModel 내에서 설정값에 접근해서 읽어와야 할까?
이에 대한 대답으로 '아니'라고 생각되어서 SettingViewModel 에 접근해서 값을 사용하는 것으로 작성했다.
해결 과정
이 의문점을 해결하기 위해서 일단 Architecture 와 MVVM 에 대해서 조금 더 알아보았고, 이해한 내용을 바탕으로 이 부분을 수정하기로 결정하였다.
여기서 MVVM 패턴에서 ViewModel 에 대한 이해가 부족하구나! 를 알게되었다.
View 에 비즈니스 로직+데이터 상태 관리에 대한 코드를 작성되게 되면 기능을 추가하거나 유지보수의 어려움을 겪으며, 코드가 많이 복잡해진다는 단점을 해소하기 위해 ViewModel 의 개념을 사용하게 되었다.
--> 여기서 알 수 있는 것은 ViewModel 와 View 는 UI 를 위해 일한다는 것은 동일하다.
하지만 View 는 ViewModel 의 data 를 통해 '화면에 보여준다.' 는 개념이 강하고, ViewModel 은 '화면에 보여지는 data 를 다룬다'는 개념이 강하다는 차이점이 있음을 알 수 있다.
(물론 ViewModel은 데이터 상태를 다룬다는 것, 데이터베이스와 관련된 일도 그리고 비즈니스 로직을 가지고 있기도 하지만!)
참고
https://stackoverflow.com/questions/58484680/what-is-difference-between-mvvm-with-clean-architecture-and-mvvm-without-clean-a
위의 생각들을 바탕으로 ProblemFragment 에서 SettingViewModel 에 접근하여 값을 사용하는 것은 옳지 않다고 생각되었다!
(왜냐하면 ViewModel 은 ViewModel 과 관련이 있는 View 에 필요한 data 를 다루는 클래스이기 때문이다.)
SettingViewModel 은 설정화면에 보여지기위해 필요한 data 를 다루는 클래스여야한다.
-> 즉, 문제화면(ProblemFragment) 에 보여지기 위해 필요한 data 를 다루지 않는다는 것이다.
그리고 ViewModel 에서 바로 DataStore 에 접근하여 값을 가져오는 것이 아닌 중간에 UseCase 를 통해 DataStore 에 접근하는 것이므로 SettingViewModel 에서 값을 가져오는 것처럼 ProblemViewModel 에서 해당 UseCase 에 접근하는 것으로 수정하면 될 것 같다.
그렇게 되면 ProblemFragment 와 SettingViewModel 간의 의존성은 존재하지 않게 된다!
수정 후 코드
ProblemViewModel.kt
// ProblemViewModel.kt
class ProblemViewModel @Inject constructor(
...
private val getTagStateUseCase: GetTagStateUseCase
) : ViewModel() {
...
val tagState: Flow<Boolean> = getTagStateUseCase.invoke()
...
}
ProblemFragment.kt
// ProblemFragment.kt
class ProblemFragment : Fragment() {
private var _binding: FragmentProblemBinding? = null
private val binding: FragmentProblemBinding get() = _binding!!
private val problemViewModel: ProblemViewModel by viewModels()
...
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewLifecycleOwner.lifecycleScope.launch {
problemViewModel.tagState.collectLatest { state ->
binding.iBtnAlgorithm.tag = state
if (state) {
binding.layoutChip.visibility = View.VISIBLE
binding.iBtnAlgorithm.setImageDrawable(resources.getDrawable(R.drawable.ic_arrow_drop_down_24))
} else {
binding.layoutChip.visibility = View.INVISIBLE
binding.iBtnAlgorithm.setImageDrawable(resources.getDrawable(R.drawable.ic_arrow_drop_up_24))
}
}
}
...
}
}
변경된 클래스간의 의존도
코드 수정 완료!
'Android > 랜덤리즘' 카테고리의 다른 글
[랜덤리즘] 예외 처리하기, 다이얼로그보다 사용자에게 선택권을 주기 (3) | 2024.03.12 |
---|---|
[랜덤리즘] 중복된 코드를 1개의 코드로 리팩토링하기 (0) | 2024.03.08 |
[랜덤리즘] 관련 알고리즘 보이기/숨기기 기능 개발하기 -2 (1) | 2024.02.26 |
[랜덤리즘] 관련 알고리즘 보이기/숨기기 기능 개발하기 -1 (0) | 2024.02.23 |
[랜덤리즘] 1차 배포하기 완료! (1) | 2024.02.21 |