Заказ пропал после звонка: почему SwiftUI и Compose не спасают от перезапуска
SwiftUI и Compose меняют способ рисования экранов, но не меняют жизненный цикл приложения: iOS и Android по-прежнему завершают фоновый процесс, когда не хватает памяти. После перезапуска всё, что лежало в памяти, пропадает. Чтобы черновик заказа выжил, его сохраняют в локальную базу или в сохраняемое состояние, а не оставляют в обычных переменных экрана.
Почему возникает потеря данных при перезапуске приложения 📱
Типичная сцена из полевых продаж. Менеджер стоит на складе клиента в Подольске и собирает заказ на сорок позиций: у каждой свой остаток и своя скидка. Звонит другой клиент, менеджер отвечает, потом открывает мессенджер за чертежом, потом почту. Через двадцать минут он возвращается в приложение и видит пустую форму нового заказа.
Месяц назад разработчики переписали интерфейс на SwiftUI и Compose. Экраны собираются быстрее, кода стало меньше. Но память телефона ограничена, поэтому система выгрузила свёрнутое приложение, а при возврате создала экран с нуля. Декларативный фреймворк честно нарисовал то состояние, которое ему передали, то есть пустое.
Что происходит на Android
Когда системе не хватает памяти, она завершает процесс свёрнутого приложения. Переменная, созданная через remember, живёт, пока экран на месте. Поворот, смена темы или языка пересоздают активность и обнуляют её. ViewModel переживает поворот, но при гибели процесса исчезает вместе со всем остальным: он хранится в той же памяти.
Что происходит на iOS
У @State в SwiftUI та же логика: значение живёт, пока жив экран. Если iOS выгрузила приложение из фона, запуск с домашнего экрана собирает всё заново. А когда пользователь сам смахнул приложение из переключателя, система не восстанавливает состояние экранов вообще. Остаётся только то, что вы записали в базу.
Бывает и хуже. Старое приложение на UIKit или Android View иногда держало состояние через onSaveInstanceState или восстановление сцен. При переписывании эти вызовы терялись вместе со старым кодом: заказ, который раньше переживал выгрузку, перестал её переживать. Переход на новый фреймворк убрал страховку, о которой никто не помнил.
Чем рискует бизнес, если приложение забывает заказ
Первая потеря видна сразу: менеджер набирает сорок позиций заново, а клиент ждёт. Вторая тише. Часть сотрудников не станет повторять ввод, запишет заказ в заметки, и до 1С он дойдёт вечером, когда остатки уже изменились. Клиент, которому не подтвердили заказ на месте, звонит соседнему поставщику.
Самая дорогая потеря связана с обменом. Приложение отправило заказ в 1С, перезапустилось, не получило ответ и показало ошибку. Менеджер нажал «Отправить» ещё раз, и в базе лежат два одинаковых документа. Склад собирает оба, потом кто-то вручную выясняет, какой удалить. В компании с десятком агентов это работа бухгалтера и кладовщика, которую никто не планировал.
Как исправить: что и где хранить в Compose и SwiftUI 🛠
Начните с вопроса, сколько должны жить данные. Состояние выпадающего списка и содержимое заказа переживают разные события, поэтому хранить их надо в разных местах.
| Данные | Где хранить | Что будет после перезапуска |
|---|---|---|
| Раскрытый список, анимация, фокус в поле | remember в Compose, @State в SwiftUI | Сбросится. Для интерфейса это нормально |
| Выбранная вкладка, фильтр, текст в строке поиска | rememberSaveable и SavedStateHandle, @SceneStorage | Восстановится после выгрузки системой, после ручного закрытия пропадёт |
| Черновик заказа или акта, список позиций | Локальная база: Room, SwiftData или Core Data | Сохранится, в том числе после ручного закрытия и обновления приложения |
| Идентификатор документа для отправки в 1С | Та же локальная база, в одной записи с черновиком | Повторная отправка не создаст дубль |
| Токен входа, данные клиентов | Keychain на iOS, Android Keystore | Сохранится, сотрудник не вводит пароль заново |
Мелкое состояние экрана
Всё, что умещается в пару строк, кладите в сохраняемое состояние. На Android это rememberSaveable в самом экране и SavedStateHandle во ViewModel, на iOS это @SceneStorage. Списки туда не складывайте. Android держит сохранённое состояние в Bundle, а общий лимит на передачу данных между процессами составляет около 1 МБ на все операции приложения сразу. Если его превысить, приложение упадёт с TransactionTooLargeException ровно в момент сворачивания.
Черновики и документы
Пишите в базу при каждом осмысленном изменении: добавили позицию, поправили количество, выбрали контрагента. Кнопка «Сохранить» для этого не нужна, черновик просто существует. При запуске экран читает последний незавершённый документ и открывает его, а сотрудник не замечает, что приложение перезапускалось.
В черновиках лежат имена и телефоны контактных лиц, а это персональные данные по 152-ФЗ. Храните такие поля в защищённом хранилище устройства и не оставляйте дольше, чем нужен документ.
Отправка в 1С без дублей
Присвойте документу уникальный идентификатор в момент создания черновика, а не в момент отправки. Приложение шлёт заказ вместе с ним, а обработчик на стороне 1С сначала ищет документ с таким же значением. Нашёл, значит, возвращает существующий номер и ничего не создаёт. Повторное нажатие кнопки или обрыв связи превращаются в безвредную повторную попытку. Так устроена интеграция приложения с 1С, и проектировать её нужно вместе с хранением, а не после.
Что делать, если ошибка повторяется
Самый частый ответ разработчика: «у меня не воспроизводится». Причина простая: на тестовом телефоне много памяти, а отладчик держит приложение живым. Потерю надо вызывать намеренно.
- ✓На Android откройте заказ, сверните приложение и завершите процесс командой adb shell am kill с именем пакета. Можно включить в параметрах разработчика пункт «Не сохранять действия»: он проверяет восстановление экрана при каждом выходе из него.
- ✓На iOS сверните приложение, остановите его кнопкой в Xcode и запустите заново с домашнего экрана.
- ✓Вернитесь в приложение и сравните: тот же экран, те же данные, тот же прокрученный список.
Если потеря остаётся, проверьте четыре типичные причины.
- ✓Данные лежат в синглтоне или глобальной переменной. Они переживают пересоздание экрана, поэтому проверка на поворот проходит, а при гибели процесса данные исчезают.
- ✓Содержимое сохраняется, а навигация нет: сотрудник возвращается на главный экран, а не в свой заказ.
- ✓Сохранённое состояние слишком велико, и приложение падает при сворачивании. Смотрите отчёты о сбоях за момент перехода в фон.
- ✓На некоторых оболочках Android энергосбережение убивает фоновые процессы агрессивнее, чем чистая система. Поэтому у части сотрудников потеря случается, а у остальных нет. Проверьте на их моделях телефонов.
Подрядчику стоит задать один вопрос: что произойдёт с заказом, если убить процесс после добавления десятой позиции? Ответ «посмотрим» означает, что про перезапуск не думали.
Как предотвратить потерю данных в следующем релизе ✅
Перезапуск должен стать пунктом приёмки, а не сюрпризом после выхода в магазин. Добавьте в чек-лист пять сценариев:
- ✓свернуть приложение с заполненным заказом, завершить процесс, вернуться: заказ на месте;
- ✓закрыть приложение вручную и открыть снова: черновик на месте;
- ✓выключить сеть в момент отправки и включить обратно: в 1С один документ;
- ✓нажать «Отправить» дважды подряд: в 1С один документ;
- ✓обновить приложение до новой версии с незавершённым черновиком: черновик открывается, схема базы мигрировала.
Часть проверок на Compose автоматизируется: в библиотеке тестирования есть StateRestorationTester, он пересоздаёт экран и проверяет, что состояние вернулось из сохранённого. На iOS то же делают UI-тестами, где приложение завершается и запускается снова (terminate и launch в XCUITest).
Заведите в системе отчётов о сбоях фильтр на падения в момент сворачивания и возврата. Часть потерь начинается с падения, а не с выгрузки, но сотрудник видит одно и то же: заказа нет.
Как мы делаем приложения, которые переживают перезапуск, и сколько это стоит
В разработке мобильных приложений для бизнеса мы закладываем перезапуск в техзадание. Для каждого экрана с вводом данных описываем, что сохраняется, где и как долго. Приёмка включает пять сценариев выше и проходит на тех телефонах, которыми пользуются ваши сотрудники, а не только на эмуляторе.
Для обмена с учётной системой делаем то, что описано в разделе про дубли: идентификатор документа на устройстве, проверка в 1С, очередь отправки, которая доживает до появления связи. Типичный пример: B2B-приложение с 1С, где агент оформляет заказ на складе клиента без сети, а в базу документ попадает один раз.
Не всякой задаче нужен нативный код. Для осмотров, фотофиксации и заказов из одной базы подойдёт 1С:Мобильная платформа: локальная база и обмен с основной базой входят в её штатные механизмы, и многое не придётся писать с нуля. SwiftUI и Compose выбирают, когда нужны быстрый интерфейс, сканер штрихкодов, камера и поведение, привычное для каждой платформы.
Разработка мобильного приложения у нас стоит от 500 000 ₽. Итог зависит от числа экранов, от того, какие документы должны работать без сети, и от сложности обмена с 1С. Сумму называем после разбора этих трёх пунктов. Если приложение уже в эксплуатации и теряет данные, начинаем с воспроизведения сбоя на ваших телефонах и только потом называем объём доработок.
❓ Частые вопросы
Почему мобильное приложение на Compose или SwiftUI теряет данные после сворачивания?
Потому что система выгружает свёрнутое приложение при нехватке памяти, а обычные переменные экрана (remember в Compose, @State в SwiftUI) живут только до этого момента. При возврате приложение запускается заново и показывает пустую форму. Данные нужно записывать в сохраняемое состояние или локальную базу, независимо от того, на каком фреймворке написан интерфейс.
Нужно ли переписывать приложение на SwiftUI и Compose, чтобы оно не теряло заказы?
Нет. Потеря заказов зависит от того, где приложение хранит данные, а не от фреймворка интерфейса. Приложение на UIKit или классических Android View можно исправить, перенеся черновики в локальную базу. Переписывать интерфейс стоит по другим причинам: поддержка новых версий систем и нехватка специалистов по старому стеку.
Как проверить, что приложение переживает перезапуск?
Вызовите потерю намеренно. На Android сверните приложение с открытым заказом и завершите процесс командой adb shell am kill с именем пакета, либо включите «Не сохранять действия» в параметрах разработчика. На iOS сверните приложение, остановите его из Xcode и запустите с домашнего экрана. После возврата экран и данные должны остаться прежними.
Как не получить дубль заказа в 1С при повторной отправке из мобильного приложения?
Присвойте документу уникальный идентификатор при создании черновика на устройстве и передавайте его в 1С вместе с заказом. Перед записью 1С ищет документ с таким идентификатором и, если находит, возвращает существующий номер вместо создания нового. Повторное нажатие кнопки или обрыв связи после отправки тогда не плодят одинаковые заказы.
Сколько стоит разработка мобильного приложения для обмена данными с 1С?
Разработка мобильного приложения у нас стоит от 500 000 ₽. Итоговая сумма зависит от числа экранов, от того, какие документы должны работать без сети, и от сложности обмена с 1С. Точную стоимость называем после разбора этих трёх параметров, до начала работ. Если приложение уже работает, цену доработки определяем после воспроизведения сбоя.
Или позвоните: +7 906 045-28-27 — в рабочее время с 9:00 до 19:00
Остались вопросы? Нужна помощь?
Менеджеры компании с радостью ответят на ваши вопросы, произведут расчет стоимости услуг и подготовят индивидуальное коммерческое предложение.
Бесплатная консультация

