-
Notifications
You must be signed in to change notification settings - Fork 0
⚙ [트러블 슈팅] UI 실시간 반영
상세 화면에서 원하는 갤러리를 팔로우/언팔로우했을 경우, 실시간으로 firestore에 업데이트한 후에 UI에 반영해야 했다. 상세 화면은 Paging3을 사용하여 구현하고 있었기 때문에, 한 게시물의 author를 팔로우한다고 해서 전체 paging data를 다시 불러오는 것은 비효율적이며, 사용자 경험에 부정적인 영향을 미칠 수 있다고 생각했다. 이러한 상황에서 팔로우 행동을 하나의 이벤트를 간주하고, 뷰모델에서 팔로우가 성공적으로 서버에 반영되었을 경우에만 이벤트를 발생시키는 방법을 고려했다.
기존에는 상태 관리를 위해서 StateFlow를 주로 사용해왔다. 하지만, StateFlow는 최신 상태(값)만을 유지하고 방출하는 상태 관리 도구이다. 즉, StaeFlow에 저장된 값이 변경될 때마다 구독자에게 새로운 값을 방출한다. 그러나 이전 값과 동일한 값이 방출될 경우, 구독자에게는 아무런 변화가 없다고 간주하여 새로운 값을 방출하지 않는다. 예를 들어, 첫 번째 게시물을 팔로우했을 경우, StateFlow는 "팔로우 상태가 true"라는 최신값을 방출할 것이다. 만약, 사용자가 두 번째 게시물의 팔로우 상태가 동일하게 "팔로우 상태가 true" 라면, StateFlow가 새로운 값을 방출하지 않는다. 그렇기 때문에, 결과적으로 UI는 첫 번째 게시물의 팔로우 상태만 반영하고 두 번째 게시물의 팔로우 상태는 반영되지 않는 문제가 발생하는 것이다.
반면, SharedFlow는 이전 값과 같더라도 방출할 수 있어 이벤트 핸들링에 적합하다. SharedFlow는 이벤트가 발생할 때마다 구독자에게 값을 방출하기 때문에 현재 상황에서 더 적합하다고 판단했다.
- ViewModel에서 SharedFlow 생성 및 이벤트 발행
- 뷰모델에서 MutableSharedFlow를 생성하고, firestore 업데이트가 성공할 때 이벤트를 방출한다. 이 과정은 비동기로 이루어지며, 성공적으로 팔로우가 처리되면 _followSharedFlow에 true를 emit한다.
private val _followSharedFlow = MutableSharedFlow<Boolean>(0)
val followState = _followSharedFlow.asSharedFlow()
fun followUser(id: String, name: String) {
viewModelScope.launch {
followRepository.followUser(id, name)
.catch { Log.e("FollowUpload", "viewModel: $it") }
.launchIn(viewModelScope) // Firestore 요청을 비동기로 처리
_followSharedFlow.emit(true) // 성공 시 이벤트 방출
}
}
- DetailScreen에서 SharedFlow 구독
- DetailScreen에서는 collectAsStateWithLifecycle을 사용하여 followState를 구독한다.
- 팔로우 상태를 저장하기 위한 remember 변수 사용
- isFollowed 변수를 사용하여 초기 팔로우상태를 저장하고, followState에서 이벤트가 방출될 때 이 변수를 업데이트한다. 이 과정에서 remember를 사용하여 상태를 유지했다.
val isFollowed = remember { mutableStateOf(imageItem.isFollower) }
LaunchedEffect(followState.value) {
followState.value?.let {
isFollowed.value = it // 이벤트 발생 시 상태 업데이트
}
}
위의 구현 과정에서, 다른 화면으로 네비게이션했다가 다시 돌아온 경우, isFollowed 변수는 초기화되기 때문에 팔로우 상태가 변경되었음에도 불구하고 초기 데이터가 유지되어 문제가 발생했다. 예를 들어, 사용자가 팔로우했지만, 다른 화면으로 이동했다가 다시 돌아왔을 경우 UI에서는 여전히 팔로우하지 않은 상태로 나타나는 것이다.
이 문제를 해결하기 위해, 구성 변경 시에도 상태를 유지해야 했다. 이를 위해 rememberSaveable을 도입하여 상태를 저장하도록 수정했다.
rememberSaveable은 remember와 유사하지만, 화면 회전이나 다른 구성 변경 시에도 상태를 유지한다. 상태를 저장할 수 있는 객체를 사용하여 사용자의 입력 데이터, 스크롤 위치를 보존하는 데에 매우 유용하다. 다음은 rememberSaveable을 사용하여 프로젝트에 적용한 코드 예시이다.
val isFollowed = rememberSaveable { mutableStateOf(imageItem.isFollower) }
LaunchedEffect(followState.value) {
followState.value?.let {
isFollowed.value = it // 이벤트 발생 시 상태 업데이트
}
}
-> SharedFlow를 사용하여 이벤트를 처리하고, 팔로우 상태 변화를 UI에 즉시 반영하고, rememberSaveable을 통해 구성 변경 시에도 상태를 유지함으로써, 사용자 경험을 개선했다. 이를 통해 사용자가 팔로우 상태를 변경한 후 화면을 전환하고 다시 돌아와도, 올바른 팔로우 상태가 UI에 반영되도록 할 수 있었다.
Copyright 2024. Team Kolown All Rights Reserved.
- ✅ [기술 결정] Camera
- ✅ [기술 결정] Image Load
- ✅ [기술 결정] UI Toolkit - Copmpose
- ✅ [기술 결정] 데이터 별 UID 생성
- ✅ [기술 결정] Debounce & Paging 사용해서 검색 구현
- ✅ [기술 결정] Google Login
- ✅ [기술 결정] 스켈레톤 UI
- ⚙ [기술 분석] DI
- ⚙ [기술 분석] Image Compress
- ⚙ [기술 분석] 이미지 리사이징
- ⚙ [기술 분석] CameraX API
- ⚙ [기술 분석] Firebase & 랜덤 로딩
- ⚙ [기술 분석] ViewModel 공유
- ⚙ [기술 분석] Firestore 쿼리 전략
- ⚙ [트러블 슈팅] Chip with TextField(Custom with IntrinsicSize)
- ⚙ [트러블 슈팅] WindowInset
- ⚙ [트러블 슈팅] UI 실시간 반영
- ⚙ [트러블 슈팅] IME Padding
- ⚙ [트러블 슈팅] PagingSource reset
- ⚙ [트러블 슈팅] SharedFlow - SnackBar
- ⚙ [트러블 슈팅] Camera와 Lifecycle 동기화