Разработка мобильного приложения для голосований и опросов
Голосование в мобильном приложении — задача, где техническая сложность скрыта за простым интерфейсом. Реальные требования: один пользователь — один голос (предотвращение накруток), результаты в реальном времени для тысяч одновременных участников, корректная работа при нестабильной сети, иногда — анонимность с верифицированной идентичностью. Это не «форма с кнопкой» — это система с требованиями к целостности данных.
Целостность голосования: как не дать накрутить
Самая критичная часть — идемпотентность голоса. Пользователь не должен проголосовать дважды даже при двойном нажатии, потере сети в момент отправки или переустановке приложения.
На клиенте: оптимистичный 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 предпочтительны для большинства случаев: однонаправленный поток с сервера, проще прокси и 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 позволяет кастомизировать нотификацию прямо из пуша — добавить результаты или прогресс-бар без открытия приложения. На Android — NotificationCompat.BigPictureStyle для rich notifications с процентами.
Этапы работы
Аудит требований (типы вопросов, масштаб, требования к анонимности) → проектирование схемы данных и API → разработка мобильного клиента → интеграционное тестирование конкурентных голосований → нагрузочное тестирование → публикация.
Сроки
Простое приложение с одним типом вопросов и базовой аналитикой: 4–6 недель. Полноценная платформа с несколькими типами вопросов, реальным временем, анонимностью и панелью администрирования: 3–4 месяца. Стоимость рассчитывается индивидуально.







