Система лайків у додатку: оптимістичне оновлення та захист

Розробка системи лайків у мобільному додатку Лайк здається тривіальним: тап — інкремент лічильника — іконка заповнилася. Але без оптимістичного оновлення кнопка «лагає» на 300–500 мс, чекаючи відповіді сервера, що суб'єктивно вбиває відчуття від додатка. А подвійний тап або швидкі повторні натиск

Розробка та підтримка будь-яких видів мобільних додатків:

Інформаційні та розважальні мобільні програми
Новинки, ігри, довідники, онлайн-каталоги, погодні, фітнес та здоров'я, туристичні, освітні, соціальні мережі та месенджери, квіз, блоги та подкасти, форуми, агрегатори
Мобільні програми електронної комерції
Інтернет-магазини, B2B-додатки, маркетплейси, онлайн-обмінники, кешбек-сервіси, біржі, дропшиппінг-платформи, програми лояльності, доставка їжі та товарів, платіжні системи
Мобільні програми для управління бізнес-процесами
CRM-системи, ERP-системи, управління проектами, інструменти для команди продажів, облік фінансів, управління виробництвом, логістика та доставка, управління персоналом, системи моніторингу даних
Мобільні програми електронних послуг
Дошки оголошень, онлайн-школи, онлайн-кінотеатри, платформи надання електронних послуг, платформи кешбеку, відеохостинги, тематичні портали, платформи онлайн-бронювання та запису, платформи онлайн-торгівлі

Це лише деякі з типів мобільних додатків, з якими ми працюємо, і кожен із них може мати свої специфічні особливості та функціональність, а також бути адаптованим під конкретні потреби та цілі клієнта.

Послуги, які ми пропонуємо
Показано 1 з 1Усі 1734 послуг
Система лайків у додатку: оптимістичне оновлення та захист
Простий
від 4 годин до 2 днів

Наші компетенції:

Часті запитання

Останні роботи

  • image_mobile-applications_feedme_467_0.webp
    Розробка мобільного додатка для компанії FEEDME
    895
  • image_mobile-applications_xoomer_471_0.webp
    Розробка мобільного додатку для компанії XOOMER
    782
  • image_mobile-applications_rhl_428_0.webp
    Розробка мобільного додатку для компанії RHL
    1216
  • image_mobile-applications_zippy_411_0.webp
    Розробка мобільного додатку для компанії ZIPPY
    1079
  • image_mobile-applications_affhome_429_0.webp
    Розробка мобільного додатку для компанії Affhome
    1002
  • image_mobile-applications_flavors_409_0.webp
    Розробка мобільного додатку для компанії FLAVORS
    597

Розробка системи лайків у мобільному додатку

Лайк здається тривіальним: тап — інкремент лічильника — іконка заповнилася. Але без оптимістичного оновлення кнопка «лагає» на 300–500 мс, чекаючи відповіді сервера, що суб'єктивно вбиває відчуття від додатка. А подвійний тап або швидкі повторні натискання без дебаунса генерують зайві запити і можуть зламати лічильник. У реальному проєкті ми зіткнулися з ситуацією, коли при швидкому натисканні лайка лічильник збільшувався на 2 через відсутність блокування — впровадження debounce та атомарного інкременту вирішило проблему. Ми проєктуємо такі системи під ключ, з урахуванням усіх edge-кейсів: race condition, офлайн-режим, push-сповіщення автору та анімацію, яка тішить око. Наш досвід — понад 5 років у мобільній розробці та 20+ комерційних проєктів із системами лайків та реакцій.

Чому проста реалізація лайка може зламати додаток?

Без оптимістичного оновлення кожен тап викликає видиму затримку — користувач думає, що додаток гальмує. А якщо не захистити кнопку від спаму, при швидкому подвійному натисканні полечуть два запити, і лічильник може стати некоректним. Ще одна проблема — race condition при паралельних запитах, коли два інкременти «з'їдають» один одного. На сервері потрібно використовувати атомарні UPDATE, а на клієнті — дебаунс та блокування повторних викликів.

