Разработка корзины покупок в мобильном приложении
Корзина — это не просто список с кнопками «плюс» и «минус». Это синхронизированное состояние между локальным хранилищем и сервером, которое должно пережить переключение между приложениями, потерю интернета, logout и повторный login. Корзина, которая теряет товары при закрытии приложения или показывает устаревшую цену при оформлении заказа — источник прямых потерь конверсии.
Где всё идёт не так
Синхронизация локального и серверного состояния. Самая частая ошибка — хранить корзину только на клиенте. Пользователь добавил товар с одного устройства, открыл приложение на другом — корзина пустая. Правильная схема: оптимистичное обновление UI + асинхронная синхронизация с сервером. В React Native с Redux Toolkit — createAsyncThunk для каждой операции (add, remove, updateQuantity), extraReducers с pending/fulfilled/rejected. При ошибке сети — rollback к предыдущему состоянию через immer.
Устаревшие цены и наличие. Пользователь добавил товар три дня назад, цена изменилась. При открытии корзины делаем запрос на валидацию с текущими данными — сервер возвращает актуальные цены и статус наличия. Товары, которых нет в наличии, выделяем визуально и блокируем чекаут.
Свайп для удаления. SwipeRow в React Native или ReorderableList часто конфликтуют с вертикальным скроллом. На Android через Compose — SwipeToDismiss из Material 3 работает стабильнее. На iOS — UISwipeActionsConfiguration в нативе, в Flutter — Dismissible виджет. Везде нужна haptic feedback при достижении порога свайпа — UIImpactFeedbackGenerator.impactOccurred() / HapticFeedback.mediumImpact().
Архитектура корзины
CartItem {
productId: string
variantId: string // размер, цвет
quantity: number
priceSnapshot: number // цена на момент добавления
currentPrice: number // актуальная цена с сервера
}
variantId — обязательное поле, которое часто забывают. Один и тот же товар в двух размерах — это два разных CartItem. Без этого пересчёт суммы ломается при смешанных вариантах.
Персистентность: AsyncStorage + сериализация JSON в RN, Room database в Android, CoreData или UserDefaults для небольших корзин на iOS. При следующем запуске восстанавливаем локальное состояние немедленно, затем делаем фоновую синхронизацию с сервером.
Из практики: e-commerce приложение, Flutter + BLoC. Корзина "забывала" товары при force-close. Причина — HydratedBloc не был инициализирован до первого обращения к storage. Перенесли инициализацию HydratedStorage в main() до runApp() — проблема ушла.
Итоговая сумма и скидки
Пересчёт суммы — только на сервере при чекауте. На клиенте показываем предварительную сумму (локальный расчёт), при переходе к оплате делаем финальный расчёт с учётом промокодов, скидок лояльности, доставки. Это предотвращает расхождение сумм.
Промокод поле — отдельный UX-элемент. При вводе невалидного кода — shake animation на поле + текст ошибки под ним, не toast.
Что входит в работу
- Список товаров в корзине с изображением, названием, вариантом, ценой
- Изменение количества (инпут или +/– кнопки) с валидацией min/max
- Свайп для удаления с undo через snackbar (5 секунд)
- Итоговая сумма с разбивкой (товары, доставка, скидка)
- Поле для промокода с валидацией
- Пустое состояние корзины с CTA
- Синхронизация с сервером + обработка offline
- Перейти к оформлению заказа с валидацией наличия товаров
Сроки
2–3 рабочих дня. Если требуется синхронизация между устройствами, интеграция с программой лояльности или сложная логика скидок — 3–5 дней. Стоимость рассчитывается индивидуально.







