마침, 최근에 리딩중인 사이드 프로젝트에서 과제로 Flow 를 사용하는 법을 간단하게 정리하기로 하여서,
이 글을 작성하게 되었습니다.
[개요]
예전 Jatpack LiveData가 등장하였을 때 더 이상 observe 한 객체의 라이프사이클 처리를 개발자가 직접적으로 처리할 필요가 없어서 매우 편해짐에 따라 이후 안드로이드 개발에서 LiveData를 거의 필수적으로 사용하게 되었습니다. 하지만 LiveData 의 경우 LifeCycle 종속적이라는 문제가 생깁니다.
대표적으로 따라 data Layer, Domain Layer 에서 Room 라이브러리를 사용할 때 반환하는 객체가 LiveData<T> 일 경우 import androidx. 종속이 생깁니다.
또한 LiveData는 간편함을 제공하는 훌륭한 data holder 지만, Reactive programming 스럽게 코드를 짤 때 데이터를 변환하는 것이 map, switchMap이 다라고 할정도로 간단한 기능만을 제공하였습니다. (LiveData는 안드로이드 개발자가 LifeCycle 처리를 직접적으로 하지 않는것에 더 집중하였기 때문이라고 생각합니다.)
그래서 Google에서 only Kotlin을 추구하고 있고, Kotlin에서 지원하는 스마트한 비동기 처리인 Coroutine 을 활용하는 방법을 공식적으로 추가로 지원하기 시작합니다. (단점으로 Java의 활용도를 생각하지 않는 점에서 Java는 많이 배제가 되고 있습니다.)
Coroutine에 데이터 스트림을 만드는 Flow 가 있습니다. 생산자, 중개자(데이터 변환), 소비자 3단계로 나뉘어서 사용할 수 있게되며 기본적으로 Cold Stream 으로 되어있어 생산자, 중개자(옵션) 에서 방출하는 데이터를 준비시켜놓고, 1명의 소비자가 발생시 그 소비자에게만 데이터가 방출 됩니다.
이런 식으로 수도꼭지를 틀고 닫듯이 사용하는것이 Flow 입니다. 하지만 Cold Stream 특성이 사실 고정된 값을 보내기만 합니다. 그래서 State Pattren(State에 따라 UI에 반영하는) 을 주로 사용하는 클라단에서 사용하기 매우 불편한 상황입니다. 그래서 Flow를 Hot Stream 으로 사용할 수 있도록 State Flow, Shared Flow를 별도로 지원 합니다.
[과제 설명]
공통 사항
사이드 팀 개발 인원 각자 개인 블로그에 Android Flow 관련 코드를 작성하여, 때 제출합니다. 공통적으로 아래 상황에서 3가지 주제를 진행합니다.
//Blog <- data ,domain 에서 사용
data class Blog(
val title:String ,
val content:String,
// 블로그 출처
val type:???
)
UiBlog <- view, viewModel 사용
data class UiBlog(
val blog:Blog,
val isSelected:Boolean
)
/**data/domainModule 에서 ui 모듈로 데이터 이동시
*ViewModel에서 UiBlog로 변환하여 사용하기*/
1) Flow를 이용하여 데이터 상태에 따라 버튼 활성화 비활성화 변경하기
//예시: 맛집을 추가할 때 필수 항목을 적어야 완료버튼이 활성화 한다는 가정
/**예시는 여러분들이 마음대로 만들어내셔서 하면 좋을것 같습니다.*/
class ViewModel(val useCase:UseCase...){
val requireDatas:StateFlow<T>()..
val enableBtn = requireDatas.map{..}.filter{..}
or
val title:StateFlow<String>()..
val image:StateFlow<Image>()...
val enableBtn = title.combine(image){...}...
}
- 다양한 예시를 만들어서 많은 코드를 작성 할수록 좋습니다
2) Flow에서 Error Catch 및 onCompletion() 를 이용한 예제
// 예시: 맛집 후기(블로그) 목록을 조회할 때 페이징처리를 할때, 페이징 도중에 에러가 났는지 체크
// 와 마지막 페이지의 마지막 데이터를 조회할때
class ViewModel(val useCase:UseCase ...){
val blog:StateFlow<UiBlog>() =
useCase.toUiBlogModel.error{..}
.onCompetion{
if(isLast){
showToast("마지막 페이지 입니다.")
}
}
}
3) StateFlow, SharedFlow, Chnnel 이 3가지를 활용한 코드 작성
//예시: StateFlow 를 통해서 아이템 목록 Ui 보여주려고 할때 등등.
val uiState:StateFlow<T>...
Activity{ viewModel.uiState.collect { ... }}
//예시: SharedFlow 를 이용해서 이벤트 처리 하려고 할때 등등..
val uiEvent:SharedFlow<T>...
//예시: Channel 를 이용해서 이벤트 처리 하려 할때 등등..
val event:Channel<T>...
위 3가지 숙제를 위해 추가 설정을 임의로 붙이셔도 상관 없습니다.
[과제 제출]
전체 코드: https://github.com/LonerStayle/SimpleFlowSample
1) Flow를 이용하여 데이터 상태에 따라 버튼 활성화 비활성화 변경하기
제출: 네이버 , 다음 Api를 통해 각각 데이터를 불러와서 UI로 보여주고, 각각 아이템마다 선택 할 수 있는 체크박스가 있습니다. 어떤 아이템을 클릭했는지에 따라 버튼 활성화 여부가 달라집니다.
//1번째 숙제 화면에서만 사용하는 FirstHomeWorkUiBlogState ui모델을 정의하여 사용했습니다.
private val _uiBlogState = MutableStateFlow(FirstHomeWorkUiBlogState.initData)
val firstHomeWorkUiBlogState: StateFlow<FirstHomeWorkUiBlogState> = _uiBlogState
- 네이버 블로그를 선택 했을 때
val completeOnlyNaverSelect: StateFlow<Boolean> = firstHomeWorkUiBlogState.map {
val resultList = it.uiBlogGroup.value.filter { uiBlog ->
uiBlog.isSelected && uiBlog.blog.type == BlogType.Naver
}
resultList.isNotEmpty()
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), false)
-------------------------------------------------------------------------------------------------------------------------------------------
- 다음 블로그를 선택 했을 때
val completeOnlyDaumSelect: StateFlow<Boolean> = firstHomeWorkUiBlogState.map {
val resultList = it.uiBlogGroup.value.filter { uiBlog ->
uiBlog.isSelected && uiBlog.blog.type == BlogType.Daum
}
resultList.isNotEmpty()
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), false)
------------------------------------------------------------------------------------------------------------------
- 다음, 네이버 블로그 둘다 선택했을 때
val completeComplexSelect: StateFlow<Boolean> = firstHomeWorkUiBlogState.map { uiBlog ->
val selectUiBlogs = uiBlog.uiBlogGroup.value.filter { it.isSelected }
selectUiBlogs.find { it.blog.type == BlogType.Naver } != null
&& selectUiBlogs.find { it.blog.type == BlogType.Daum } != null
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), false)
-----------------------------------------------------------------------------------------------------
- 블로그 전체 4개이상 선택 했을 때,
val completeFourCountSelect: StateFlow<Boolean> = firstHomeWorkUiBlogState.map {
it.uiBlogGroup.value.filter { it.isSelected }.size >= 4
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), false)
2) Flow에서 Error Catch 및 onCompletion() 를 이용한 예제
제출: onCompletion()은 Job이 완료 되어야 실행 됩니다. flow는 collect를 하고 있어서 잠시 테스트를 위해 onPause()에서 job.cancle 처리를 해주었습니다.
private val job = lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.blogList.onCompletion {
viewModel.showToast(SecondHomeWorkViewModel.ShowMsg("Job 처리 완료."))
}.collect {
binding.rvBlogs.adapter = SecondHomeWorkAdapter(it)
}
}
}
override fun onCreate(savedInstanceState: Bundle?) {
...
setAdapter()
..
}
private fun setAdapter() {
job.start()
}
override fun onPause() {
...
job.cancel()
}
3) StateFlow, SharedFlow, Chnnel 이 3가지를 활용한 코드 작성
제출: 1,2번 과제에 녹아들어있으므로 따로 작성하지 않았습니다.
'안드로이드 Android' 카테고리의 다른 글
안드로이드 개발 (39) Kotlin In Action 정리 - 3 (0) | 2023.02.12 |
---|---|
안드로이드 개발 (37) Kotlin In Action 정리 - 1 (2) | 2023.02.05 |
안드로이드 개발(35) 안드로이드 설계 - Layered Architecture (2) | 2022.03.19 |
안드로이드 개발 (34) RecyclerView 성능 향상 (6) | 2022.01.08 |
안드로이드 개발 (33) Coroutine Flow on Android (2) | 2021.12.06 |