• ViewModel에서 UI State를 어떻게 처리하고 있나 ?

    • UI State
      • 앱에서 사용자에게 표시되는 정보
      • 사용자가 보는 항목이 UI라면, UI 상태는 앱에서 사용자가 봐야한다고 지정되는 항목
    • 뷰모델에서 활용하고있는 stateFlow / liveData 들이 UI State라고 볼 수 있음
    • UIState를 활용하고 있는건 사람마다 / 회사마다 다 다름
      • 정답이 있는 문제는 아님
    • 깃헙 sample도 나중에 전달 해 줌
  • Sample 앱에서는 로딩이 있고 / 리스트를 보여주고 / 오류를 보여주거나 / 아이템이 비어있는 상태를 보여주는 앱을 제작함

  • UI State 여러개로 처리하기 ( General method )

    • 거의 대부분의 이 방법을 활용 중
    // 로딩 보여주기 ...
    private val _isLoading = MutableStateFlow(false)
    val isLoading = _isLoading.asStateFlow()
    
    // 아이템 보여주기 ... 
    
    • 위와 같이 변수륾 만들어 놓고 binding 혹은 observing
    • 각 상태들이 다른 상태에 영향을 주지 않는다는 장점이 있음
    • 데이터바인딩과도 / 컴포즈와도 함께 활용 할 수도 있음
    • 장점
      • 각자의 상태가 영향을 주지 않음
      • 코드 작성 난이도가 다소 쉬움
    • 단점
      • 변경을 누락해서 개발자 실수로 이어지기 쉬움
      • 특정 이벤트가 어떤 상태 변경을 만드는지 파악이 어려움
      • 코드 가독성이 좋지 않음
  • data class 하나로 만들기

    data class NewsUiState (
    		val isLoading: Boolean
    		val isError: Boolean 
    		val newsItems: List<News>
    ) {
    		val isEmpty: Boolean = !isLoading && newsItems.isEmpty()
    	
    		companion object {
    				val UnInitialized = NewsUiState( ... ) 
    		}
    }
    
    private val _state = MutableSharedFlow(NewsUiState)
    val _state = _state.asStateFlow()
    
    • 위 state 변수를 data binding / observe를 통해 view 갱신
    • 마찬가지로 compose와 함께 활용 가능
    • data 클래스 하나만 활용하기 때문에, copy 메소드를 활용하여 쉽게 상태 변경 가능
    • 장점
      • data class 특성을 활용 할 수 있음
      • 부분적인 업데이트가 편리하다
      • 상태에 필요한 로직을 클래스에 위임 할 수 있음
    • 단점
      • 하나의 상태만 변경해도 모든곳에 전파 됨
      • 클래스로 분리하기 위한 설계 능력이 요구됨
  • sealed class로 상태 분리하기

    sealed class NewsUiState {
    		object Uninitialized : NewsUiState()
    		object Loading : NewsUiState() 
    		...
    }
    
    • sealed 클래스로 데이터 바인딩을 활용 할 때엔, 특정 변수 타입과 함께 활용할 때엔 추가적인 코드가 많이 생산 될 수 있는 단점이 있긴 함
    sealed class NewsUiState {
    
    		open val newsItems: List<NewsItemUiState> = emptyList
    
    		object Uninitialized : NewsUiState()
    		object Loading : NewsUiState() 
    		...
    }
    
    • compose에서는 xml에서 활용하는것이 아니기 때문에 좀 더 UIState 코드가 간편해짐
    • compose 에서는 when 표현식을 활용 할 수 있다는 강점이 있음
    • MVI에서는 reducer 개념을 활용하기도 함
    • 타입 관련된 처리가 많아짐 ( if ( it is Success ) ... )
    • 장점
      • sealed class를 활용한다
      • 가독성이 좋고, 표현하고자 하는 상태가 클래스와 1:1 대응함
    • 단점
      • 상태의 복잡도와 클래스 갯수가 비례함
      • 화면 전체를 다루는 경우보다, 아이템 상태를 다루는데 좀 더 유리함
      • xml로 뷰를 짤경우 데이터바인딩에 복잡한 바인딩 어댑터가 추가 될 수 있음
  • UIState 일반화 하기 ( Modeling, 추상화 하기 )

    data class UIState<T> (
    		val isLoading: Boolean = false,
    		val cause: Throwable? = null,
    		val value: T? = null
    ) {
    		val isError: Boolean = cause != null
    
    		companion object {
    				fun <T> success(value: T) = Success(value)
    				...
    		}
    }
    
    • sealed 클래스로도 동일하게 활용 할 수 있음
    • state를 mutableStateFlow로 담아놓고, 이 stateFlow를 map 등을 활용하여 변수를 만들어놓고, 만들어진 변수를 xml에서 binding 해줄 수 있음
    • 추상화 된 것이라 baseViewModel 등을 만들어 놓고, 공통적으로 활용 할 수있음
    • UIState 라는 단순 interface를 만들고, 활용하기도 함
    • 장점
      • 시스템을 만들기 좋고, 아키텍처와 같이 활용할 수 있음 ( 매버릭스 / TCA 아키텍처 ... )
      • 가독성이 좋음
    • 단점
      • 구현 난이도가 높음
      • 시스템과 약간만 달라도 충돌이 발생하고, 도입하기 어려워짐
      • 사용하기 위해서 팀원들을 설득해야함