Как бороться с повторными быстрыми нажатиями, чтобы не отправлять несколько одинаковых запросов
Что проверяют
Понимаете ли вы, что двойное нажатие это проблема состояния, а не только кнопки, и что защищаться нужно на нескольких уровнях. Следом спрашивают, что будет при повороте экрана и как быть, если запрос всё равно ушёл дважды из-за повтора на уровне сети.
Как отвечать
Выстройте ответ слоями:
- состояние во
ViewModel: пока операция идёт, экран получаетisLoading = true, кнопка рисуется сenabled = false. СамаViewModelигнорирует повторную команду:if (submitJob?.isActive == true) return, затемsubmitJob = viewModelScope.launch { ... }. Это переживает поворот экрана, флаг вActivityне переживает; - троттлинг клика: собственное расширение вроде
setOnSingleClickListener, которое пропускает первое нажатие и отбрасывает следующие в течение 500 или 1000 мс. Время берите изSystemClock.elapsedRealtime(), а не изSystem.currentTimeMillis(), которое меняется при переводе часов. В Compose ту же обёртку ставят вокруг лямбдыonClick; - идемпотентность на сервере: клиент генерирует UUID один раз на операцию и шлёт его в заголовке вроде
Idempotency-Key, сервер по нему отбрасывает дубль. Только это защищает от повторов по таймауту, когда пользователь нажал один раз.
Отдельно про реактивные операторы. debounce для кнопок не подходит: он задерживает первое нажатие и срабатывает только после паузы. Нужен throttleFirst: в RxJava он есть, а в kotlinx.coroutines для Flow его пишут вручную. Для сериализации подойдёт Mutex.tryLock(): если замок занят, команда отбрасывается.
Где спотыкаются
Выключают кнопку и включают её обратно сразу после вызова асинхронного sendRequest(), не дождавшись ответа. Защита живёт несколько миллисекунд.
Создают новый Observable внутри каждого setOnClickListener, как в популярных примерах. У каждого клика своя цепочка, и throttleFirst ничего не отсекает.
Ограничиваются интерфейсом. Пользователь с плохой сетью нажал один раз, клиент по таймауту повторил запрос, и заказ создан дважды, потому что сервер ключ идемпотентности не проверяет.