-
Notifications
You must be signed in to change notification settings - Fork 0
⚙ [트러블 슈팅] SnackBar UI 상태
snackBar를 호출하는 과정에서 너무나 많은 요청이 있을 경우 계속해서 스낵바가 뜨는 경우가 있었다.
snackBar-ezgif.com-crop-video.mov
또한 MainScreen에서 SnackBar를 처리하고 있기 때문에 하위 스크린에서 SnackBar를 실행시키기 위해서는
Props Drilling으로 Lambda를 함수의 인자로 계속해서 전달해 주어야 했다.
SnackBar Lambda를 Props Drilling 하지 않고 바로 접근할 수 없을까?
앱에서 일어나는 SnackBar 이벤트를 하나의 Screen에서 모두 일괄처리 할 수 있을까?
기타 모든 곳에서 SnackBar에 자유롭게 접근할 수 없을까?
이를 해결하기 위해 생각한 방법은 아래와 같다
1.SnackBar Lambda를 Props Drilling 하지 않고 바로 접근할 수 없을까?
3.기타 모든 곳에서 SnackBar에 자유롭게 접근할 수 없을까?
-> CompositionLocal
을 사용하여 current로 접근 가능하게 하자!
- 앱에서 일어나는 SnackBar 이벤트를 하나의 Screen에서 모두 일괄처리 할 수 있을까?
-> MainViewModel
에 SharedFlow를 넣어 모든 SnackBar 이벤트를 순서대로 넣어 MainScreen에서 관찰하여 일괄처리하자!
결과적으로 아래와 같은 구조를 생각해냈다.
먼저 SnackBar에게 전달할 데이터인 SnackBarEvent를 만들었다.
우리 서비스에서는 Login화면으로 이동, 혹은 단순 메시지 SnackBar만 존재하기 때문에 두 가지 타입을 Sealed class로 만들었다.
sealed class SnackBarEvent {
abstract val message: String
abstract val actionLabel: String?
class LoginRequired : SnackBarEvent() {
override val message: String = "로그인 후 이용 가능한 서비스입니다."
override val actionLabel: String = "로그인"
}
data class Message(
override val message: String,
override val actionLabel: String?,
) : SnackBarEvent()
}
그 다음 SnackBar의 Event를 ViewModel에 전달하기 위한 class SnackBarBridge를 만들었다.
class SnackBarBridge(
private val onSnackBarDataAdded: (SnackBarEvent) -> Unit
) {
fun postSnackBarEvent(event: SnackBarEvent) {
onSnackBarDataAdded(event)
}
fun postSnackBarString(message: String) {
onSnackBarDataAdded(SnackBarEvent.Message(message, null))
}
}
SnackBarHostState에 위의 데이터를 사용하여 SnackBar를 표시하는 Extension 함수와 CompositionLocalProvider에 들어갈 compositionLocal 변수를 만들었다.
val LocalSnackBarBridge = compositionLocalOf<SnackBarBridge> { error("No SnackBarHostState provided") }
suspend fun SnackbarHostState.showSnackBarWithData(data: SnackBarEvent) =
showSnackbar(
message = data.message,
actionLabel = data.actionLabel,
duration = SnackbarDuration.Short
)
이제 데이터를 담을 SharedFlow를 ViewModel에 만들어야 한다.
SnackBar의 데이터를 최대 2개 까지만 보여주고 싶었다.
그럼 현재 보여지고 있는 SnackBar 1개와, 그 사이에 들어온 SnackBar Data 1개.
즉, 대기 큐에는 1개만 들어가야 한다.
그래서 SharedFlow에 extraBufferCapacity를 1로 놓고, 새로운 데이터가 들어오면 아직 보여주지 않은 낡은 데이터를 없애기 위해 BufferOverflow.DROP_OLDEST를 해 주었다.
private val _snackBarFlow = MutableSharedFlow<SnackBarEvent>(
replay = 0,//새로운 구독자가 생기면 재발행 할 데이터 (재발행 안함)
extraBufferCapacity = 1,//버퍼의 크기(대기큐. 기획상 1개만)
onBufferOverflow = BufferOverflow.DROP_OLDEST//(새로운 데이터가 들어올 시 낡은 데이터를 버퍼에서 제거)
)
val snackBarFlow = _snackBarFlow.asSharedFlow()
fun postSnackBarData(data: SnackBarEvent) {
viewModelScope.launch {
_snackBarFlow.emit(data)
}
}
모든 준비는 끝났다.
이제 MainScreen에 세팅을 해 주어
모든 곳에서 MainViewModel의 SnackBar SharedFlow에 데이터를 저장할 수 있게 하자.
//앱 전체에서 사용할 수 있도록 CompositionLocalProvider로 래핑
PorringTheme(isLightBars = isLightBars) {
CompositionLocalProvider(LocalSnackBarBridge.provides(snackBarBridge)) {
MainScreen(
navigator = navigator,
mainViewModel
)
}
}
여러번 클릭해도 SnackBar는 최대 두번만 뜨게 된다.
2024-12-051.02.44-ezgif.com-crop-video.mov
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 동기화