Android/랜덤리즘

[랜덤리즘] 설정화면에서 변경한 값을 다른 화면에서 사용해도 될까?

김 안개 2024. 2. 28. 19:08

지난번 포스팅에서 2가지 의문점을 가졌었는데, 그 중 2번째 의문점에 대해서 작성해보려고 한다.

 

 

지난번 의문점은 위의 사진을 보면 알 수 있다.

 

포스팅은 여기!

 

[랜덤리즘] 관련 알고리즘 보이기/숨기기 기능 개발하기 -2

SharedPreferences vs DataStore 처음에는 SharedPreferences 를 사용해서 데이터를 저장하려고 했는데, 공식문서에서 SharedPreferences 를 사용하는 것 보다는 DataStore 를 사용하는 것을 추천한다고 해서 SharedPrefer

w36495.tistory.com

 


상황 설명

설정 화면에서 변경한 '관련 알고리즘 보이기/숨기기'의 값에 따라 문제 화면에서도 해당 부분이 보여져야한다!

그렇다면 문제 화면에서 해당 설정 값을 가져오려면 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))
                }
            }
        }
        
        ...
    }
}

 

변경된 클래스간의 의존도

 

 

 

굿잡

 

코드 수정 완료!