Розроблення системи відзивів та рейтингів у мобільному додатку
Без системи відзивів додаток втрачає один з головних інструментів соціального доказу — користувачи не бачать чужого досвіду й не залишають свого. Але зробити її правильно складніше, ніж здається: агреговано рейтинг з перекосом через кілька ранніх відзивів, фотографії, які гружаться 4 секунди, або модерація, яку обходять боти — усе це типічні проблеми, з якими приходять на доробку.
Що зазвичай ломається в самописних реалізаціях
Агрегація та оновлення рейтингу
Найчастіша помилка — вираховувати середній рейтинг на льоту SELECT AVG(rating) по всій таблиці відзивів при кожному запиті сторінки продукту. При 50 000 відзивів це починає гальмувати. Правильний підхід: денормалізовано поле average_rating та reviews_count на стороні сервера, оновлюване через триггер або чергу (Celery/Sidekiq/BullMQ) при додаванні/зміні/видаленні відзиву. Клієнт отримує вже готове значення.
На мобілі рейтинг потрібно відображати як зірковий індикатор — iOS та Android реалізують його по-різному. У UIKit будуємо кастомний UIView з CALayer-масками або складаємо з п'яти UIImageView зі стерами .full, .half, .empty. У Jetpack Compose — Row з Icon й обчисленням через floor/ceil дробового значення. Анімуємо заповнення при першому завантаженні через withAnimation (Compose) або UIView.animate зі зміною ширини clip-mask.
Пагінація та infinite scroll у списку відзивів
Класичний OFFSET/LIMIT працює погано при великій кількості відзивів — на 10 000-й сторінці база все одно сканує весь індекс до потрібного зміщення. Використовуємо cursor-based pagination: сортуємо по created_at DESC, id DESC, у відповіді повертаємо next_cursor (base64 від останнього id + timestamp), наступний запит передає його як параметр.
На iOS список будуємо на UICollectionView з UICollectionViewDiffableDataSource — додавання нової сторінки через applySnapshot без мерцання. prefetchDataSource запрошує наступну сторінку коли залишається 3-4 клітини. На Android — LazyColumn з LazyPagingItems з Paging 3.
Фото до відзиву
Завантаження фото напрямку через основний API — антипаттерн. Правильна схема: клієнт запрошує presigned URL у S3-сумісному сховищі (AWS S3, Cloudflare R2, MinIO), завантажує файл напрямку туди, потім передає в API тільки ключ об'єкту. Стиснення перед завантаженням — на клієнті: iOS через UIImage.jpegData(compressionQuality: 0.75), Android через Bitmap.compress(Bitmap.CompressFormat.JPEG, 75, outputStream). Ліміт — 2-3 фото, максимум 5 МБ на файл після стиснення.
Відображення — через Kingfisher (iOS) або Coil (Android) з placeholder й crossfade 200ms. Для галереї при таяпі — модальний UIPageViewController або HorizontalPager у Compose з pinch-зумом.
Як улаштована повна реалізація
Структура даних. Відзив містить: user_id, entity_id (продукт, послуга), entity_type, rating (1-5), body (текст, опціонально), photos[], status (pending/approved/rejected), helpful_count, created_at. Індекси: (entity_id, entity_type, status, created_at DESC) для вибірки схвалених відзивів по об'єкту.
Модерація. Автоматичний pre-filter через профанітіфільтр (бібліотека bad-words або кастомний список на бекенді) + флаг на ручну перевірку для відзивів з ключовими словами. Фото проходять через AWS Rekognition Moderation Labels або Google Cloud Vision SafeSearch перед публікацією. У панелі модератора — черга з approve/reject й можливістю відповісти на відзив.
Відповідь на відзив. Бізнес відповідає на відзив — це окрема сутність review_reply (один до одного з review). При публікації відповіді — push-сповіщення автору через FCM/APNs з deeplink на відзив.
Голосування «корисно». helpful_votes — окрема таблиця (user_id, review_id, UNIQUE). Ліміт: один голос з одного акаунту. На клієнті — оптимістичне оновлення лічильника з відкатом при помилці.
Верифікація покупки. Якщо платформа дозволяє — позначаємо відзиви від реальних покупців значком «Підтверджена покупка», перевіряючи наявність закритого заказу з user_id та entity_id.
Етапи роботи
Аудит поточної реалізації (якщо є) → проектування схеми даних та API → розроблення бекенду → мобільний UI (обидві платформи або одна) → інтеграція модерації → тестування нагрузкою (Artillery/k6 на сценарій «500 одночасних відзивів») → публікація.
Для Flutter-проектів весь UI — один раз, логіка виділена в ReviewBloc (BLoC) або ReviewNotifier (Riverpod).
Терміни
Базова система (зірковий рейтинг, текстовий відзив, список з пагінацією, модерація через статус) — 3-5 робочих днів. З фотографіями, відповідями бізнесу, голосуванням та верифікацією покупки — 8-12 днів. Вартість розраховується індивідуально після аналізу вимог.







