Reputation: 768
In my application I want use MVI for application architecture and for this I used StateFlow instead of LiveData.
I want when click on button call method with StateFlow.
I write below codes, after click on button show me log in viewModel but not update in Activity
!
ViewModel codes :
@HiltViewModel
class MainViewModel @Inject constructor(private val repository: MainRepository) : ViewModel() {
val mainIntent = Channel<MainIntent>()
private val _state = MutableStateFlow<MainState>(MainState.Empty)
val state: StateFlow<MainState> get() = _state
init {
handleIntent()
}
private fun handleIntent() {
viewModelScope.launch {
mainIntent.consumeAsFlow().collect {
when (it) {
is MainIntent.LoadAllNotes -> fetchingAllNotesList()
is MainIntent.FilterNote -> fetchingFilterNotes(it.filter)
}
}
}
}
private fun fetchingAllNotesList() {
_state.value = MainState.LoadNotes(repository.allNotes())
}
private fun fetchingFilterNotes(filter: String) {
_state.value = MainState.Empty
Log.e("DataLog", "Empty from viewmodel")
}
}
Activity codes :
lifecycleScope.launch {
//Send
viewModel.mainIntent.send(MainIntent.LoadAllNotes)
//Get
viewModel.state.collect { state ->
when (state) {
is MainState.Empty -> {
Log.e("DataLog", "Empty on activity")
emptyLay.visibility = View.VISIBLE
notesList.visibility = View.GONE
}
is MainState.LoadNotes -> {
Log.e("DataLog", "Load notes")
emptyLay.visibility = View.GONE
notesList.visibility = View.VISIBLE
state.list.collect {
noteAdapter.setData(it)
notesList.apply {
layoutManager = StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL)
adapter = noteAdapter
}
}
}
}
}
}
After click on button show me Empty from viewmodel message into logcat
, but not call this method is MainState.Empty from activity
!
How can I fix this problem?
Upvotes: 1
Views: 1054
Reputation: 93541
I can't spot the error in your code, but it is more convoluted than it needs to be. Maybe if you try simplifying it, the problem will present itself more clearly or will have been resolved.
Instead of using a Channel to take commands from the Activity, the Activity can just call your functions directly. The below has the exact same functionality as you had before, but it is much simpler logic and less code.
@HiltViewModel
class MainViewModel @Inject constructor(private val repository: MainRepository) : ViewModel() {
private val _state = MutableStateFlow<MainState>(MainState.Empty)
val state: StateFlow<MainState> get() = _state
fun fetchAllNotes() {
_state.value = MainState.LoadNotes(repository.allNotes())
}
fun fetchFilteredNotes(filter: String) {
_state.value = MainState.Empty
Log.e("DataLog", "Empty from viewmodel")
}
}
// In your activity code, change the send line:
//Send
viewModel.fetchAllNotes()
Also, I think there's another layer of complexity you can remove, but I can't be certain because I don't know the details of how your repository works. But I think it would be simpler if your state flow was just a flow of Lists of notes instead of having this MainState sealed class wrapper and a nested Flow inside that. The collect
call inside another collect
call may cause the first collect
call to never finish processing the first item because the inner collect
call never returns, so it could lock up your coroutine and cause your problem with never publishing anything to your UI.
If we assume your current MainState's list is a List<Note>
, here's how I'd refactor your code to completely eliminate this extra level of complexity:
@HiltViewModel
class MainViewModel @Inject constructor(private val repository: MainRepository) : ViewModel() {
private val fetchFlow = MutableStateFlow<Flow<List<Note>>>(emptyFlow())
val notes: SharedFlow<List<Note>> = fetchFlow
.flatMapLatest { it }
.shareIn(viewModelScope, SharingStarted.WhileSubscribed(5000), emptyList())
fun fetchAllNotes() {
fetchFlow.value = repository.allNotes()
}
fun fetchFilteredNotes(filter: String) {
fetchFlow.value = flowOf(emptyList()) //TODO get filtered flow using filter
Log.e("DataLog", "Empty from viewmodel")
}
}
Then in your activity:
lifecycleScope.launch {
//Send
viewModel.fetchAllNotes()
//Get
viewModel.notes.collect { list ->
when {
list.isEmpty() -> {
Log.e("DataLog", "Empty on activity")
emptyLay.visibility = View.VISIBLE
notesList.visibility = View.GONE
}
else -> {
Log.e("DataLog", "Load notes")
emptyLay.visibility = View.GONE
notesList.visibility = View.VISIBLE
noteAdapter.setData(list)
notesList.apply {
layoutManager = StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL)
adapter = noteAdapter
}
}
}
}
}
Upvotes: 2