Как JavaScript позволяет создавать кроссплатформенные приложения, которые должны работать несколько дней, но не должны иметь ни одного недостатка

Нет времени исправить это

В течение последних двух или трех лет я все активнее занимался разработкой определенного типа мобильного приложения, требующего особых технических мер предосторожности, которые не требуются в большинстве других приложений. Это приложения, поддерживающие всех участников публичных или частных мероприятий, таких как корпоративные собрания, свадебные церемонии, встречи выпускников, экскурсии и т. Д.

Такие приложения имеют особенность: они становятся доступными всего за несколько дней до начала мероприятия и перестают работать, как только мероприятие закончится (или несколько дней спустя). Краткость их «срока годности» имеет заметные последствия: они могут быть опубликованы в магазинах приложений только один раз, и, если в их содержании или функциях обнаружен какой-либо дефект, исправить его путем отправки новой версии невозможно.

Немного из моей технической истории

Я начал создавать приложения для iOS в 2008 году, сначала дома, а затем на постоянной работе. Изначально используя собственные платформы (XCode и Android SDK), я постепенно перешел на кроссплатформенные инструменты, пройдя через несколько различных решений (Appcelerator, ExtJS, Xamarin, React Native). Я никогда не был полностью удовлетворен.
Более или менее то же самое разочарование, которое я испытывал при создании веб-приложений с 2001 года: были ли все эти фреймворки хорошей вещью? Неужели волнение первого момента стоит вдалеке, когда возникают надоедливые и бесконечные задачи по обслуживанию?
Короче говоря, я убедил себя и своих коллег в том, что это немного больше, чем минимальная платформа. Достаточно для разработки всего, где «минимальный» означает HTML, CSS и jQuery.
Как бы странно это ни звучало, использование таких фундаментальных инструментов может дать реальные преимущества по сравнению с такими продвинутыми инструментами, как React или Flutter. Но я подробнее остановлюсь на этом в следующей статье.

Здесь стоит сказать, что наш выбор оказался важным для решения возникшей у нас проблемы: как исправить приложение, не перестраивая его.

Что делает приложение для управления событиями

Прежде всего, что делает такое недолговечное приложение? Он предоставляет пользователям

  • информация о мероприятии (расписание, контакты, места проведения и т. д.)
  • уведомления
  • подсказки и любопытства о городе и окрестностях
  • карты
  • функции для бронирования дополнительных услуг и подписки на дополнительные мероприятия
  • чат или другие каналы связи

В большинстве случаев предоставляется бэк-офисное веб-приложение, в котором «администраторы» событий могут управлять информацией, поступающей на пользовательские устройства и с них.

Кодируйте один раз, меняйте все время

У нас было три проблемы, которые нужно было решить.

Во-первых, нашему клиенту может потребоваться изменить содержимое страниц в любой момент.
Он или она может захотеть изменить, добавить или удалить любой текст, изображение, стиль и дерево навигации. само по себе, в связи с непредвиденными обстоятельствами, после выпуска Приложения.

Во-вторых, некоторые аспекты логики приложения могут измениться даже после начала события (например, некоторое поведение функций резервирования из-за организационных ограничений).

В-третьих, мы должны иметь возможность исправить любую ошибку, возникающую после выпуска, без публикации новой версии.

Короче говоря, найденное нами решение основано на механизме, в котором приложение синхронизируется с данными, находящимися на внутреннем сервере, где «данные» включают в себя фактическое содержимое, но также и фрагменты программного обеспечения (HTML, CSS и javascript ).

Посмотрим, как мы это сделаем.

Программная платформа

Мы пишем приложение во многом как стандартное одностраничное приложение, в основе которого лежат HTML, CSS и jQuery. Для преобразования его в пакет iOS и Android мы используем Apache Cordova. Преимущество этой архитектуры заключается в том, что мы можем выполнять 95% разработки и тестирования с помощью браузера (обычно Google Chrome): несколько вещей, которые необходимо исключить, - это функции только для устройства, такие как push-уведомления или просмотр фотогалереи.

