Розробка системи лайків в мобільній програмі
Лайк здається тривіальним: тап — інкремент лічильника — іконка заповнилась. Але без оптимістичного оновлення кнопка «гальмує» 300-500ms очікуючи відповідь сервера, що суб'єктивно знищує ощущення від програми. А двойний тап або швидкі повторні натискання без debounce генерують зайві запити і можуть сломати лічильник.
Оптимістичне оновлення
Стандарт для соціальних програм — оновіти UI миттєво, не очікуючи відповіді сервера:
iOS (UIKit):
func toggleLike(for post: Post) {
let wasLiked = post.isLiked
// Миттєво міняємо UI
post.isLiked = !wasLiked
post.likesCount += wasLiked ? -1 : 1
updateCell(for: post)
// Запрос на сервер
apiService.toggleLike(postId: post.id) { [weak self] result in
if case .failure = result {
// Откат
post.isLiked = wasLiked
post.likesCount += wasLiked ? 1 : -1
self?.updateCell(for: post)
}
}
}
На Compose аналогічно: likedState у ViewModel міняється одразу, запрос йде паралельно, при помилці — StateFlow откатується до попереднього значення.
Debounce та захист від спаму
Швидкі двойні натискання потребують захисту. Найпростіший спосіб — флаг isRequesting: Bool на рівні ViewModel, що блокує повторний виклик до отримання відповіді. Для складніших випадків — debounce на 300ms: надішліть фінальний стан (liked/unliked), а не кожне натискання.
На Android з Kotlin Flow:
likeButtonClicks
.debounce(300)
.distinctUntilChanged()
.flatMapLatest { liked -> toggleLikeUseCase(postId, liked) }
.launchIn(viewModelScope)
Анімація
Анімація лайку — дрібниця, яку користувачі помічають. Instagram-підхід: сердечко «пружинить» при тапі. На iOS — UIView.animate(withDuration: 0.1, animations: { button.transform = CGAffineTransform(scaleX: 1.3, y: 1.3) }) { _ in UIView.animate(...) { button.transform = .identity } }. На Compose — animateFloatAsState з spring(dampingRatio = 0.4f).
Колір заповненого лайку через tintColor (iOS) або ColorFilter.tint (Compose). Іконка — SF Symbol heart / heart.fill на iOS, Material Icon на Android.
Лічильник та агрегація
Зберігайте likes_count як денормалізоване поле у таблиці поста — правильно. Не рахуйте SELECT COUNT(*) при кожному запиті стрічки. Інкремент/декремент через атомарний UPDATE posts SET likes_count = likes_count + 1 WHERE id = ? — без race condition.
Унікальність лайку: таблиця likes (user_id, post_id, PRIMARY KEY (user_id, post_id)). Дублі неможливі на рівні БД.
Часові рамки
Базова реалізація з оптимістичним оновленням та анімацією — 4 години – 1 день залежно від платформи. Вартість розраховується індивідуально.







