0. 궁금해요.
안드로이드 개발을 할 때 우리는 로컬에 간단한 데이터를 저장하기 위해서 SharedPreference를 사용했었습니다. 하지만 이제는 안드로이드 공식에서 DataStore 사용을 권장하고 있습니다.
DataStore는 Coroutine을 사용하여 동시성 프로그래밍에 최적화 된 API 입니다. 그래서 DataStore를 통해 Preference 값을 가져올 경우 Flow 형태로 데이터를 받게 됩니다.
DataStore의 장점이 Coroutine을 사용하는 API라는 것에 대해서 지금까지 저는 그냥 “그렇구나.. 그래서 값을 Flow로 받을 수 있구나..” 라고만 생각하고 사용하고 있었는데, 궁금증이 생겼습니다. 그렇다면 DataStore는 Flow를 어떻게 발행하는 것일까요?
1. DataStore가 Flow를 리턴하는 방법 알아보기 (내부 살펴보기)
─── ・ 。゚☆: *.☽ .* :☆゚. ─── 결론적으로는 DataStore 내부에 있는 StateFlow를 통해 값을 발행합니다.─── ・ 。゚☆: *.☽ .* :☆゚. ───
SingleProcessDataStore는 DataStore Interface를 구현하는 클래스입니다.
구현된 클래스를 보면 DataStore Interface에 있던 Flow 타입의 data를 확인할 수 있는데요. data는 downstreamFlow 라는 flow에서 값을 가져와서 emit 하고 있었습니다. ⬇️
그리고 downstreamFlow는 State를 가지는 MutableStateFlow 입니다.
즉, 우리가 DataStore를 통해 Preference에 값을 저장하면, data에 직접 값이 저장되는 것이 아니라 함수를 통해 데이터의 상태에 따른 값(State<T>)이 downstreamFlow에 저장되고, 그 값을 data가 Flow로 발행합니다.
+ 추가
downstreamFlow에 저장되는 State는 아래와 같습니다.
- UnInitialized : 아직 값이 없는 상태
- Data : 디스크에서 값 읽기를 성공한 상태. 값은 디스크 상태의 현재 상태
- ReadException : 디스크에서 값 읽기를 실패한 상태.
- Final : scope가 취소 되었을 때. 새 읽기나 쓰기를 처리할 수 없는 상태.
2. DataStore에서 가져온 Flow 활용하기 (shareIn, stateIn)
Flow는 단지 데이터의 흐름이기 때문에 저장되지 않습니다. 그렇기 때문에 우리는 Flow를 사용할 때 shareIn, stateIn 연산자를 통해 각각 SharedFlow, StateFlow로 변환하여 사용합니다. 이를 통해 값을 저장하고, 여러개의 collector에서 받을 수 있도록 할 수 있습니다.
이 때 값이 어떻게 공유될지 started 매개변수를 통해 정할 수 있는데, 3가지 옵션이 있습니다.
- SharingStarted.Eagerly: collector가 존재하지 않더라도 바로 sharing이 시작되며 중간에 중지되지 않는다. 값이 replay 크기보다 많이 들어오면 바로 삭제된다.
- SharingStarted.Lazily: collector가 등록된 이후부터 sharing이 시작되며 중간에 중지되지 않는다. 첫 번째 collector는 그동안 emit 된 모든 값들을 얻으며, 이후의 collector는 replay 개수 만큼만 얻어간다. collector가 모두 없어지더라도 sharing는 동작을 유지한다. 다만 이때는 replay 개수 만큼만 cache 한다.
- SharingStarted.WhileSubscribed: collector가 등록되면 바로 sharing을 시작하며 collector가 전부 없어지면 바로 중지된다. 또한 replay 개수 만큼만 cache 한다.
stateIn을 사용할 때는 어떤 옵션을 사용하던지 큰 문제가 없었습니다. StateFlow의 특성상 collect를 시작하면 마지막에 저장된 값을 가져오기 때문에 어찌됐든 마지막 상태를 가져올 수 있었습니다.
하지만 shareIn을 사용하면 SharedFlow의 특성상 collect를 하고 있지 않을 때 발행된 값이 있다면 replay를 설정하지 않는 이상 데이터가 유실되는 상황이 생길 수 있습니다.
(ex: repeatOnLifecycle(Lifecycle.State.STARTED) + SharedFlow를 사용했다면 앱이 백그라운드에 있을 때 상태가 변경된 경우 값이 유실될 것이다.)
‼ 그런데 막상 repeatOnLifecycle(Lifecycle.State.STARTED) + SharingStarted.WhileSubscribed 의 조합으로 사용했을 때 데이터 유실이 발생하지 않았습니다. 그 이유는 바로 위에서 설명했던 것처럼 DataStore 내부에서 값을 StateFlow로 가지고 있기 때문에 WhileSubscribed 를 사용할 경우 collector가 등록되었을 때 sharing을 시작(StateFlow의 기본값을 가져옴)하기 때문입니다.
다만, Eagerly와 Lazily 는 collector가 없어도 동작하거나 모두 없어지더라도 sharing 동작을 유지하기 때문에 백그라운드에 갔을 경우 그 사이에 발행된 데이터가 유실될 수 있습니다. (다시 collect를 시작하는게 아니라 sharing을 계속 하고 있기 때문에 그 사이에 발행된 데이터는 replay를 설정해두지 않는 이상 유실된다.)
3. 정리
- DataStore는 데이터를 Flow 형식으로 리턴하는데 내부에서는 StateFlow를 가지고 있다.
- Flow에서 발행하는 값을 저장하여 쓰기 위해서는 SharedFlow나 StateFlow를 사용해야하고, 그러기 위해서 shareIn, stateIn 연산자를 사용한다.
- shareIn, stateIn 연산자의 started 옵션을 잘 조합해야 데이터 유실이 발생하지 않을 수 있다.
- repeatOnLifecycle(Lifecycle.State.STARTED) + Eagerly/Lazily 조합 조심 !!
'Android' 카테고리의 다른 글
Firebase 핸드폰 번호 인증 관련 GooglePlay 대응기 (0) | 2023.03.16 |
---|---|
새로운 버전의 로그캣 등장, 5가지 꿀기능을 알아보자! (0) | 2023.02.01 |
Firebase Crashlytics 적용하기 (1) | 2022.12.08 |
안드로이드 접근성 - 대체 텍스트를 적용해보자! (0) | 2022.11.22 |
안드로이드 저장소 권한의 변화와 새롭게 등장한 Photo Picker! (2) | 2022.10.27 |