네트워크 에러 처리
Coroutine Exception Handling을 사용하여 ViewmodelScope 내부에서 예외 처리
네트워크 연결 시 예외 발생 여부에 따라 viewmodel의 stateFlow value 값을 변경하고, Activity에서 collect하여 상황에 따라 처리하게 함
서버로부터 응답받은 유저 데이터를 viewmodel의 stateFlow value로 저장하고, Activity에서 collect하여 알 수 있게 함
collect 문제 인식 및 수정
여러 번 데이터가 변경되어 startActivity가 중복 수행되거나 상황에 맞지 않게 수행(로그인이 실패한 오프라인 상태에서도 홈 화면으로 넘어감)
기존 코드
특이점: 하나의 lifecyclescope에서는 하나의 flow.collect만 수행됨 (다른 코드는 무시됨)
문제 고민 및 해결책 제시
loginResponse는 한 번의 로그인 시도에도 여러 번 바뀔 수 있으므로, 네트워크 연결 여부를 저장하는 stateflow의 value가 바뀔 때 연결 문제가 없으면 홈 화면으로 이동하게 한다.
loginResponse는 따로 Intent에 넣어 보내지 않고, 필요한 부분만 가공해서 SharePreference로 저장할 것이다.
위와 같이 변경하면 평상시에는 문제없지만, 오프라인 상황 → 온라인 상황으로 변경 후 로그인을 하면 stateflow의 value가 여러 번 같은 값으로 변경되어 collect 괄호 안 코드가 실행되기 때문에 homeActivity가 여러 번 생성된다.
온라인
오프라인
오프라인 →온라인
stateFlow의 value가 같은 값으로 변경되어도 collect 괄호 안 코드가 실행된다..?
분명 StateFlow는 내부적으로 값을 conflate 해주고 있기 때문에, 동일한 값을 2번 방출하지 않는다고 알고있는데..
[Coroutine] State flow vs Shared flow with case study
collect의 생명주기를 관리하지 않은 문제일까?
마스터클래스에서 설명해 주신 대로 repeatOnlifecycle을 추가해보았다.
… home activity에서 back 버튼을 누를 때마다 login activity에서 작동해버려서 무한정 생성된다.
오프라인 → 온라인 후 로그인 버튼을 눌렀을 때 homeActivity가 여러 번 생성되는 것도 해결되지 않았다.
Lifecycle.State를 Started에서 Created로 변경하니 무한정 생성 문제는 해결되었다. 여전히 오프라인 → 온라인 문제는 존재한다.
이 과정에서 다른 문제가 몇 개 더 발견되었다.
현재 고쳐지지 않은 문제
2번 문제 해결
viewmodel의 networkResult
stateflow의 value가 LoginActivity가 onStart 될 때마다 초기화 되지 않기 때문이므로, loginRepository의 함수를 호출하기 직전에 value를 null로 초기화해서 해결
유저 정보 혼선 방지를 위해 viewmodel의 loginResponse
stateflow의 value도 함께 초기화
2번 문제를 해결했더니 다른 문제가 생겼다.
StateFlow는 내부적으로 값을 conflate 해주고 있기 때문에, 동일한 값을 2번 방출하지 않는다
라고 가정한다면 이전에 수행하던 코루틴 Job이 종료되지 않은 채 새 Job이 만들어졌고, StateFlow 값 변경을 기존 Job과 새로운 Job이 동시에 확인하고 있으므로 마치 동일한 값을 2번 방출하는 것처럼 보이는 현상이라고 추측할 수 있다.
위 추측이 맞다면 현재 사용하고 있는 repeatOnLifecycle이 생명주기에 따른 Job 생성/취소 관리를 의도(다른 Activity로 넘어갈 때 기존 job 취소)에 맞게 작동하지 않고 있다고 추측할 수 있다.
repeatOnLifecycle에 대해 조금 더 찾아보았다.
[CoroutineScope] 3. repeatOnLifecycle 사용하여 불필요한 메모리 사용 방지하기 : flow의 올바른 데이터 수집 방법
공식 문서와 다른 블로그를 보면
androidx.lifecycle | Android Developers
[번역]Android UI에서 flow를 수집하는 안전한 길
공식 문서 주석
The block passed to repeatOnLifecycle is executed when the lifecycle is at least started and is cancelled when the lifecycle is STOPPED.
공식 문서 설명
Runs the given block in a new coroutine when this Lifecycle is at least at state and suspends the execution until this Lifecycle is Lifecycle.State.DESTROYED
Lifecycle.Event 공식 문서와 Lifecycle.repeatOnLifecycle
클래스 내부를 보면
Lifecycle.Event | Android Developers
Pluu Dev - Lifecycle-ktx flowWithLifecycle API
repeatOnLifecycle(Lifecycle.State.*CREATED*)
의 경우
onCreate에서 job launch, onDestroy에서 cancel 반복,
repeatOnLifecycle(Lifecycle.State.STARTED)
의 경우
onStarted에서 job launch, onStop에서 cancel 반복,
repeatOnLifecycle(Lifecycle.State.*Resumed*)
의 경우
onResume에서 job launch, onPause에서 cancel 반복하며,
설정한 state에 상관없이 onDestroy에서 반복 종료되는 것을 알 수 있다.
따라서
로그인 버튼이 클릭되었을 때가 아니라 앱 최초 실행시 loginActivity의 onCreate 생명주기에서
lifecycleScope.launch를 실행하면
오직 하나의 CoroutineScope 객체가 loginActivity의 생명주기가 최소 onCreate일때 job을 launch하고 onDestory일때 job을 cancel하는 것을 반복하게 되어 문제가 해결된다.
수정된 코드
이렇게 고친 결과 아래와 같은 문제가 해결되었다.