пятница, 27 декабря 2013 г.

DeployFolder - Развёртывание мобильного подкаталога

DeployFolder - Развёртывание мобильного подкаталога

Автоматическое прописывание пути развёртывания файла для мобильного приложения.


Для конкурса Embarcadero "Осеннее настроение 2013" исходник выложен в папку с программой, разработка которой без DeployFolder была бы совершенно невозможной: https://subversion.assembla.com/svn/who-is-who/trunk/WhoIsWho

Вы можете отдельно взять исходный код программы здесь: https://db.tt/YAOLB40g. Но я не гарантирую, что оставлю его без изменения.


Итак, с чего началось создание программы - с простого вопроса: как добавить файл в проект мобильного приложения? Казалось бы, есть простой ответ: Project|Deployment и Add Files. Так? Не совсем. Вам ещё следует прописать каталог, в который файл попадёт на мобильном устройстве. Доступ к этому файлу в коде будет выглядеть примерно так:

  DataDir :=
{$IF Defined(MSWINDOWS)}
    '..\..'{runing in windows - <Platform>\<Configuration>}
{$ELSE}
    TPath.GetDocumentsPath
{$ENDIF}
    +PathDelim + 'Data' + PathDelim;


В данном случае 'Data' – это имя подпапки, в которой лежат мои файлы для приложения внутри папки проекта. А что значит TPath.GetDirectory – зависит от мобильной системы. Для iOs – это '.\StartUp\Documents', а для Android – ‘.\assets\internal’.





К сожалению, эти константы вы вынуждены вносить вручную для кучи разных конфигураций. Не вздумайте что-нибудь забыть или перепутать – приложение может не найти ваш файл после запуска на устройстве или в эмуляторе! Это не очень удобно, не правда ли? Давайте немного облегчим себе жизнь и заполним пути автоматически. Тем более, если количество добавленных файлов больше одного!


Итак, где хранятся пути развёртывания файла? Нет, не в <ProjectName>.deployproj, а в <ProjectName>.dproj! По правде говоря, сначала я был немного озадачен файлом <deployproj>. С одной стороны – расширение файла «.deployproj» так и просит заглянуть в этот файл. И – да! – там перечислены все те файлы, которые были добавлены в проект. Но с другой стороны - всё, что я менял в <deployproj>, вдруг исчезало самым таинственным образом - мистика! Если вы читаете эти строки, то вы уже не попадётесь в эту ловушку. А теперь давайте посмотрим внутрь <dproj> и сравним его кусочек с таблицей Deployment <ProjectName> в IDE.

                </Excluded_Packages>
            </Delphi.Personality>
            <Platforms>
                <Platform value="Android">True</Platform>
                <Platform value="iOSDevice">True</Platform>
                <Platform value="iOSSimulator" ActiveMobileDevice="iPad">True</Platform>
                <Platform value="Win32">True</Platform>
            </Platforms>
            <Deployment>
                <DeployFile LocalName="Data\896.jpg" Class="File">
                    <Platform Name="Android">
                        <RemoteDir>.\assets\internal\Data\</RemoteDir>
                        <RemoteName>896.jpg</RemoteName>
                    </Platform>
                    <Platform Name="iOSDevice">
                        <RemoteDir>.\StartUp\Documents\Data\</RemoteDir>
                        <RemoteName>896.jpg</RemoteName>
                    </Platform>
                    <Platform Name="Win32">
                        <RemoteName>896.jpg</RemoteName>
                    </Platform>
                    <Platform Name="iOSSimulator">
                        <RemoteDir>.\StartUp\Documents\Data\</RemoteDir>
                        <RemoteName>896.jpg</RemoteName>
                    </Platform>
                </DeployFile>
                <DeployFile LocalName="Data\816.jpg" Class="File">
                    <Platform Name="Android">


Без труда можно заметить, что добавленные нами файлы имеют тип “File”. В зависимости от платформы у файла может быть разный каталог размещения. Зная, что <dproj> - это в сущности XML, мы можем бросить на форму из палитры Internet компонент TXMLDocument, найти узел “\Project\ProjectExtensions\BorlandProject\Deployment” и расставить теги <RemoteDir>, в которых хранятся пути развёртывания наших файлов.


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

 Ещё отмечу, что новый путь я ставлю только для платформ из списка 'Android','iOSDevice','iOSSimulator'.

Стоит также заметить, что после обработки проекта я вставил ещё несколько действий. Во-первых - вы можете смеяться, но я не знаю, как удалить строку XML-заголовка, которая появляется в файле проекта после сохранения его компонентом XMLDocument1. Поэтому, не сильно задумываясь, я опять считываю файл на этот раз в TStringList и удаляю первую строчку. Во-вторых, моя Delphi (наткнулся на это в XE5 trial) не хочет собирать проект, если остался старый deployproj. Что ж, удалить его не сложно.

DeployFolder очень удобно поместить в IDE как инструмент c параметром $PROJECT. Этот инструмент можно вызывать каждый раз, когда вы добавили файлы в развёртывание проекта. Например так:


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


Но, увы - на больших проектах происходит ошибка занятости файла*. Ещё одна проблема - при открытой странице Project|Deployment новые файлы задваиваются. Поэтому я закрываю проект после добавления новых файлов и вызываю инструмент без параметров. Обычно, когда работа идёт над одним и тем же проектом, никаких неудобств не возникает - имя проекта сохраняется.

Кстати. Я стал задумываться, не сделать ли каталогам псевдонимы, например c:\mo\x=\x. Что вы об этом думаете? Есть у вас ещё предложения? Пишите мне – alhymov@mail.ru

И да, ВНИМАНИЕ, УВАГА, ATTENTION, ACHTUNG: При формировании для Андроид списка дополнительных файлов Delphi (или SDK?) формирует список, где имена каталогов в нижнем регистре! Имена самих каталогов в пакете приложения при этом остаются в том регистре, в котором вы написали на вкладке Project|Deployment. При старте приложения на устройстве происходит выгрузка файлов из пакета приложения в "песочницу". И, поскольку имена каталогов в списке и в пакете не совпадают, приложение даже не проходит строчку begin в проекте. Будьте бдительны! А в моей программе это обстоятельство уже учитывается.
---
ошибка занятости файла судя по всему связана с тем, что проект хранится в DropBpox. Т.е. как только файл изменился, его начинают тащить в сеть и, видимо, для этого блокируют. Т.е. надо перед первой записью самому файл захватить и отпустить только после второй записи. Если кто-то сразу мне кинет ссылку на блокировку файла, буду признателен. Но я скорее всего пойду по другому пути - однократной записи. Т.е. ХМL записать в поток и считать его уже оттуда в StringList, а не из файла. И уже потом сохранить один раз. Пока руки не дошли.**
---
** Руки дошли - см. DropBox против файлов http://alhymov.blogspot.ru/2014/02/dropbox.html

воскресенье, 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. Любил я его.