Дерево каталогов типичного приложения Cordova имеет следующую структуру:

Обратите внимание, что папка www содержит весь исходный код приложения, в то время как все остальные папки и файлы служат для Cordova Builder, чтобы сгенерировать все дополнительные компоненты, необходимые для упаковки Android, iOS. и браузерная версия того же приложения. Типичное (минимальное) содержимое папки www может быть:

При сборке Cordova копирует всю папку в активы Android и XCode, которые становятся частью окончательного пакета. Файл index.html - это точка входа в приложение, такая же, как и в простом веб-приложении. В нашем приложении мы также используем базу данных SQLite, которую соответствующий плагин Cordova (cordova-sqlite-ext) копирует в доступную для записи область устройства, в то время как все остальные файлы не могут быть записаны программой.

Давайте теперь посмотрим, как мы используем эту простую структуру, чтобы получить приложение, которое мы можем изменить, не перестраивая его, не говоря уже о публикации.

Первое: синхронизация данных

Наше приложение может работать как в онлайн, так и в автономном режиме, благодаря собственной локальной базе данных SQLite, содержащей все необходимые элементарные данные (программа мероприятия, участники, адреса, бронирования и т. Д.). Когда мы впервые загружаем приложение в магазины, файл .sqlite в пакете содержит последнюю версию данных, доступных на тот момент. После этого приложение поддерживает его актуальность, вызывая при необходимости внутренний сервер:

  • на старте
  • когда пользователь возобновляет его из фона
  • когда он вызывает сервер для других целей, таких как бронирование услуги

При вызове сервера наше приложение отправляет ему метку времени «последнего обновления» и заботится о сохранении новой метки времени в локальном хранилище приложения. Со своей стороны, сервер выбирает все изменения, сделанные после этого времени, и возвращает их в приложение в виде операторов SQLite. Подробности мы увидим позже.

Во-вторых: синхронизация контента

Чтобы позволить пользователю иметь полный контроль над содержимым страниц приложения, все приложение динамически строит свои страницы из данных, хранящихся в его базе данных SQLite, в той части, которую мы называем, даже не говоря, CMS или управление контентом. система. CMS содержит

  • тексты (в формате HTML, возможно, включая части CSS)
  • изображения и галереи изображений
  • определения отношений и переходов между страницами

Механика синхронизации данных, являющаяся частью той же базы данных, применима и к данным CMS. Мы создали довольно продвинутый инструмент бэк-офиса, чтобы позволить нетехническим пользователям поддерживать эти данные CMS в бэк-офисе.

В-третьих: перезапись HTML и CSS

CMS позволяет пользователю обрабатывать большинство страниц приложения с широкой автономией. Изображения, тексты и ссылки определяются пользователем через веб-сервер и помещаются в правильный контекст HTML с помощью кода javascript приложения. Затем этот контент дополняется вставкой таких гаджетов, как формы, кнопки и т. Д., Как это запланировано дизайнером и разработчиком приложения.

Такая предварительно разработанная часть приложения, состоящая из шаблонов HTML и определений CSS, находится в статических папках www / html и www / css соответственно. Поэтому в случае необходимости его нельзя напрямую перезаписать.

Непредвиденные изменения предопределенных HTML и CSS происходят реже, но все же случаются. Чтобы преодолеть это препятствие, мы решили

  • передавать любые обновления HTML-шаблонов по тому же каналу, который используется для базы данных
  • использовать тот же канал для переопределения определений CSS путем вставки тегов script в файл HTML
  • сохранить все обновленные HTML-файлы в локальном хранилище
  • загружать весь HTML динамически, беря его из локального хранилища или, если он отсутствует, из встроенных файлов.

Четвертое: переопределение функций Javascript

Как насчет исправления или изменения части JavaScript в нашем приложении?

