Разработка кроссплатформенных мобильных приложений в Delphi #2
В предыдущей части цикла мы сделали обзор основных возможностей новой RAD Studio XE5. Сегодня же перейдем к практике. Прежде всего, давайте определимся с задачей.
Постановка задачи
Не смотря на некоторую академичность задачи, в качестве примера мы выбрали кулинарную книгу. А для придания оригинальности разрабатываемому приложению, попытаемся расширить обычный для подобных программ функционал несколькими дополнительными функциями:
Пересчет количества требуемых продуктов.
Обычно в рецептах указывается точное количество используемых продуктов. Но если требуется изменить количество порций, то соответственно, необходимо пересчитывать и количество продуктов. В процессе приготовления блюда это не всегда удобно.
Таймер.
Если вы новичок в кулинарии, то, вам следует использовать рецепты, в которых четко указано время той или иной операции. И таймер на планшете, телефоне или нетбуке вполне может стать удобной заменой кухонному таймеру.
Часто бывает, что несколько операций выполняются параллельно. Каждая операция может иметь свой «таймер».
При этом нас интересует, как «настольная», так и мобильная реализация приложения. Мобильное приложение будет использоваться в качестве пособия для визуализации рецепта и сопровождения процесса подготовки. Настольная версия может быть использована в более расширенном функционале для, например, формирования новых рецептов.
Здесь следует оговориться, что в принципе, поваренные книги, как и книги по программированию, могут быть рассчитаны на разный уровень подготовленности читателей. В нашем случае речь идет о «рецептах для чайников», т. е. тех, в которых указывается точное количество продуктов и точное время того или иного действия.
Данное приложение мы реализуем для Windows и для Android. Затем на основе единой базы исходных кодов мы сможем выполнить портирование приложения на MacOS и iOS.
Выбор средств разработки
Прежде чем приступить непосредственно к разработке, давайте определимся с инструментарием. Разумеется, что данные, которые будут использоваться в приложении, целесообразнее всего хранить в базе данных.
Для чистоты эксперимента в качестве СУБД используем SQLite. Эта СУБД обладает рядом преимуществ, среди которых скорость работы, простота использования, экономичность в отношении ресурсов. Она идеально подходит для решения несложных задач, а кроме того, Android имеет встроенную поддержку SQLite.
В дальнейшем мы рассмотрим, каким образом можно перевести приложение на использования СУБД InterBase и покажем все преимущества «родного» решения от Embarcadero.
Вместе с приложением мы будем распространять уже готовую СУБД, созданную отдельно. Такой подход обусловлен спецификой задачи.
Конечно же, нам понадобиться Delphi XE5. Для создания Windows приложения в принципе мы могли бы ограничитьсяProfessional редакциях. Но для т.н. мобильной разработки нам понадобиться как минимум Enterprise редакция продукта. В качестве альтернативы можно воспользоваться пакетом Mobile Add-On Pack for Delphi XE5 Professional.
Для работы с SQLite в Windows нам понадобится скачать с официального сайта библиотеку sqlite-dll-win32-x86-3080100.zip. Проще всего поместить данную библиотеку в одну из системных папок (например, Windows/SYSTEM32).
В настоящий момент существует довольно большое количество бесплатных средств администрирования баз данных SQLite. Вы вполне можете воспользоваться одним из них.
Доступ к базе данных из приложения мы будем осуществлять с помощью библиотеки FireDAC.
FireDAC — это универсальная библиотека доступа к данным, предназначенная для разработки приложений, подключаемых к различным базам данных. С помощью FireDAC можно разрабатывать, как VCL, так и FireMonkey приложения. Библиотека поддерживает следующие СУБД: InterBase, SQLite, MySQL, SQL Server, Oracle, PostgreSQL, DB2, SQL Anywhere,Advantage DB, Firebird, Access, Informix. Однако, на данном этапе, в мобильных приложениях «напрямую» (без использования технологии DataSnap) с помощью FireDAC можно подключиться только к SQLite и InterBase.
Создание базы данных
Процесс разработки мы начнем с создания структуры базы данных. Логическая модель ее приведена на диаграмме.
Данная диаграмма получена с помощью еще одного инструмента от компании Embarcadero Technologies — ER/Studio. Developer редакция этого продукта доступна пользователям RAD Studio Architect. В рамках настоящей публикации мы не станем детально останавливаться на описании ER/Studio. Этот мощный инструмент вполне заслуживает того, что бы посвятить ему отдельный материал.
При создании структуры базы данных каждой сущности логической модели будет соответствовать физическая таблица. Вкратце рассмотрим назначение полученных таблиц.
В таблице tblFoodstuff мы будем хранить список всех возможных продуктов. В таблице tblUnit – список единиц измерения (грамм, ложка, стакан и т.д.). Таблица tblRecipe содержит список рецептов. В таблицах tblIngredientes и tblActionсодержатся перечень ингредиентов (с указанием количества в поле qty) и действий (с указанием времени и описаниями), соответственно.
Для создания SQLite базы вы можете просто воспользоваться приведенным ниже скриптом.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
CREATE TABLE <span style="color: #000066;">[</span>tblAction<span style="color: #000066;">]</span> <span style="color: #000066;">(</span> <span style="color: #000066;">[</span>Id<span style="color: #000066;">]</span> <span style="color: #000066; font-weight: bold;">INTEGER</span> <span style="color: #000000; font-weight: bold;">NOT</span> NULL PRIMARY KEY AUTOINCREMENT<span style="color: #000066;">,</span> <span style="color: #000066;">[</span>IdRc<span style="color: #000066;">]</span> <span style="color: #000066; font-weight: bold;">INTEGER</span> <span style="color: #000000; font-weight: bold;">NOT</span> NULL<span style="color: #000066;">,</span> <span style="color: #000066;">[</span>Sec<span style="color: #000066;">]</span> <span style="color: #000066; font-weight: bold;">INTEGER</span> NULL<span style="color: #000066;">,</span> <span style="color: #000066;">[</span>DescriptionShort<span style="color: #000066;">]</span> VARCHAR<span style="color: #000066;">(</span><span style="color: #0000ff;">128</span><span style="color: #000066;">)</span> <span style="color: #000000; font-weight: bold;">NOT</span> NULL<span style="color: #000066;">,</span> <span style="color: #000066;">[</span>DescriptionExtendent<span style="color: #000066;">]</span> TEXT <span style="color: #000000; font-weight: bold;">NOT</span> NULL <span style="color: #000066;">)</span><span style="color: #000066;">;</span> CREATE TABLE <span style="color: #000066;">[</span>tblFoodstuff<span style="color: #000066;">]</span> <span style="color: #000066;">(</span> <span style="color: #000066;">[</span>Id<span style="color: #000066;">]</span> <span style="color: #000066; font-weight: bold;">INTEGER</span> PRIMARY KEY AUTOINCREMENT NULL<span style="color: #000066;">,</span> <span style="color: #000066;">[</span>Abbr<span style="color: #000066;">]</span> VARCHAR<span style="color: #000066;">(</span><span style="color: #0000ff;">10</span><span style="color: #000066;">)</span> NULL<span style="color: #000066;">,</span> <span style="color: #000066;">[</span>Title<span style="color: #000066;">]</span> VARCHAR<span style="color: #000066;">(</span><span style="color: #0000ff;">50</span><span style="color: #000066;">)</span> NULL <span style="color: #000066;">)</span><span style="color: #000066;">;</span> CREATE TABLE <span style="color: #000066;">[</span>tblIngredientes<span style="color: #000066;">]</span> <span style="color: #000066;">(</span> <span style="color: #000066;">[</span>Id<span style="color: #000066;">]</span> <span style="color: #000066; font-weight: bold;">INTEGER</span> <span style="color: #000000; font-weight: bold;">NOT</span> NULL PRIMARY KEY AUTOINCREMENT<span style="color: #000066;">,</span> <span style="color: #000066;">[</span>IdCB<span style="color: #000066;">]</span> <span style="color: #000066; font-weight: bold;">INTEGER</span> <span style="color: #000000; font-weight: bold;">NOT</span> NULL<span style="color: #000066;">,</span> <span style="color: #000066;">[</span>IdFS<span style="color: #000066;">]</span> <span style="color: #000066; font-weight: bold;">INTEGER</span> <span style="color: #000000; font-weight: bold;">NOT</span> NULL<span style="color: #000066;">,</span> <span style="color: #000066;">[</span>IdUnit<span style="color: #000066;">]</span> <span style="color: #000066; font-weight: bold;">INTEGER</span> <span style="color: #000000; font-weight: bold;">NOT</span> NULL<span style="color: #000066;">,</span> <span style="color: #000066;">[</span>qty<span style="color: #000066;">]</span> <span style="color: #000066; font-weight: bold;">INTEGER</span> NULL <span style="color: #000066;">)</span><span style="color: #000066;">;</span> CREATE TABLE <span style="color: #000066;">[</span>tblRecipe<span style="color: #000066;">]</span> <span style="color: #000066;">(</span> <span style="color: #000066;">[</span>Id<span style="color: #000066;">]</span> <span style="color: #000066; font-weight: bold;">INTEGER</span> <span style="color: #000000; font-weight: bold;">NOT</span> NULL PRIMARY KEY AUTOINCREMENT<span style="color: #000066;">,</span> <span style="color: #000066;">[</span>Title<span style="color: #000066;">]</span> VARCHAR<span style="color: #000066;">(</span><span style="color: #0000ff;">150</span><span style="color: #000066;">)</span> <span style="color: #000000; font-weight: bold;">NOT</span> NULL <span style="color: #000066;">)</span><span style="color: #000066;">;</span> CREATE TABLE <span style="color: #000066;">[</span>tblUnit<span style="color: #000066;">]</span> <span style="color: #000066;">(</span> <span style="color: #000066;">[</span>Id<span style="color: #000066;">]</span> <span style="color: #000066; font-weight: bold;">INTEGER</span> <span style="color: #000000; font-weight: bold;">NOT</span> NULL PRIMARY KEY AUTOINCREMENT<span style="color: #000066;">,</span> <span style="color: #000066;">[</span>UnitName<span style="color: #000066;">]</span> VARCHAR<span style="color: #000066;">(</span><span style="color: #0000ff;">25</span><span style="color: #000066;">)</span> <span style="color: #000000; font-weight: bold;">NOT</span> NULL<span style="color: #000066;">,</span> <span style="color: #000066;">[</span>Abbr<span style="color: #000066;">]</span> VARCHAR<span style="color: #000066;">(</span><span style="color: #0000ff;">7</span><span style="color: #000066;">)</span> <span style="color: #000000; font-weight: bold;">NOT</span> NULL <span style="color: #000066;">)</span><span style="color: #000066;">;</span> CREATE INDEX <span style="color: #000066;">[</span>IDX_TBLACTION_Recipe<span style="color: #000066;">]</span> <span style="color: #000000; font-weight: bold;">ON</span> <span style="color: #000066;">[</span>tblAction<span style="color: #000066;">]</span><span style="color: #000066;">(</span> <span style="color: #000066;">[</span>IdRc<span style="color: #000066;">]</span> ASC <span style="color: #000066;">)</span><span style="color: #000066;">;</span> CREATE INDEX <span style="color: #000066;">[</span>IDX_TBLINGREDIENTES_<span style="color: #000066;">]</span> <span style="color: #000000; font-weight: bold;">ON</span> <span style="color: #000066;">[</span>tblIngredientes<span style="color: #000066;">]</span><span style="color: #000066;">(</span> <span style="color: #000066;">[</span>IdCB<span style="color: #000066;">]</span> ASC <span style="color: #000066;">)</span><span style="color: #000066;">;</span> CREATE INDEX <span style="color: #000066;">[</span>idxFoodStuff<span style="color: #000066;">]</span> <span style="color: #000000; font-weight: bold;">ON</span> <span style="color: #000066;">[</span>tblIngredientes<span style="color: #000066;">]</span><span style="color: #000066;">(</span> <span style="color: #000066;">[</span>IdFS<span style="color: #000066;">]</span> ASC <span style="color: #000066;">)</span><span style="color: #000066;">;</span> CREATE INDEX <span style="color: #000066;">[</span>idxRecipe<span style="color: #000066;">]</span> <span style="color: #000000; font-weight: bold;">ON</span> <span style="color: #000066;">[</span>tblIngredientes<span style="color: #000066;">]</span><span style="color: #000066;">(</span> <span style="color: #000066;">[</span>IdCB<span style="color: #000066;">]</span> ASC <span style="color: #000066;">)</span><span style="color: #000066;">;</span> CREATE INDEX <span style="color: #000066;">[</span>idxUnit<span style="color: #000066;">]</span> <span style="color: #000000; font-weight: bold;">ON</span> <span style="color: #000066;">[</span>tblIngredientes<span style="color: #000066;">]</span><span style="color: #000066;">(</span> <span style="color: #000066;">[</span>IdUnit<span style="color: #000066;">]</span> ASC <span style="color: #000066;">)</span><span style="color: #000066;">;</span> |
Создание Windows приложения
Непосредственно процесс разработки приложения мы организуем следующим образом. Начнем с создания базовой версии приложения для Windows, затем портируем его на платформу Android. Далее будем расширять функционал обоих приложений параллельно. MacOS и iOS версии приложения станут заключительным этапом. К их созданию мы приступим после того, как будем иметь готовую Windows и Android версию.
IDE Delphi предоставляет несколько шаблонов новых приложений. В данном случае нас интересует FireMonkey Desktop Application.
После выбора соответствующего пункта меню на экран будет выведен дополнительный диалог, в котором будет предложено выбрать тип нового FireMonkey приложения – HD (High Definition) или 3D.
Переименуем главную форму приложения (в инспекторе объектов меняем свойство Name) и сохраним вновь созданный проект. Добавим в проект новый Data Module (меню File| Other ветка Delphi Files), переименуем и сохраним его.
Таким образом, мы создали новый проект и подготовились непосредственно к разработке.
Настройка FireDAC соединения
Первое, что нам необходимо сделать – настроить подключение к базе данных. В FireMonkey данная процедура существенно не отличается от VCL.
Поместим в DataModule компоненты TFDConnection, TFDGUIxWaitCursor и TFDPhysSQLiteDriverLink. В Object Inspector изменим значение свойства LoginPrompt компонента TFDConnection:
LoginPrompt = False
Двойной щелчок по компоненту на форме вызовет диалог настройки подключения.
В качестве Driver ID в нашем случае следует указать SQLite, а также в качестве значения параметра Database указать путь к файлу базы данных. Проверить правильность настроек можно с помощью кнопки Test.
Сразу же создадим функцию, отвечающую за подключение к БД в Run Time.
1 2 3 4 5 6 7 8 |
<span style="color: #000000; font-weight: bold;">function</span> TDM<span style="color: #000066;">.</span><span style="color: #006600;">ConnectToDB</span><span style="color: #000066;">:</span> <span style="color: #000066; font-weight: bold;">Boolean</span><span style="color: #000066;">;</span> <span style="color: #000000; font-weight: bold;">begin</span> <span style="color: #000000; font-weight: bold;">try</span> FDConnection1<span style="color: #000066;">.</span><span style="color: #006600;">Connected</span> <span style="color: #000066;">:</span><span style="color: #000066;">=</span> <span style="color: #000000; font-weight: bold;">True</span><span style="color: #000066;">;</span> <span style="color: #000000; font-weight: bold;">except</span> <span style="color: #000000; font-weight: bold;">end</span><span style="color: #000066;">;</span> Result<span style="color: #000066;">:</span><span style="color: #000066;">=</span> FDConnection1<span style="color: #000066;">.</span><span style="color: #006600;">Connected</span><span style="color: #000066;">;</span> <span style="color: #000000; font-weight: bold;">end</span><span style="color: #000066;">;</span> |
Для того, что бы данную функцию можно было вызвать из главного модуля, вынесем ее заголовок в секцию public.
Главная форма приложения
На главную форму приложения последовательно поместим компоненты TGroupBox, TTabControl и TSplitter. Настроим для каждого из них свойство Align (alLeft, alLeft и alClient, соответственно). На левую панель помещаем два компонента – TListBox и TBindNavigator. Размещаем их как показано на рисунке. Двойным щелчком мыши вызовем дизайнер пунктов компонента TTabControl, и создадим два пункта.
В левом части нашего приложения будет отображаться список рецептов. На вкладках в правой части – список ингредиентов блюда (с указанием количества) и, непосредственно, сам рецепт, состоящий из определенного списка действий.
Подключение данных
Как мы уже говорили, в FireMonkey нет специальных компонентов отображения данных. Вместо этого используется механизм связывания LiveBinding. Давайте посмотрим, как он работает. Подключим список рецептов к компоненту TListBox на главной форме. Прежде всего, нужно создать набор данных для работы со списком рецептов. В модуле данных используем компонент TFDTable. Настроим его свойства следующим образом:
1 |
Name <span style="color: #000066;">=</span> fAddRecipe BorderStyle <span style="color: #000066;">=</span> bsToolWindow Position <span style="color: #000066;">=</span> poMainFormCenter |
В код нашей функции ConnectToDB добавим вызов метода открытия набора данных.
1 |
FDTRecipe<span style="color: #000066;">.</span><span style="color: #006600;">Open</span><span style="color: #000066;">;</span> |
Для того, что бы установить соединение с базой в режиме Run Time следует добавить вызов функции ConnectToDB. Можно привязать его к событию OnCreate модуля данных.
1 2 3 4 |
<span style="color: #000000; font-weight: bold;">procedure</span> TDM<span style="color: #000066;">.</span><span style="color: #006600;">DataModuleCreate</span><span style="color: #000066;">(</span>Sender<span style="color: #000066;">:</span> <span style="color: #000066; font-weight: bold;">TObject</span><span style="color: #000066;">)</span><span style="color: #000066;">;</span> <span style="color: #000000; font-weight: bold;">begin</span> DM<span style="color: #000066;">.</span><span style="color: #006600;">ConnectToDB</span><span style="color: #000066;">;</span> <span style="color: #000000; font-weight: bold;">end</span><span style="color: #000066;">;</span> |
На начальном этапе разработки приложения мы не будем обрабатывать исключения, которые могут возникнуть при подключении, хотя в дальнейшем это необходимо будет сделать.
Организуем вывод данных из таблицы TRecipe в список на главной форме. Для этого, прежде всего, добавим модуль данных в секцию Uses модуля главной формы программы.
1 2 3 4 5 |
<span style="color: #000000; font-weight: bold;">interface</span> <span style="color: #000000; font-weight: bold;">uses</span> uDM<span style="color: #000066;">,</span> System<span style="color: #000066;">.</span><span style="color: #006600;">SysUtils</span><span style="color: #000066;">,</span> System<span style="color: #000066;">.</span><span style="color: #006600;">Types</span><span style="color: #000066;">,</span> System<span style="color: #000066;">.</span><span style="color: #006600;">UITypes</span><span style="color: #000066;">,</span> System<span style="color: #000066;">.</span><span style="color: #006600;">Classes</span><span style="color: #000066;">,</span> System<span style="color: #000066;">.</span><span style="color: #006600;">Variants</span><span style="color: #000066;">,</span> |
В редакторе формы вызовем контекстное меню и выберем пункт Bind Visually. В нижней части экрана откроется LiveBinding Designer. В нем в виде элементов диаграммы представлены объекты и их основные свойства. Простым перетаскиванием свяжем свойства трех объектов так, как это показано на рисунке.
Собственно, это все, что нужно для отображения данных из таблицы рецептов (т.е. списка названий рецептов). Если мы сейчас запустим программу, она покажет список, но он будет пустым, и нам следует позаботиться о том, как добавлять рецепты.
Создадим новую FireMonkey HD форму. Настроим ее свойства следующим образом:
Name = fAddRecipe BorderStyle = bsToolWindow Position = poMainFormCenter
Подключим модуль данных и поместим на форме компонент TEdit и две кнопки. В LiveBinding Designer свяжем свойство Text компонента TEdit с полем Title набора данных.
Сохраним новую форум и удалим ее из списка автоматически создаваемых форм (меню Project|Options вкладка Forms).
Для главной формы приложения создадим метод:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
<span style="color: #000000; font-weight: bold;">procedure</span> TfMain<span style="color: #000066;">.</span><span style="color: #006600;">RecipeAfterInsert</span><span style="color: #000066;">(</span>DataSet<span style="color: #000066;">:</span> TDataSet<span style="color: #000066;">)</span><span style="color: #000066;">;</span> <span style="color: #000000; font-weight: bold;">var</span> fAddRecipe<span style="color: #000066;">:</span> TfAddRecipe<span style="color: #000066;">;</span> <span style="color: #000000; font-weight: bold;">begin</span> <span style="color: #000000; font-weight: bold;">try</span> fAddRecipe<span style="color: #000066;">:</span><span style="color: #000066;">=</span> TfAddRecipe<span style="color: #000066;">.</span><span style="color: #006600;">Create</span><span style="color: #000066;">(</span>Application<span style="color: #000066;">)</span><span style="color: #000066;">;</span> fAddRecipe<span style="color: #000066;">.</span><span style="color: #006600;">ShowModal</span><span style="color: #000066;">;</span> <span style="color: #000000; font-weight: bold;">if</span> fAddRecipe<span style="color: #000066;">.</span><span style="color: #006600;">ModalResult</span> <span style="color: #000066;">=</span> mrOk <span style="color: #000000; font-weight: bold;">then</span> <span style="color: #000000; font-weight: bold;">begin</span> <span style="color: #000000; font-weight: bold;">if</span> DataSet<span style="color: #000066;">.</span><span style="color: #006600;">State</span> <span style="color: #000000; font-weight: bold;">in</span> <span style="color: #000066;">[</span>dsInsert<span style="color: #000066;">,</span> dsEdit<span style="color: #000066;">]</span> <span style="color: #000000; font-weight: bold;">then</span> DataSet<span style="color: #000066;">.</span><span style="color: #006600;">Post</span><span style="color: #000066;">;</span> <span style="color: #000000; font-weight: bold;">end</span> <span style="color: #000000; font-weight: bold;">else</span> <span style="color: #000000; font-weight: bold;">begin</span> <span style="color: #000000; font-weight: bold;">if</span> DataSet<span style="color: #000066;">.</span><span style="color: #006600;">State</span> <span style="color: #000000; font-weight: bold;">in</span> <span style="color: #000066;">[</span>dsInsert<span style="color: #000066;">,</span> dsEdit<span style="color: #000066;">]</span> <span style="color: #000000; font-weight: bold;">then</span> DataSet<span style="color: #000066;">.</span><span style="color: #006600;">Cancel</span><span style="color: #000066;">;</span> <span style="color: #000000; font-weight: bold;">end</span><span style="color: #000066;">;</span> <span style="color: #000000; font-weight: bold;">finally</span> <span style="color: #000066;">FreeAndNil</span><span style="color: #000066;">(</span>fAddRecipe<span style="color: #000066;">)</span><span style="color: #000066;">;</span> <span style="color: #000000; font-weight: bold;">end</span><span style="color: #000066;">;</span> <span style="color: #000000; font-weight: bold;">end</span><span style="color: #000066;">;</span> |
Теперь, запустив приложение, мы получим список рецептов, который можно модифицировать с помощью навигатора. При добавлении нового рецепта на экране появляется форма с полем ввода.
В этой части цикла мы создали простейшее FireMonkey приложение для Windows. Научились устанавливать соединение с базой данных при помощи пакета FireDAC и рассмотрели простейший пример использования LiveBinding.
В следующей, возможно самой интересной, части цикла мы создадим первое Android приложение.
Оставайтесь с нами…
Источник: habrahabr.ru