воскресенье, 15 декабря 2013 г.

Конкурс Осенняя Мобилизация

Конкурс Осеняя Мобилизация

Embarcadero предлагает приятный повод поближе познакомиться со своими новыми технологиями в Delphi. Широко рекламируемые FireMonkey, FireDAC, Android и iOS - всё, что хотелось попробовать, но до чего всё никак не доходили мои усталые натруженные руки. Овладевание тем инструментом, который есть, но лежит ещё без дела - не единственный плюс от участия в конкурсе. Есть ещё, конечно, слабая надежда на выигрыш. А с надеждой жить гораздо интереснее. И ещё есть надежда, что читая блоги участников, жюри сделает выводы о том, как лучше помогать программистам в разработке, как улучшить Delphi. Ну и, как знать, возможно удастся подсмотреть что-нибудь полезное у других участников. Обмен, так сказать, опытом.

Мной на конкурс представлено приложение WhoIsWho - аналог нашего корпоративного телефонного справочника. Слепленное на пробу из примеров приложение вдруг стало популярным в нашем коллективе. Люди гораздо охотнее пользуются приложением, чем смотрят те же данные на корпоративном сайтеТакое вот открытие.

https://subversion.assembla.com/svn/who-is-who/trunk/WhoIsWho

Описание назначения приложения:

WhoIsWho - Программа предназначена для знакомства сотрудников в организации друг с другом. И ещё хотелось бы обратить внимание общественности на DeployFolder - сопутствующая программа, которую, возможно, также следует считать кандидатом на участие в конкурсе.

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

В исходный код включены следующие проекты:
1.WhoIsWho - Заявленное на конкурс приложение для Android, iOs, Windows.
2.GetWIWDate - Windows-приложение для получения данных из корпоративной базы данных для приложения WhoIsWho. Сначала я просто сохранял таблицу из MS Management Studio через MS Excel, но потом решился на FireDAC. FireDAC - страшное слово, куча страшных компонентов. Но на удивление, пользоваться оказалось не страшно, и даже можно сказать - удобно. Только исходники FireDAC в поставке с Delphi - явно устаревшие.
3.mess - Windows-приложение для подготовки данных к конкурсу - превращение реальных данных в случайные. Там есть пример смены символов в строке.
4.DeployFolder - Windows-приложение для формирования части deployment Delphi-проекта. Не смотря на то, что это не android, это важный инструмент для разработки на android.

Основной сценарий работы:

1. Упомянутое в разговоре имя сотрудника можно использовать в строке запроса, чтобы понять, о ком шла речь.
2. Увиденного случайно человека можно разыскать по фотографии, применяя в поиске гендерные и возрастные признаки.
3. Легко разыскать нужного сотрудника, использовать номера телефона и прочие сведения, включая фото, для звонка, отправки почты или формирования контактной записи в телефоне и т.п., независимо от доступа к корпоративной сети.
4. В корпоративной среде бывает очень полезно посмотреть, кто родился в текущем или следующем месяце.

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

Идея и реализация.
За основу были взяты примеры TabletMasterDetailWithSearch и шаблон нового приложения Phone-Master Detail.  Славный Ярослав Бровин показывал, как делать формы для разного формата экрана для iOS, но пример для Андроида  - PhotoEditorDemo я увидел только спустя некоторое время. Поэтому я не стал наследовать формы. Может быть, оно и к лучшему.

Хранение данных - трудности выбора
Как и FM, FireDAC был мне чужд. Но как быть? Предлагался IBlite, были примеры с SQLite. И главная непонятность - биндинг! IB напугал лицензированием и разнообразием форм. Не возникло к IB доверия и из-за неудачного примера в поставке к Delphi. SQLite тоже вызвал вопросы. Хотя в поставке для Windows не было нужной DLL, она легко нашлась в Сети. А с русскими буквами - тоже проблема. Опять-таки в поставку вошёл неудачный пример для SQLite. Позднее я увидел, что FireDAC() спасает. Но FireDAC - это ещё одна terra incognita. Слишком много неизвестного для начала - FMX, binding, new DB engine. Совершенно было непонятно, как перейти от источника примерных данных к источнику данных реальных.

Хранение данных - простое решение
Добрый Всеволод Леонов в своём блоге как-то писал о простом текстовом IO для мобильных устройств. Я попробовал - да. Так что - в топку binding, здравствуйте мои дорогие TStringList и INI! Осталось только положить рядом фотографии - и что ещё нужно?

Проблема множества файлов
Deployment сделан ужасно. Мало того, что он сортировку сбрасывает, глючит - задваивает и вдруг перестаёт видеть файлы, так им ещё и просто невозможно добавить количество файлов, превышающее пару десятков. Попробуйте добавить для всех конфигураций путь на устройстве - это каторга.  Я разговаривал на конференциях с людьми - как и что делать, никто не знал. Тогда я сделал для Deployment специальный инструмент DeployFolder. Теперь стало легко добавлять тысячу файлов. И даже когда добавлен только один файл - гораздо удобнее запустить DeployFolder, чем писать пути руками.

Проблема именования пути
Вот это была засада! Пока у меня не было исходников (в триале), выяснить причину беды было невозможно. На симуляторе iOS - всё прекрасно. А на Androide - даже не запускается. Оказывается, дело в искажении имён каталогов при деплойменте. Сами файлы, размещаемые в подкаталогах, остаются в том регистре, в котором они и были. И имя папки - тоже в том же регистре. Но! - в списке файлов для распаковки на устройстве каталоги - в нижнем регистре! И при этом, когда приложение стартует и выделяет из себя данные, в System.StartUpCopy.CopyAssetToFile даже нет никакого assert, на доступность упакованных в приложение файлов.Там написано "// We have a valid AssetManager. Start", но нет "// We have a valid Asset File". Так что вот.

Проблема с добавлением своего стиля
Как добавить новую стильную картинку на компонент, чтобы на разных платформах отражалось по-разному? Я не знаю. Евгений Крюков рассказывал, что не сложно - стоит нажать правую кнопку, и... И вот нет на мобильной форме команды редактирования стиля. А на немобильной - нет ни правой части редактора, где отображена структура разметки, ни содержимого центральной. Редактор стилей не работает.

Что использовать вместо пиктограммы
В Windows вообще нет пиктограмм. Я использовал вместо пиктограммы текст. Мне так понравилось использовать символы, что я так и оставил в телефонной форме букву @ вместо стиля actiontoolbuttonbordered. А как взять из стиля пиктограмму для некнопочного контрола? Просто так картинку нельзя, она должна быть в общем стиле, меняться от платформы к платформе. Я захотел сделать свой сплиттер-експандер, чтобы увеличивать и уменьшать область фотографии. Мне понравились значки "вверх" и "вниз", но они разные на разных платформах. Пришлось взять кнопки целиком. Но они всё время хотели съехать вниз. Чтобы отцентровать их по вертикали я использовал дополнительные Layout. Что получилось - можно увидеть на вертикальной телефонной форме.

Интерактивные жесты 
Интерактивные жесты лучше кликов и стандартных жестов дают обратную реакцию - не надо ждать завершения касания. Но в этом и трудность - они непрерывны, и когда реакция обработана, пользователь ещё не закончил движение, и мы снова ловим жест, который уже не нужен. Например, двойное нажатие на фото и сдвиг по нему - если двигать часто, то это может сойти за двойной тап. Не забываем про Handled! А чтобы "прекратить" отслеживать интерактивный сдвиг я придумал смотреть на время. Если с последнего зафиксированного и обработанного сдвига прошло меньше четверти секунды, то это ещё остаток обработанного движения, и на него не стоит реагировать.

Права на внешнее хранение
Я не знаю, как обновить приложение, не убив его внутренние данные. К счастью, в Android есть внешние каталоги. Но! - если использовать что-то вроде TPath.GetSharedDocumentsPath, то можно нарваться на пустую строку! Шайтан спрятался в пермишинах проекта - надо посмотреть список прав и поставить нужные external write галочки.

Каталог устройства, легко доступный с ПК
Я посмотрел, что корни файловой системы на разных Android-устройствах разные. Но есть у них общее - там лежит папка /Android/. Вроде, все так и делают - берут SharedDocumentsPath и оставляют от него только корень, чтобы сделать свою папку с понятным названием. Но об этом в учебниках не сказано.

OnChange
Событие OnChange в FMX ведёт себя совсем не так, как в VCL. Раньше присвоение ItemIndex или IsChecked не вело к вызову события. А теперь - да. Поэтому при инициализации формы я использую специальный флаг - IsCreated, чтобы отличить внутренние присвоения от действий пользователя.

TPopup
Когда я запускаю TPopup, он выскакивает там, где мышь или было нажатие. Хотелось бы управлять позицией TPopup, но не знаю как. Не сразу я поборол прозрачность TPopup - оказалось, что надо не забывать установить StyleLookup. Ещё при старте следует ограничивать размер TPopup - иначе, он вылезет за границу экрана.

TComboBox vs TPopupBox
TComboBox и TPopupBox выглядят на мобильных устройствах одинаково:  "барабан" на iOS и диалог с радиокнопками на Android. Явное различие появляется на Windows: TComboBox делает прокручиваемый список, а TPopupBox рисует панель, которая может вылезти далеко за пределы экрана.

Хардварный Бэк
Пока не нашёл, как по кнопке "Назад" в Андроиде программно прятать приложение. Когда ходим по закладкам и возвращаемся, то запросто нажать кнопку лишний раз. Если спросить пользователя о закрытии, то что бы он ни ответил, программа не закроется. Пока прибиваю Halt(0). Но это - конечно, варварство.

Флушинг конфига
Я пишу в INI-файл конфигурацию сразу, как что-то поменялось. Но если программу взять в список задач и выкинуть оттуда, то она закроется, не успев сказать "ой", и изменения не будут записаны. Поэтому сразу после Write<T> нужно делать UpdateFile.

Unicode - Проблема со шрифтами 
Википедия даёт нам знаки зодиака в виде юникодных кодов. В Windows эти знаки прекрасно отображаются. Казалось бы, Unicode должен работать на всех платформах, но увы. Пришлось отрезать значки на мобильных устройствах.

Unicode - Особенности текста 
Во-первых, нельзя забывать об указании кодировки при работе с TStringList. Иначе, по умолчанию под Windows будет сохранятся Ansi, который не стоит перетаскивать на мобильное устройство.  Ещё одной особенностью является необходимость указания разделителя строк #13#10 для iOS. Иначе при разборе текста строка может перенестись "вдруг" где-нибудь в середине.

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

Проблемы iOS - Delphi
Конкурс проводится, конечно, для Android. Но суть мобильной разработки на Delphi - многоплатформенность. Поэтому нужно сказать, что у меня с iOS большие проблемы. Во-первых, я не могу запустить отладчик с реальным устройством - Delphi ругается, что у меня два одинаковых сертификата - в системной и в пользовательской связках ключей. Во-вторых, после Ad Hok Delphi виснет с указанием последнего файла в списке Deployment. Delphi я снимаю диспетчером, а файл на Маке получается рабочий.

Проблемы iOS - FMX
Я использую TPopup для ввода параметров. Если для закрытия используется кнопка - не важно, TButton или TSpeedButton, то всё падает. Убрал для iOS кнопки - приходится тыкать в свободное место, что мне кажется неудобным. Кроме того, событие OnClose срабатывает дважды! Ну и зашарить картинку - никак: едва показывается какая-то панель со значками, и всё вылетает. Уж iOs, казалось бы, не первый год осваивается FMX. Вот же ж.

Проблемы iOS - Apple
Не придумал, как сохранить файлы конфигурации, чтобы при обновлении программы настройки сохранялись. Особенно актуально это стало, когда в программе появились пользовательские заметки.

Досадные мелочи при разработке
Мне досаждает порча dpr при добавлении юнита в проект. Delphi добавляет кучу строк  Application.CreateForm(TPhoneMasterDetail, PhoneMasterDetail) и убирает , Androidapi.JNI.GraphicsContentViewText. Я захожу в историю (спасибо челу, который сделал эту штуку в Delphi!) и копирую Androidapi.JNI.GraphicsContentViewText оттуда. Возможно, следует вынести переключение формы в отдельный модуль, но как-то лениво. 

Ограничения смены ориентации
Не понимаю, зачем в FMX сознательно исключается из ориентации телефонного форм-фактора "обратно-вертикальное" положение. Типа, "кнопки" вверху на телефоне не должны быть. С чего бы это? Мало ли, как у телефона провода или ещё какие боковые элементы управления расположены! Иногда перевернуть телефон - очень нормально. Например, Yandex Navigator вертится всяко. А некоторые приложения - не хотят. Но пусть это нежелание лежит на совести программиста. Нельзя лишать программиста возможности сделать "неправильно", но так, как удобно пользователю.

Нехватка мощностей
Люди жалуются, что некоторые фото на Андроиде - прямоугольник Малевича, т.е. чернота. Я посмотрел - да, начиная примерно с около 400 КБ фото чернеют. Поэтому при загрузке данных я добавил в GetWIWDate пробежку по картинкам, где уменьшаю на 3/4 габариты фото, которые больше 375 КБ. Если фото не уменьшилось до нужного размера, процесс повторяется. Интересно, что от владельцев iOS жалоб не поступало.

Прочие мелочи-находки
Не сразу нашёл, поэтому, наверно, кому-то, новичку интересно будет. По умолчанию FMX делает форму для Windows на весь полный экран без заголовка и панели задач. Эта неприятность исправляется в Object Inspector - Full Screen. Убрать "макет устройства" - Alt+F12 и DesignerMobile = False. Ф-я IsTablet присутствует в платформенных модулях графики.  Я за образец взял PhotoEditorDemo - пример, где Ярослав сложил эти ф-ии вместе. Это скорее всего просто косяк FM, но после того, как я изменил содержимое memo (даже внутри BeginUpdate-EndUpdate) в Windows надо вызывать Repaint - иначе изменений не видно, пока не кликнешь по контролу мышкой. Ещё удивился, что (см.GetWIWDate ) между TBitmap.CreateFromFile и TBitmap.SaveToFile надо вставлять TFile.Delete - иначе при записи файл будет занят!

А ещё - мне ОЧЕНЬ-ОЧЕНЬ-ОЧЕНЬ не хватает WITH. Любил я его.