Мой путь воина, или как я готовила приложение для жизни в Sailfish +12


Искусство воина состоит в сохранении равновесия
между ужасом быть человеком и чудом быть человеком.
«Путешествие в Икстлан»


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

С недавних пор я тружусь на позиции разработчика в компании Digital Design, и иногда мне приходится разбираться с задачами, с которыми я ранее не встречалась. Это интересно и часто весело. Сейчас, например, я пишу корпоративное приложение под Sailfish OS и хочу поделиться с вами своим опытом – об этом и пойдет речь ниже. Следуйте за мной под кат, если вы начинающий разработчик или, так же, как и я, столкнулись с задачей адаптации корпоративного приложения под ОС Sailfish и не знаете, с чего начать, а также те, кто ещё не слышал о Qt и особенностях Sailfish.


Немного предыстории, или зачем я это сделала


Когда компания 5 лет назад разрабатывала решение ИСКО «Ареопад» под iPad (iOS), никто и подумать не мог, что заказчикам потребуется приложение для работы на устройстве с ОС Sailfish (тогда ОС принадлежала финской компании Jolla, впрочем, сейчас тоже). Но в 2016 году российская компания «Открытая мобильная платформа» приобрела лицензию на её использование, модифицирование, распространение и довела её до ума: приспособила под отечественные мобильные устройства и пользователей (в основном компании и госучреждения) и зарегистрировала Sailfish Mobile OS RUS в реестре. И сейчас это, можно сказать, единственная официальная российская операционная система для мобильных устройств.

Естественно, нам захотелось в ней разобраться – остальные ОС мы уже покорили, а у Sailfish, судя по всему, большое будущее (да, это я про импортозамещение). Из этого всего следует, что приложению для Sailfish быть! В качестве первого испытуемого объекта выступило мобильное приложение «Информационная система для работы коллегиальных органов «Ареопад» (оно как раз находилось на стадии модернизации).

Теперь о Qt


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

На всякий случай уточню, что вычисления и отображение происходят на разных языках в разных модулях (С++ и QML), взаимодействующих между собой специальными хитрыми методами (property, signal-slot).

А ещё Qt – это основной фреймворк, используемый при разработке ПО для Sailfish.

Подходы к решению задачи: налево пойдёшь – костыль наживешь, направо пойдёшь – фичи потеряешь


Поначалу, окрылённая идеей кроссплатформенности и тем, что буду её воплощать, я подумала, что приложение, написанное для Sailfish, не взлетит, а радостно вспорхнёт на любую другую ОС, да хоть на холодильник или чайник. Но счастье моё длилось ровно до того момента, пока я не села запускать его на Android – тут мне стало грустно. Sailfish под это дело ну совсем не приспособлен и менять определённо ничего не намерен.

Если Android или iOS позволяют использовать условную компиляцию и предоставляют для этого какой-то набор инструментов, то Sailfish невозможно отличить от Linux (в рамках Qt, конечно).

Далее возникает проблема совместимости QML: в Sailfish устаревшая версия Qt Quick (2.6 для Sailfish и уже 2.11 для всех остальных), Android не умеет Silica, Sailfish не умеет QtQuick.Controls. Поэтому для разных ОС приходится тянуть одинаковые компоненты из разных библиотек, что плодит почти одинаковые файлы для каждой ОС.
При этом Silica имеет, вероятно, всё те же возможности, что и QtQuick.Controls, но отказывается запускаться на любой другой платформе.

Особенности Sailfish


Будучи ленивой максимально креативной творческой личностью со стремлением оптимизировать трудозатраты, я пошла по пути наименьшего сопротивления функционала и написала свой шедевр на чистой Qt, без использования QtQuick.Controls и Silica. В итоге, на Sailfish всё работает чинно и благородно, а вот на Android половина элементов, несмотря на мой строгий приказ сидеть на месте, разъехались кто куда по своим наверняка важным делам, так что следующим шагом будет всё-таки сделать по-взрослому и развести файлы разметки для каждой ОС по разным углам. [Что я и впоследствии и сделала, пока готовила статью к выходу в свет. Эта история тянет на отдельную статью]. Прошу запомнить и принять: магические константы – зло (например, width:15). И на двух почти идентичных девайсах даже с одной ОС они могут отличаться в 2 раза.

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

Речь пойдет только про одну библиотеку для реализации графического интерфейса Silica, ибо остальное, что я успела узнать и понять, ничем не отличается от стандартного программирования на QML.

  • Theme.

Этот инструмент меня заинтересовал больше всего, потому что аналога в других ОС я не встречала, а ещё он несложный. Суть в том, что дизайн приложений не является константой, а зависит от выбранной темы.

Элемент Theme содержит в себе информацию о выбранной теме на данный момент: основные цвета, размеры шрифтов, отступов и компонентов. Таким образом, при написании приложений не нужно хардкодить цвета, подгонять размеры под размер экрана, да и вообще на макет смотреть (да простят меня дизайнеры, у вас и без того работы много), можно просто подтянуть это все из элемента Theme. Ну разве не круто!



  • Menu.

Тоже фича Sailfish (хотя, может так и в Android можно, но я не видела).

Есть три вида меню:
PullDownMenu — PushUpMenu — ContextMenu



Что касается Silica…View – они ничем от стандартных …View не отличаются (± пара не особо заметных свойств), а компонент Button заставляет изрядно понервничать – высоту кнопки нельзя изменить, приходится везде писать Rectangle и MouseArea. Если бы я делала приложение только для Sailfish, используя стандартный дизайн этой ОС, менять высоту кнопки не было бы необходимости или можно было бы использовать компонент из Silica. Но поскольку я пишу приложение для всех устройств, то решено было использовать самый массовый дизайн (Android), чтобы всем было привычно и удобно. Многие со мной не согласятся и скажут, что нужно использовать нативные элементы UI для каждой платформы, но это совсем другая тема, она является предметом холиваров уже на протяжении долгих лет, за это время выросло не одно поколение разработчиков.

Ложка дёгтя к мобильной разработке на Qt


  • Ассинхронщина

В Qt для общения с сервером вызова одной функции недостаточно, нужно отправить запрос и попросить специальный объект, отвечающий за ответ сервера, вызвать функцию обработки ответа (которую, конечно, пишем сами), когда ответ будет получен:

void foo()
{
      QNetworkRequest request("http://www.leningrad.spb.ru");
      QNetworkReply *reply = mngr->get(request);
      connect(reply, &QNetworkReply::finished, this, &Class:: onReplyFinished);
}
void
onReplyFinished ()
{
    QNetworkReply *reply = (QNetworkReply*)sender();
    QByteArray ans = reply->readAll();
    qDebug() << ans;
}

Функция onReplyFinished будет вызвана в момент получения ответа от сервера, а это может занять неизвестно сколько времени – зависит от качества соединения и серверной части. Теперь представьте, как это все будет организовано при 1500-2000 запросах, которые при этом друг от друга зависят и должны запускаться в порядке строгой очереди. А если вспомнить, что от ответа зависит вся UI часть, то совсем страшно становится.

  • Костыльно-ориентированное программирование, или креативный подход к решению проблем

Другая тема – ListView, это совсем локальная проблема Qt, но всё же неприятная.

У любого уважающего себя ListView обязан быть компонент header, который в качестве первого по счёту делегата вставляет какой-то принципиально отличающийся от остальных элемент. Да, он это делает, да, всё прекрасно рисует, если бы не одно «но»: у меня делегат занимает весь экран, и его надо листать, но как только я пытаюсь с нулевого индекса листнуть влево к хедеру – он принципиально отказывается этот самый хэдер показывать. Что с этим делать? Затыкать костылем. Кусок моего кода формирования модели для данного ListView в cpp части, максимально точно описывающий происходящее:

foreach (QVariant quest, QQuestions)
 {
    Questions.append(new Question(client, quest));
 }
kostilQuestions = Questions;
if (Questions.size() > 0)
   kostilQuestions.prepend(Questions[0]);

Далее в делегате создаются 2 независимых айтема, видимость которых зависит от индекса.
При индексе равном нулю видим один, иначе – другой.

Итог


В результате, я получила приложение, которое «летает» как на Android и iOS, так и на Sailfish, точнее гибкое приложение, которое под эти ОС адаптируется. И магия тут ни при чем. Я нашла способ избежать конфликта и подружить все ОС c Sailfish путём подстановки некоторых файлов для Sailfish и всех остальных. Это позволило в коде приложения выделить слои пользовательского интерфейса под каждую ОС.

Можно считать, что первый шаг мы сделали, – подружили приложение с Sailfish. Но работа на этом не завершена, мы с Sailfish выходим на новый уровень наших отношений – готовим приложение ИСКО «Ареопад» к регистрации в реестре (как приложение на Sailfish), а это значит, что нам нужно будет адаптировать его ещё и для реестра.

Надеюсь, было интересно. Продолжение следует.




К сожалению, не доступен сервер mySQL