Skip to content

Instantly share code, notes, and snippets.

@gbajaj
Created July 3, 2025 08:37
Show Gist options
  • Save gbajaj/1ab99cdb67f97ae7c21409fdd31cc713 to your computer and use it in GitHub Desktop.
Save gbajaj/1ab99cdb67f97ae7c21409fdd31cc713 to your computer and use it in GitHub Desktop.
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
/**
* A ViewModel to manage the logic of a single item in our shopping cart.
*/
class ShoppingCartItemViewModel {
// A private MutableStateFlow to hold the current quantity of the item.
// This will be updated by the UI when the user clicks the '+' or '-' buttons.
private val _quantity = MutableStateFlow(1)
init {
// This is where the magic happens. We create a coroutine scope
// to collect the flow of quantity changes.
val scope = CoroutineScope(Dispatchers.Default)
scope.launch {
_quantity
// First, we skip the initial value of 1, as we don't need to validate it on startup.
.drop(1)
// THIS IS THE SOLUTION: We apply the debounce operator.
// It will wait for 300 milliseconds of silence before emitting the latest value.
// If a new value arrives within that 300ms window, the timer resets.
.debounce(300L)
.collect { qty ->
// This block will only execute after the user has stopped changing the quantity.
println("\n-----------------------------------------")
println("USER PAUSED. Final quantity is $qty.")
println("Updating cart...")
updateCart(qty)
println("-----------------------------------------\n")
}
}
}
/**
* Simulates the user updating the quantity. In a real app, this would be
* called by the onClick listeners of the '+' and '-' buttons.
*/
fun updateQuantity(newQuantity: Int) {
if (newQuantity > 0) {
println("User set quantity to: $newQuantity")
_quantity.value = newQuantity
}
}
/**
* Simulates the expensive operations (network calls, calculations).
* We add delays to represent the time these operations would take.
*/
private suspend fun updateCart(quantity: Int) {
// 1. Validate Inventory (simulated network call)
println("1. Checking inventory for $quantity items...")
delay(150)
println(" -> Inventory confirmed.")
// 2. Recalculate Totals
println("2. Recalculating totals...")
delay(50)
println(" -> Totals updated.")
// 3. Check for Promotions (simulated network call)
println("3. Checking for promotions...")
delay(200)
println(" -> No new promotions applied.")
println("Cart update complete!")
}
}
/**
* The main function to run our simulation.
*/
fun main() = runBlocking {
val viewModel = ShoppingCartItemViewModel()
println("User is rapidly clicking the '+' button...")
// Simulate a user clicking '+' 5 times in quick succession.
// Notice how each quantity change is printed immediately, but the
// expensive `updateCart` function is not called for each one.
for (i in 2..6) {
viewModel.updateQuantity(i)
delay(150) // User clicks every 150ms
}
// Keep the main function alive long enough to see the debounced result.
delay(1000)
println("Simulation finished.")
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment