Home > Blockchain >  How to start a multiple flows that can be "switched off" when not needed?
How to start a multiple flows that can be "switched off" when not needed?

Time:11-07

Scenario

  1. List<Message> in a composable
  2. messages = mutableStateListOf<Message> in viewModel
  3. messages depends on two actions getAll or search
  4. 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)
  5. Flow is used with SQLite because new messages can come from the network.

Questions

  1. How to switch between getAll and search flow
  2. 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())
  • Related