Тонкости drag-and-drop и синхронизации данных через стороннее API

IT-копирайтер
Время чтения: 4 минуты
На днях решил приготовить паэлью. Нашел в Интернете рецепт, составил список ингредиентов, обошел кучу магазинов – алгоритм не из простых. А все могло быть гораздо проще, используй я приложение, которое мы не так давно разработали для крупной сети супермаркетов. Кроме хозяйственной пользы, о которой я расскажу чуть позже, проект здорово помог подрасти профессионально: обеспечение корректной работы drag-and-drop, отладка процесса синхронизации данных при работе со сторонним API, обновление предметной области — задачи интересные и непростые. О них и будет сегодняшний пост.
Для начала кратко расскажу о проекте и функциональности приложения. От нас требовалось разработать две версии мобильного приложения: для Android и iOS. По задумке клиента приложение должно было информировать пользователей о рекламных кампаниях, проводимых торговой сетью, при этом оставаясь надежным помощником для покупателей.
В галерее приложения — регулярно обновляемые рецепты блюд, все ингредиенты к которым можно приобрести в магазинах сети по специальным ценам. Пользователи могут планировать меню, добавляя понравившиеся блюда в календарь. На основе заданного меню составляется список покупок с рассчитанным количеством необходимых продуктов. Очень удобно: никаких бумажных списков, всё можно купить в одном месте и по выгодной цене, плюс — узнать рецепты новых блюд.
Но чтобы реализовать эти функции, нам нужно было решить ряд задач.
Drag-and-drop
Небольшие затруднения вызвала реализация корректного перемещения картинок из галереи блюд в календарь.
В Android-приложениях, чтобы захватить элемент для перетаскивания, используется «долгое нажатие» (long click). Он обрабатывается галереей, которая к тому же берет на себя управление всеми последующими событиями касания (touch events). Поэтому, при переносе картинки с выбранным блюдом в календарь в низ экрана, галерея просто могла начать прокручиваться. Нам это было не нужно.
Чтобы drag-and-drop корректно работал, мы перехватили события касания у галереи и перевели их в глобальную систему координат. Так удалось избежать прокрутки галереи и организовать взаимодействие перемещаемых элементов с другими объектами на экране.
Аналогичная проблема возникла и при разработке iOS-версии приложения: теперь TableView воспринимал движение вниз как скролл. Чтобы исправить ситуацию, было решено отключить распознаватели жестов (gesture recognizers) в TableView, когда пользователь нажимал на картинку для переноса в календарь.
Обновление и синхронизация
Сложнее было реализовать обновление данных и их синхронизацию с сервером.
В первую очередь мы вынесли эти процессы в отдельный фоновый сервис, чтобы они не мешали пользователю свободно перемещаться по приложению.
В чем же были сложности?
Во-первых, мы должны были обеспечить полноценную работу приложения оффлайн. Требовалось хранить все данные — информацию о рекламных кампаниях, рецепты, списки покупок и т. д. — не только на сервере, но и в самом приложении, чтобы пользователь всегда имел к ним доступ. Как только появлялось подключение к Интернету, запускался процесс синхронизации сохраненных данных со свежими.
В процессе обновления все данные, естественно, должны были корректно отображаться на экране . Но проблема в том, что предметная область приложения состоит из большого числа связанных сущностей, и в ходе обновления эта связность могла легко нарушиться.
Чтобы справиться с этими проблемами, мы решили кэшировать данные и при обновлении синхронизировать соответствующие участки кода между рабочими потоками.
Помимо всего прочего, особое внимание пришлось уделить календарю.
Здесь надо было выполнить дополнительные условия. Во-первых, сделать привязку календаря к конкретному пользовательскому аккаунту, чтобы составлять меню могли одновременно все члены семьи с разных устройств, через мобильное приложение или веб-сайт.
При этом начинать синхронизацию данных в календаре можно было только после удачного обновления предметной области, чтобы избежать получения от сервера неизвестных рецептов.
Вообще-то, синхронизация обычно делается достаточно просто: сохраняем даты изменения и потом смотрим, чьи изменения более поздние. Однако у нас возникли трудности со сторонним API.
Первоначально нам предложили организовать синхронизацию только при помощи поля lastModifiedDate, в котором сохранялось время последнего изменения элемента на сервере. Но этого было недостаточно, чтобы вычислить разницу во времени и сравнить даты последних изменений на девайсе и на сервере. Поэтому в API было добавлено текущее на сервере время.
Однако API все равно работал неправильно. Время сервера он возвращал в виде UNIX-time, а поле lastModifiedDate — в формате строки «dd.MM.yyyy HH:mm».
Но дело в том, что в UNIX-time отсчет времени ведется в секундах, а поле lastModifiedDate в форматированном виде не содержало секунд! Поэтому, если бы изменения на сервере и на девайсе произошли в пределах одной минуты, результат синхронизации мог стать непредсказуемым.
В свою очередь, время в формате UNIX-time не содержит информацию о часовом поясе! А при форматировании в строку «dd.MM.yyyy HH:mm» учитывался часовой пояс сервера — GMT+2 и часовой пояс девайса при декодировании. Сложно представить последствия синхронизации при таких условиях.
В конце концов нам удалось обеспечить правильную работу API: поле lastModifiedDate стало возвращаться в виде UNIX-time, т.е. с секундами, миллисекундами и без учета часового пояса.
Таким образом мы решили все задачи и без проблем выпустили приложение.
Выполненный проект, безусловно, можно назвать успешным. Не столько из-за сотен тысяч скачиваний из Apple App Store и Google Play, сколько из-за сотен тысяч сытых и довольных ужином пользователей.
Комментарии