Розробка мобільного приложення для голосувань та опитувань
Голосування в мобільному приложенні — завдання, де технічна складність приховується за простим інтерфейсом. Реальні вимоги: один користувач — один голос (запобігання накруткам), результати в реальному часі для тисяч одночасних учасників, коректна робота при нестабільній мережі, іноді — анонімність із верифікованою ідентичністю. Це не «форма з кнопкою» — це система з вимогами до цілісності даних.
Цілісність голосування: як не дати накрутити
Найкритичніша частина — ідемпотентність голосу. Користувач не повинен проголосувати двічі навіть при двійному натиску, втраті мережі в момент відправки або переінсталяції приложення.
На клієнті: оптимістичний UI — відразу показуємо вибір користувача, блокуємо повторний тап, відправляємо голос на сервер. При помилці мережі — ставимо в чергу та повторюємо з експоненціальним backoff.
На сервері: унікальний constraint (poll_id, user_id) в PostgreSQL — це єдина гарантія. Будь-яка логіка на рівні приложення недостатня при паралельних запитах.
// Android Kotlin — оптимістичний UI + захист від двійного натиску
viewModel.castVote(optionId) // StateFlow: VoteState.Loading -> VoteState.Success/Error
// ViewModel
fun castVote(optionId: String) {
if (_voteState.value is VoteState.Loading) return // захист від повторного тапу
viewModelScope.launch {
_voteState.value = VoteState.Loading
_selectedOption.value = optionId // оптимістичне оновлення UI
repository.castVote(pollId, optionId)
.onSuccess { _voteState.value = VoteState.Success }
.onFailure { error ->
_selectedOption.value = null // відкат
_voteState.value = VoteState.Error(error)
}
}
}
Реальний час: результати без перезавантаження
Для відображення результатів голосування в реальному часі використовуємо WebSocket або Server-Sent Events. SSE переважно для більшості випадків: односпрямований потік із сервера, простіше з proxy та CDN, вбудований reconnect.
На Flutter через http пакет або web_socket_channel:
// SSE для результатів голосування
final stream = http.Client()
.send(http.Request('GET', Uri.parse('$baseUrl/polls/$pollId/results/stream')))
.asStream()
.expand((response) => response.stream
.transform(const Utf8Decoder())
.transform(const LineSplitter())
.where((line) => line.startsWith('data: '))
.map((line) => PollResult.fromJson(json.decode(line.substring(6)))));
Для корпоративних опитувань з тисячами учасників — WebSocket через socket.io або нативний URLSessionWebSocketTask на iOS / OkHttp WebSocket на Android.
Анонімність з верифікацією
Частина сценаріїв вимагає: результати анонімні, але кожен учасник — реальна верифікована людина. Це реалізується через токени голосування: при авторизації користувач отримує одноразовий анонімний токен, який сервер не може пов'язати з identity після видачі. Голос відправляється з цим токеном, не з user_id.
Складніший варіант — Zero-Knowledge Proof, але для більшості корпоративних опитувань достатньо односпрямованого хешу: vote_token = HMAC(user_id + poll_id, secret), де secret відомий тільки серверу та знищується після завершення опитування.
Типи питань та їх реалізація
| Тип | Особливості реалізації |
|---|---|
| Single choice | Radio buttons, ідемпотентний vote endpoint |
| Multiple choice | Checkboxes, валідація min_selections / max_selections |
| Rating scale (NPS) | Слайдер або кнопки 1–10, нейтральний стан за замовчуванням |
| Ranked choice | Drag-and-drop, ReorderableListView на Flutter |
| Open text | TextEditingController з обмеженням символів, модерація |
| Matrix / grid | Нестандартний компонент, важкий для вузьких екранів |
Drag-and-drop для Ranked choice — найтрудомісткіший тип: на iOS UICollectionViewDiffableDataSource з drag interaction, на Android ItemTouchHelper.
Повідомлення та життєвий цикл опитування
Push-повідомлення про початок та закінчення голосування через Firebase Cloud Messaging. На iOS: UNNotificationServiceExtension дозволяє кастомізувати нотифікацію прямо із push — додати результати або прогрес-бар без відкриття приложення. На Android — NotificationCompat.BigPictureStyle для rich notifications з процентами.
Етапи роботи
Аудит вимог (типи питань, масштаб, вимоги до анонімності) → проектування схеми даних та API → розробка мобільного клієнта → інтеграційне тестування конкурентних голосувань → нагрузкове тестування → публікація.
Строки
Просте приложення з одним типом питань та базовою аналітикою: 4–6 тижнів. Повнофункціональна платформа з кількома типами питань, реальним часом, анонімністю та панеллю адміністрування: 3–4 місяці. Вартість розраховується індивідуально.







