토이프로젝트 1-10 flow, scope 이용하기
이번에는 제대로 코루틴을 써보자고.
룸에서 지원하는 기능중에 데이터를 그냥 받는것도 있지만, flow나 livedata로 만들어주는 기능이 있어. 그걸 이용해보려고 해
먼저 flow가 뭔지 궁금하면...
https://lonewhite.tistory.com/33
코루틴에서 Flow가 뭐야?
일단 정의 먼저 보면, "코틀린의 플로우는 순차적으로 값을 내보내고 정상적으로 또는 예외로 완료되는 비동기적인 데이터 스트림입니다." 정의만 봐도 대충 뭔소린지는 알겠지??? rx를 써 봤으
www.lonewhite.com
방법은 간단해
그냥 아래처럼... 넘겨주는걸 flow로만 바꾸면 끝!
import kotlinx.coroutines.flow.Flow
@Dao
interface InvItemDao {
// @Query("SELECT * FROM $INV_ITEM_TABLE_NAME")
// fun getAll(): List<InvItem>
@Query("SELECT * FROM $INV_ITEM_TABLE_NAME")
fun getAll(): Flow<List<InvItem>>
@Insert
fun insertAll(vararg invItems: InvItem)
@Delete
fun delete(item: InvItem)
}
주석이랑 차이점을 보면 정말 Flow만 추가한게 끝이야.
참고로 꼭 flow 를 import 하려고 보면 여러개가 나올텐데,
꼭 kotlinx.coroutines.flow.Flow 이걸 이용해야해
그럼 이제 어떻게 쓰는지 알아볼까??
일단 기존에 콜하던 repository 코드도 flow를 넘기게 아래처럼 바꾸고..
class Repository(val dao: InvItemDao) {
// fun getAllData(): List<InvItem> {
// return dao.getAll()
// }
fun getAllData(): Flow<List<InvItem>> {
return dao.getAll()
}
fun insertData(invItem: InvItem) {
dao.insertAll(invItem)
}
}
이제 view 모델에서 어찌 해야 하는지 알아 볼께.
일단 큰 그림은 저 flow 자체를 viewmodel에서 넘기는게 아니라... 개수만 넘길꺼야.
지난 과정을 보면 DB에 몇개가 저장되어 있는지만 표시했자나. 그걸 동일하게 할껀데, 지금 room에서 넘겨주는 flow는 개수가 아니라 전체 데이터자나.
그래서 viewmodel에서도 개수만 업데이트 하는 flow를 만들꺼야.
flow를 만드는 방법은 간단해
StateFlow랑 SharedFlow가 있는데 지금 당장 중요한게 아니니... 일단 아래처럼 그냥 StateFlow를 만들고... View에서 가져가는 코드를 만들자.
class InvestItemViewModel @Inject constructor(
private val repository: Repository
) : ViewModel() {
// fun getItemCount(): Int {
// return repository.getAllData().size
// }
private val _countFlow = MutableStateFlow<Int>(0)
val countFlow = _countFlow.asStateFlow()
}
즉 저 countFlow는 int를 꾸준히? 넘겨주는 flow가 되는거지.
그럼 새로만든 countFlow와 room에서 가져온 list를 넘겨주는 flow를 어떻게 연결지을까???
설명부터 하면, list를 넘겨주는 flow를 collect하고 그 내부에서 countFlow에 데이터를 넘겨주는거지 아래처럼
init {
viewModelScope.launch {
repository.getAllData().collect {
_countFlow.emit(it.size)
}
}
}
private val _countFlow = MutableStateFlow<Int>(0)
val countFlow = _countFlow.asStateFlow()
시작하자마자 DB를 읽어오고... DB 변경이 있어서 collect 내부가 불리게 될때마다 countFlow에 값을 넘겨주는거지.
viewModelScope가 뭔지 궁금하지?
https://lonewhite.tistory.com/34
여튼 그래서 저 viewModel에 연동되는 Scope 단위이고... 거기서만 실행되고 viewModel이 끝나면 알아서 끝나는 scope인거지.
다시 설명하면.. room에서는 리스트를 넘겨주는 flow였고,
그걸 collect 하면서 size를 넘겨주는 flow를 새로 하나 만든거야.
그러면 이제 View에서는 어떻게 써야 할까??
기존에는 button1을 누르면 사이즈를 가져와서 textview에 업데이트 했다면, 이제는 시작하자마자 lifecycle에 맞춰서 scope를 가져와서 countFlow를 collect 하게 하는거지. 그리고 viewModel에서는 아까 init에 넣었으니깐 시작하자마자 DB에서 list를 collect하고 변경 있으면 countFlow를 업데이트 하고...
아래처럼 말이지
class TestFragment : Fragment() {
private lateinit var binding: FragmentTestBinding
@Inject
lateinit var factory: ViewModelProvider.Factory
private val viewModel: InvestItemViewModel by viewModels { factory }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.countFlow.collect() {
binding.textView.text = it.toString()
}
}
}
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
(requireActivity().application as FinApplication).applicationComponent.inject(this)
binding = FragmentTestBinding.inflate(layoutInflater)
val view = binding.root
binding.button2.setOnClickListener{
val invItem = InvItem("test1", null, 55, Date(235235L), null)
viewModel.insert(invItem)
}
return view
}
}
이제 버튼 누를때마다 DB가 변경되고, 그러면 countFlow내의 코드가 돌면서 뷰가 업데이트 되는거야.
그럼 빌드해볼까?
근데 바로 에러...
room에서 flow를 가져올려면,
implementation "androidx.room:room-ktx:$room_version"
요걸 꼭 해야함!
음 이제 다음은... usecase를 써볼꺼야. viewModel이 넘흐 커지고 막강해지면 안되니깐.. usecase한테 좀 넘기는? 그런거야. 그 usecase는 다른데서 또 쓸 수도 있고 말이지. 담에 시간되면 업데이트 할께~