ABOUT ME

-

  • Android) State 패턴을 통해서 UI 상태 관리하기
    Android 2021. 2. 26. 12:19


    오늘은 제가 요즘 유용하게 사용하고 있는 State 패턴에 대해서 글을 작성해보려고 합니다.

     

     

    State 패턴

    • 객체의 특정 상태를 클래스로 선언하고, 클래스에서는 해당 상태에서 할 수 있는 행위들을 메서드로 정의합니다.
    • 각 상태 클래스들을 캡슐화하여, 클라이언트에서 호출하는 방식
    • UI 상태관리를 Readable하게 하기 위한 방법 중 하나

     

     

    이런 특징들로 회사에 출근하기 위한 State 패턴을 예로 만들어보자면 아래와 같은 상태로 나눌 수 있지 않을까 싶습니다.

    각 상황에 따라 대처할 행위를 명시해 두는 것이죠. 그래면 해당 상황에 직면했을 때 대비하는 것이 쉬워집니다.

    	when (상태) {
                    is 출근.준비 -> {
                        씻기()
                        밥먹기()
                    }
                    is 출근.실패 -> {
                        늦잠()
                        회사에연락()
                    }
                    is 출근.성공 -> {
                        업무()
                        회의()
                    }
                }

    Usage

    interface, enum class, sealed class 모두로 State 패턴을 구현할 수 있는데 sealed class로 구현하는 것을 추천드립니다.

     

    sealed  클래스Child 클래스종류를 제한하는 특성이 있어, 정의된 하위 클래스 외에 다른 하위 클래스는 존재하지 않는다는 것을 컴파일러에게 알려주는 것과 같은 효과를 냅니다. 

     

    when 문으로 모든 케이스에 대해서 처리가 되어야 하기 때문에 다른 클래스들은 else 구문이 들어가야 하지만, 

     sealed class는 컴파일 시점에 하위 클래스들이 정해져 있기 때문에 else 없이도 클래스들을 관리할 수 있습니다.

     

    sealed class의 하위클래스는 class, data class, object 로 정의할 수 있습니다.

     

    State Class

    sealed class UiState {
        object Loading : UiState()
        object Empty : UiState()
        data class Success(val list: List<FavoriteItem>) : UiState()
        data class Error(val message: String?) : UiState()
    }

     

    위와 같이 State 클래스를 선언했습니다.

     

    ViewModel

    private val _uiState = MutableStateFlow<UiState>(UiState.Loading)
        val uiState = _uiState.asStateFlow()
    
    private fun getSomething() {
                trendingRepository.getSomething()
                    .onStart {
                        _uiState.value = UiState.Loading
                        delay(300)
                    }
                    .catch { exception -> UiState.Error(exception.localizedMessage) }
                    .collect { _uiState.value = UiState.Success(it) }
    }

    그런 후에 뷰모델에서 위와 같이 사용할 수 있습니다. 저는 코루틴의 flow와 함께 사용했습니다.

     

    View

    private fun showSomething() {
            viewModel.uiState.asLiveData().observe(viewLifecycleOwner, Observer { 
                when (it) {
                    is UiState.Loading -> {
                        showLoadingView()
                        hideRecyclerView()
                    }
                    is UiState.Empty -> {
                        hideLoadingView()
                        showEmptyText()
                    }
                    is UiState.Success -> {
                        hideLoadingView()
                        showRecyclerView()
                        adapter.submitList(it)
                    }
                    is UiState.Error -> {
                        hideLoadingView()
                        showErrorText(it.message.toString())
                    }
                }
            })
        }

    그런 후에 View(Fragment)에서 State를 나누어 해당 상태에 맞는 코드를 작성하면 됩니다.

     

    State 패턴을 사용한다면 굉장히 직관적이고 가독성 있는 코드를 작성할 수 있지 않을까요? 

    State 패턴은 UI 상태가 복잡해질수록 직관적이고, Side Effect 적은 방식이기 때문에 이런 경우에 유용한 패턴이라고 생각합니다.

     

    반응형

    댓글

Designed by Me.