코드를 수정하면서 느낀점들
왜 리액티브 프로그래밍인가
XML 기반의 메인액티비에 로직이 많은 코드를 수정하면서
코틀린에서도 단순 suspend함수가 아닌 flow를 사용하는지를 체감하게 되었다.
1. 기존 명령형 시스템의 한계
- UI 상태변경시 모든 변경점에서 상태값을 업데이트해야한다.
- 즉, 현재 액티비티가 아닌, 다른 프래그먼트에서 값을 수정시, 메인액티비티의 콜백을 사용해야한다.
- 이로인한 보일러 플레이트코드 과다, 동일한 코드의 복붙으로 관리포인트가 증가한다.
2. 선언형 접근의 이점
- 무엇을 보여줘야하는가(What)를 정의만 하면, 어떻게(How)는 프레임워크가 처리한다.
- 불변성의 개념과 함께 사용되므로, SideEffect에 대해서 걱정할 필요가 없어진다.
특히 체감되는 부분은 다음 예제와 같다.
기존 명령형 방식
fun countClick(){
count +=1
countRepository.addCount()
}
- 상태 관리의 분산: UI로직과 데이터저장을 일일이 제어
- 동시성 문제: 원자적 업데이트를 보장하지 못하게 되었을시, 예외처리 코드가 필요함
Flow 방식
val uiState = countRepository.countState
.map{ it.toUiState }
.stateIn(viewModelSceop)
fun clickCount(){
repository.addCount()
}
- SingleSourceOfTruth : 데이터의 출처와 상태를 Repository에서만 관리 : 뷰모델이 중복 상태를 가질 필요가 없음
- 자동 UI 동기화 및 부수효과 최소화 : 상태변화는 항상 단방향으로 이루어지고, 데이터흐름의 일관성이 유지된다.
모든곳에 리액티브 스트림을 사용할 필요는 없고,
웬만한경우에는 repository에서 단순 suspend, ViewModel에서는 optimistic update를 사용해도 된다.
Repository에서 단일진실공급원을 유지해야하는 Case가 있을때, 이와같은 리액티브 개념을 사용하면 좋다고 생각한다.
- ViewModel 여러곳에서 같은 Repository 값이 필요할때
- 현재 뷰모델과 다른곳에서 해당값이 수정되었을때
class UserRepository(private val api: UserApi) {
private val _userState = MutableStateFlow<User?>(null).onStart{
api.getUser()
}
fun getUserStream(): Flow<User?> = _userState
}
기존방법에서는 이렇게
class UserViewModel(repo: UserRepository) : ViewModel() {
val userState: StateFlow<UiState<User>> = flow {
emit(UiState.Success(repo.loadUser()))
}
.onStart { emit(UiState.Loading) }
.catch { e -> emit(UiState.Error(e)) }
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5000),
initialValue = null
)
}
'Android' 카테고리의 다른 글
[Android] Jetpack Compose에서 Props drilling을 피하는 패턴 (1) | 2025.04.11 |
---|---|
[Android] 아키텍처 정리 (1) | 2025.01.31 |
[Android] Navigation 정리 (0) | 2025.01.30 |