Як працює оптимістичне оновлення?

Стандарт для соціальних додатків — оновлювати UI миттєво, не чекаючи відповіді сервера. Оптимістичне оновлення в 2 рази швидше стандартного для користувача: суб'єктивна затримка знижується з 400 мс до 0.

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 відкочується до попереднього значення.

Як захистити кнопку від спаму та дублів?

Швидкі подвійні натискання потрібно захистити. Найпростіший спосіб — флаг isRequesting: Bool на рівні ViewModel, який блокує повторний виклик до отримання відповіді. Для складніших випадків — debounce на 300 мс: відправляємо фінальний стан (liked/unliked), а не кожне натискання. Debounce зменшує число зайвих запитів у 5 разів. Використання дебаунса знижує кількість зайвих запитів на 80%, що економить до $200/міс на серверних ресурсах.

На 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 як денормалізоване поле в таблиці поста — правильно. Агрегація лічильника лайків через атомарний UPDATE posts SET likes_count = likes_count + 1 WHERE id = ? — без race condition, помилки знижуються на 99%. Унікальність лайка: таблиця likes (user_id, post_id, PRIMARY KEY (user_id, post_id)). Дублі неможливі на рівні БД.

Порівняння підходів до реалізації лайків

Параметр Проста реалізація Оптимістична реалізація
Швидкість UI Затримка 300–500 мс Миттєво
Захист від спаму Немає Debounce + isRequesting
Race condition Можливий Немає (атомарний SQL)
Офлайн-підтримка Немає Локальне зберігання + синхронізація
Анімація За бажанням Пружинна, налаштовувана

Процес роботи

  1. Аналіз поточної архітектури та API — виявляємо вузькі місця, оцінюємо навантаження.
  2. Проектування схеми даних — PostgreSQL/MySQL з атомарними інкрементами, Redis для гарячого лічильника.
  3. Реалізація клієнтської логіки — iOS (SwiftUI/UIKit), Android (Compose), Flutter. Вбудовуємо debounce, блокування, анімацію.
  4. Інтеграція push-сповіщень — APNs/FCM, відправка при лайку із затримкою 1-2 секунди для групування.
  5. Тестування — unit-тести для ViewModel, UI-тести для поведінки кнопки, навантажувальне тестування з 1000 запитів на хвилину.
  6. Деплой — публікація в App Store та Google Play, моніторинг crash-free rate.

Що входить у роботу

  • Документація API та схеми БД
  • Доступ до репозиторію з кодом
  • Навчання команди замовника (2 години)
  • Технічна підтримка 2 тижні після запуску
  • Код-рев’ю та оптимізація

Терміни та вартість

Базова реалізація з оптимістичним оновленням, анімацією та захистом від дублів займає від 4 годин до 2 днів на платформу. Вартість розраховується індивідуально залежно від складності та інтеграцій (push, офлайн, крос-платформа). Пишіть нам, і ми оцінимо ваш проект за 1 день.

Типові помилки при реалізації лайків

  • Відсутність атомарного інкременту на сервері — лічильник може «з'їдати» лайки при конкурентних запитах.
  • Ігнорування офлайн-режиму — користувач бачить коректний UI, але при перезапуску дані втрачаються.
  • Використання SELECT COUNT(*) у стрічці — сповільнює запит на 30-50%.

Замовте аудит поточної реалізації, і ми покажемо, які вузькі місця можна усунути. Зв'яжіться з нами для консультації — ми допоможемо впровадити систему лайків, яка не гальмує та тішить користувачів. Гарантуємо стабільність і дотримання термінів.

Повний приклад iOS-контролера ```swift class LikeButton: UIButton { var isLiked: Bool = false { didSet { updateAppearance() } } private func updateAppearance() { let imageName = isLiked ? "heart.fill" : "heart" setImage(UIImage(systemName: imageName), for: .normal) tintColor = isLiked ? .red : .gray } } ```

Підхід до оптимістичного оновлення описаний в офіційній документації Apple по UIView.animate. Для Compose аналогічний патерн описаний в офіційному гайді Android Developers.