개요
안드로이드에서 실시간 데이터의 변환을 감지해야 하는 화면이 여럿 있습니다.
예를 들어 커뮤니티와 댓글창과 같이 다른 유저의 행동으로 인해 서버의 데이터가 변한 경우, 이를 다른 유저의 화면에도 반영해야 합니다.
통상적으로 MVVM 패턴에서 프래그먼트나 액티비티의 뷰모델이 생성될 경우 init 블록이나 view가 생성될 당시 서버의 데이터를 로드해서 UI에 반영하게 되는 로직이 일반적이라고 생각합니다.
즉 서버로의 데이터 호출은 딱 한번 일어납니다.
하지만 어떻게? 다른 앱은 댓글과 게시글이 달릴 경우 다른 유저가 화면을 이동시키지 않아도(프래그먼트나 뷰모델이 재생산되지 않아도) 실시간으로 반영할 수 있는지 궁금했습니다.
구조를 간단하게 도식화하면 아래와 같습니다.
- 뷰모델이 초기 생산되면 서버로 데이터를 요청합니다.
- 서버가 요청에 대한 결과로 데이터를 뷰모델에 반환합니다.
- 뷰모델의 데이터를 UI에 반영합니다.
물론 뷰모델 <-> 서버의 과정에서 레포지토리가 위치하지만, 현재 글에서는 뷰모델의 동작을 다루기 위해 생략했습니다.
즉 위와 같은 화면에서 사용자 A와 사용자 B가 있다고 가정할 때, B가 영화 일기를 작성할 경우 A의 화면에도 반영되어야 합니다.
사용자 B가 다른 사용자들에게 나 추가했어~~라고 전달하는 구조는 옳지 않다고 생각했습니다.
따라서 저는 사용자들의 화면에서 주기적으로 새로고침 즉 데이터를 로드하는 행위가 필요하다고 생각했습니다.
구현
이를 구현하기 위해 thread를 이용한 while 문으로 처리할 수 있지만, viewModel에서 flow를 사용하고 있기에, 저는 thread가 아닌 flow로 이를 구현해 보았습니다.
저는 2개의 함수를 구현했습니다.
첫 번째로는 입력된 주기를 바탕으로 flow를 생산하는 함수입니다.
즉 주기마다 울리는 알람이라고 생각하시면 편합니다.
private fun createTickerFlow(interval: Long): Flow<Unit> = flow {
while (true) {
delay(interval)
emit(Unit)
}
}
두 번째 함수는 생명주기동안 주기마다 행위를 실행하는 새로고침 함수입니다.
따라서 주기와, 생명주기, 행위를 입력받습니다
fun runTickerFlow(
interval: Long, // 새로고침 주기
scope: CoroutineScope, // 생명 주기
action: () -> Unit, // 행위
) {
val tickerFlow = createTickerFlow(interval)
scope.launch {
tickerFlow.collect {
action.invoke()
}
}
}
createTickerFlow를 통해 주기적으로 flow를 생산하는 알람 형태의 tickerFlow를 생산합니다.
생명주기동안 해당 flow가 생산될 때마다, 행위로 소비해 줍니다.
뷰모델 내에서 함수를 정의하지 않는 이유는 여러 화면에서 새로고침이 필요하기 때문에 따로 분리해 줬습니다.
뷰모델에서는 다음과 같이 호출합니다.
// viewModel init 블록
runTikcerFlow{
interval = 1000L,
scope = viewModelScope,
action = { loadComments(viewModelScope)},
}
결과물은 다음과 같습니다.
제가 원한 새로고침은 생명주기동안 새로고침을 불러오는 것이었습니다.
하지만 로그를 찍어보니 화면이 destroy가 돼도, 계속해서 로그가 찍히는 것을 확인할 수 있었습니다.
정말 의도치 않은 상황이었고, 화면을 벗어나도 새로고침 행위가 반복되기 때문에 이는 메모리 누수로 이어지는 큰 버그였습니다.
프래그먼트가 onDetach 돼도, viewModel의 onCleared가 호출되지 않았고, 이는 내비게이션 호스트에 의해 뷰모델이 관리되었기 때문이라고 판단 내렸습니다.
따라서 새로고침을 반복하는 생명주기를 변경해야 했기에, UI가 살아있을 동안만 새로고침을 하면 되기 때문에 다음과 같이 수정했습니다.
기존 viewModel의 init 블록에서 데이터를 로드하는 것을 fragment의 onViewCreated에서 호출해 줬습니다.
private fun loadData() {
runTickerFlow(
interval = 1000L,
scope = lifecycleScope,
action = { communityViewModel.loadComments(lifecycleScope) },
)
}
커뮤니티의 게시글들은 stateFlow로 관리하기 때문에 변화값이 없다면, 업데이트가 되지 않았기 때문에 효과적으로 관리할 수 있었습니다.
수정 결과 UI가 없어지면, 새로고침도 멈추는 것을 확인할 수 있었습니다.
새로고침을 구현해 보면서, 현업에서는 어떻게 새로고침을 구현하는지 궁금해지긴 했습니다.
토이프로젝트라서 코드가 깔끔하진 않았지만, 그래도 새로고침을 구현하는 경험을 할 수 있었고, 개선까지 할 수 있어서 만족했던 프로젝트였습니다.
참고
'Skils > Android' 카테고리의 다른 글
[Android] 비디오 타임라인 이벤트 처리 (0) | 2024.06.23 |
---|---|
[Android] 영상 편집 UI 구현 (0) | 2024.06.23 |
[Android] - Youtube API 적용해보기 (2) | 2024.05.26 |
[Android] - Retrofit으로 에러 메시지 처리하기 (1) | 2024.05.02 |
[Android] DataSource 적용 및 분리 (4) | 2024.05.02 |