How to correct call API search request with debounce?

65 views Asked by At

I tried to setup correct API call with debounce. Now I have function in ViewModel which call API search request with delay (debounce) and unique (latest) value, but as many as there are changes in the TextField in one time after debounce

This my code:

UI:

val searchedText by viewModel.searchText.collectAsState()

OutlinedTextField(
                modifier = Modifier
                    .fillMaxWidth()
                    .padding(15.dp),
                value = searchedText,
                onValueChange = { newText ->
                    viewModel.updateSearchedText(newText)
                    if (newText.isNotEmpty()) {
                        viewModel.getSearchedNotifications(5, 0)
                    } else {
                        viewModel.getAllNotifications(5, 0)
                    }
                },

ViewModel:

private val _searchText = MutableStateFlow("")
    val searchText: StateFlow<String> = _searchText

@OptIn(FlowPreview::class, ExperimentalCoroutinesApi::class)
    fun getSearchedNotifications(
        take: Int,
        skip: Int
    ) {
        viewModelScope.launch {
            _searchText
                .debounce(2000L)
                .distinctUntilChanged()
                .collectLatest { searched ->
                    val response = notificationsRepository.getNotifications(
                        take,
                        skip,
                        searched
                    )
                    when (response) {
                        is Resource.Success -> {
                            _notifications.update {
                                response.data
                            }
                        }

                        is Resource.Error -> {

                        }

                        else -> {}
                    }
                }
        }
    }

For example, I wrote in TextField "acc", delay 2 second and after that I had 3 API requests with same parametr acc. Expected that must be only one request with this parametr after user cancel entering and delay will overed.

1

There are 1 answers

2
Tenfour04 On

As mentioned in the Leviathan's comment, TextFields are very buggy with flows, so you should use State instead of StateFlow to hold your TextField's text.

And you are creating a new flow every time your function is called, which means you have multiple flows updating your _notifications State or Flow.

Instead, you can use your function to update a flow or flows that feed into a single flow that you create only once and make persistent using shareIn or stateIn. Something like this:

var searchText by mutableStateOf("")
    private set

private val notificationsTakeAndSkip = MutableSharedFlow<Pair<Int, Int>>(replay = 1)

@OptIn(FlowPreview::class, ExperimentalCoroutinesApi::class)
val notifications = snapshotFlow { searchText }
    .debounce(2000L)
    .distinctUntilChanged()
    .combineTransform(notificationsTakeAndSkip.distinctUntilChanged()) { searched, (take, skip) ->
        val response = notificationsRepository.getNotifications(
            take,
            skip,
            searched
        )
        when (response) {
            is Resource.Success -> emit(response.data)
            is Resource.Error -> { }
            else -> { }
        }
    }
    .shareIn(viewModelScope, SharingStarted.WhileSubscribed(5000L), replay = 1) 

fun setSearchedNotificationsParameters(take: Int, skip: Int) {
    notificationsTakeAndSkip.tryEmit(take to skip)
}