Scenario
List<Message>
in a composablemessages = mutableStateListOf<Message>
in viewModelmessages
depends on two actionsgetAll
orsearch
- In both cases we observe SQLite, but only one flow should be active at a single instant (user is either viewing all or is searching)
- Flow is used with SQLite because new messages can come from the network.
Questions
- How to switch between
getAll
andsearch
flow - When a new search happens the old
search
flow should be cancelled and a new one should start. How to do this?
Current implementation with two flows
@Composable
fun ListOfMessages() {
val messages = viewModel.messages
}
// -----------------------------
// ViewModel
// -----------------------------
@ExperimentalCoroutinesApi
class MessageViewModel(application: Application) {
val messages = mutableStateListOf<Message>()
val isLoading = mutableStateOf(false)
init {
fetchMessages()
}
fun fetchMessages() {
MessageUseCase(messagesDB).getAll().onEach { dataState ->
isLoading.value = dataState.loading
dataState.data?.let { data ->
messages.clear()
messages.addAll(data)
}
dataState.error?.let { error ->
// UIState -> error message
}
}.launchIn(viewModelScope)
}
fun SearchWithInMessage(q: String) {
MessageUseCase(messagesDB).search(q).onEach { dataState ->
isLoading.value = dataState.loading
dataState.data?.let { data ->
messages.clear()
messages.addAll(data)
}
dataState.error?.let { error ->
// UIState -> error message
}
}
}
}
// -----------------------------
// Use Cases
// -----------------------------
class MessageUseCase(messagesDB: messageDao) {
@ExperimentalCoroutinesApi
fun getAll(): Flow<DataState<List<Message>>> = channelFlow { {
send(DataState.loading())
try {
fetchAndSaveLatestMessagesFromRemote()
val messages = messagesDB.getAllStream()
messages.collectLatest { list ->
// business logic
send(DataState.success(list))
}
} catch (e: Exception){
send(DataState.error<List<Message>>(e.message?: "Unknown Error"))
}
}
@ExperimentalCoroutinesApi
fun search(q: String): Flow<DataState<List<Message>>> = channelFlow { {
send(DataState.loading())
try {
fetchAndSaveSearchedMessageFromRemote(q)
val messages = messagesDB.searchStream(q)
messages.collectLatest { list ->
// business logic
send(DataState.success(list))
}
} catch (e: Exception){
send(DataState.error<List<Message>>(e.message?: "Unknown Error"))
}
}
}
// -----------------------------
// DB and Network calls
// -----------------------------
CodePudding user response:
You can use flatMapLatest(). It receives a flow of flows and always replicate items of the latest flow sent to it, cancelling previous flow. As a result, it works as the flow that can switch between other flows.
Depending on how your getAll()
and search()
are declared, it could be something like this:
private val state = MutableStateFlow<String?>(null)
private val messages = state.flatMapLatest {
if (it == null) getAll() else search(it)
}
suspend fun requestSearch(search: String) = state.emit(search)
suspend fun requestAll() = state.emit(null)
fun getAll(): Flow<List<Message>> = TODO()
fun search(search: String): Flow<List<Message>> = TODO()
state
represents our currently required search state. Whenever we emit to it, it requests either getAll()
or search()
flow and replicates it as messages
flow.
Then, to use it with Compose, you need to convert it to State
:
messages.collectAsState(emptyList())