Прежде всего, мы структурировали наш код так, чтобы он состоял из небольших функций javascript: основной исходный объект («приложение», являющееся приложением Cordova), имеет несколько свойств, отображающих логические подразделения нашего приложения (например, app.program, app.reservation, app.contact, app.map и т. д.). Каждая функция тогда является свойством своего родительского объекта (например, app.reservation.listServices, app.contact.sendMsg и т.п.).

Остальное просто:

  • мы передаем исправления к функциям javascript по тому же каналу, который используется для базы данных и HTML-шаблонов
  • мы сохраняем их в локальном хранилище в том же порядке, в котором они были
  • когда приложение запускается, оно считывает все переопределения функций из локального хранилища и выполняет их, таким образом переопределяя любую функцию с одинаковым именем, ранее обнаруженную в статических файлах javascript.

Давайте теперь посмотрим на код.

На сервере: API «getupdates»

Наш внутренний сервер предоставляет RESTful API, который получает метку времени и возвращает ответ JSON, содержащий новую метку времени и три, возможно, пустых массива:

  • список названных файлов HTML
  • список файлов javascript в порядке их выполнения
  • список операторов SQLite в порядке их выполнения

Вот пример кода PHP, собирающего обновления для файлов javascript и HTML:

Он сканирует каталог, чтобы собрать все файлы, измененные позже заданной даты. Для файлов javascript он возвращает только содержимое файла. Для файлов HTML он возвращает как имя файла, так и его содержимое, и каждый файл должен быть назван точно так же, как файл проекта, который он собирается заменить.

Что касается базы данных, мы приняли следующую стратегию:

  • Приложение получает все обновления БД в виде операторов SQL
  • для каждой обновляемой таблицы SQLite x существует представление sync_x в базе данных MySql, имеющее те же столбцы, что и таблица x
  • каждый sync_x включает отметку времени и первичный ключ
  • триггеры перехватывают физическое удаление строк таблицы и создают соответствующие операторы SQL; типичный триггер
DELIMITER $$
CREATE TRIGGER IF NOT EXISTS `deleted_from_mytable` AFTER DELETE 
ON mytable FOR EACH ROW BEGIN
INSERT INTO app_sql (SqlStatement) VALUES( CONCAT('DELETE FROM mytable WHERE MyTable_Id=',old.MyTable_Id) );
END;
$$
  • временная таблица (app_sql в приведенном выше примере) собирает все операторы SQL, сгенерированные триггерами и созданные вручную (в случае аварийного обслуживания базы данных SQLite)

Вот код, который с этим справляется:

Стоит отметить, насколько коротким и простым является код, необходимый для обработки всего персонала в серверной части.

В приложении: получение обновлений

Клиентский код, обрабатывающий обновление, также прост. Наше приложение вызывает внутренний API и обрабатывает ответ, как показано ниже:

После того, как все обработано, у нас есть

  • актуальная база данных
  • актуальные функции javascript, также сохраненные в локальном хранилище
  • новая копия измененных файлов HTML, сохраненная в локальном хранилище

Когда программа хочет использовать HTML-файл, она всегда загружает его динамически с помощью такой очень простой функции:

Для выполнения задачи нам нужно еще кое-что: каждый раз, когда наше приложение запускается, оно должно восстанавливать фиксированные определения javascript. Это тривиально:

Выводы

Обратите внимание: что делает такое решение возможным, так это использование простого javascript и HTML. Я не думаю, что текущие основные инструменты (например, Ionic, React Native, Flutter и т. Д.) Могут делать то же самое, то есть обновлять код приложения на лету. Скажите, пожалуйста, если я ошибаюсь.

Мы могли бы назвать эти приложения «самовосстановлением». Даже если это не так, это как если бы это было правильно. Мы помним как большой успех, когда нам удавалось решать неожиданные проблемы на смартфонах пользователей без какой-либо медвежьей услуги и при этом пользователь даже не замечал.

Этакая магия.