Пример кроссплатформенного приложения: задача
Tasky Portable — это простое приложение списка задач. В этом документе описывается, как он был разработан и построен, следуя инструкциям в документе "Создание кроссплатформенных приложений ". В обсуждении рассматриваются следующие области:
Процесс разработки
Рекомендуется создать дорожную карту для того, что вы хотите достичь, прежде чем приступить к написанию кода. Это особенно верно для кроссплатформенной разработки, где вы создаете функциональные возможности, которые будут предоставляться несколькими способами. Начиная с четкой идеи о том, что вы создаете, экономит время и усилия позже в цикле разработки.
Требования
Первым шагом в разработке приложения является определение необходимых функций. Это могут быть высокоуровневые цели или подробные варианты использования. Задача имеет простые функциональные требования:
- Просмотр списка задач
- Добавление, изменение и удаление задач
- Задайте для задачи состояние "готово"
Следует рассмотреть возможность использования функций для конкретной платформы. Может ли задача воспользоваться преимуществами геофенсинга iOS или плитки Windows Phone Live? Даже если вы не используете функции для конкретной платформы в первой версии, необходимо заранее спланировать, чтобы обеспечить их размещение на уровнях бизнес- и данных.
Проектирование пользовательского интерфейса
Начните с высокоуровневого дизайна, который можно реализовать на целевых платформах. Обратите внимание на ограничения пользовательского интерфейса с спецификациями платформы. Например, TabBarController
в iOS может отображаться более пяти кнопок, в то время как эквивалент Windows Phone может отображать до четырех.
Нарисуйте поток экрана с помощью выбранного средства (работа бумаги).
Модель данных
Зная, какие данные должны храниться, поможет определить, какой механизм сохраняемости следует использовать. Дополнительные сведения о доступных механизмах хранения см. в кроссплатформенных доступах к данным и помогут решить их. Для этого проекта мы будем использовать SQLite.NET.
Задача должна хранить три свойства для каждого объекта TaskItem:
- Name — строка.
- Заметки — строка
- Готово — Логический
Основные функции
Рассмотрим API, который пользовательский интерфейс должен использовать для удовлетворения требований. Для списка задач требуются следующие функции:
- Вывод списка всех задач — для отображения основного списка всех доступных задач
- Получение одной задачи — при касании строки задачи
- Сохранение одной задачи — при изменении задачи
- Удаление одной задачи — при удалении задачи
- Создание пустой задачи — при создании новой задачи
Для повторного использования кода этот API следует реализовать один раз в переносимой библиотеке классов.
Внедрение
После того как проект приложения будет согласован, рассмотрите, как оно может быть реализовано в виде кроссплатформенного приложения. Это станет архитектурой приложения. Следуя инструкциям в документе "Создание кроссплатформенных приложений" , код приложения должен быть разбит на следующие части:
- Общий код — общий проект, содержащий повторно используемый код для хранения данных задачи; предоставляет класс модели и API для управления сохранением и загрузкой данных.
- Код для конкретной платформы — проекты, которые реализуют собственный пользовательский интерфейс для каждой операционной системы, используя общий код в качестве серверной части.
Эти две части описаны в следующих разделах.
Общий код (PCL)
Tasky Portable использует стратегию переносимой библиотеки классов для совместного использования общего кода. См. документ "Параметры кода общего доступа" для описания параметров совместного использования кода.
Весь общий код, включая уровень доступа к данным, код базы данных и контракты, помещается в проект библиотеки.
Полный проект PCL показан ниже. Весь код в переносимой библиотеке совместим с каждой целевой платформой. При развертывании каждое собственное приложение будет ссылаться на библиотеку.
На схеме классов ниже показаны классы, сгруппированные по слоям. Класс SQLiteConnection
является стандартным кодом из пакета Sqlite-NET. Остальные классы — это пользовательский код для Tasky. TaskItem
Классы TaskItemManager
представляют API, предоставляемые приложениям для конкретной платформы.
Использование пространств имен для разделения слоев помогает управлять ссылками между каждым слоем. Проекты, относящиеся к платформе, должны включать using
только инструкцию для бизнес-уровня. Уровень доступа к данным и уровень данных должны быть инкапсулированы API, предоставляемым TaskItemManager
в бизнес-слое.
Ссылки
Переносимые библиотеки классов должны использоваться на нескольких платформах, каждый из которых имеет различные уровни поддержки функций платформы и платформы. Из-за этого существуют ограничения, на которые можно использовать пакеты и библиотеки платформы. Например, Xamarin.iOS не поддерживает ключевое слово c# dynamic
, поэтому переносимая библиотека классов не может использовать любой пакет, зависящий от динамического кода, даже если такой код будет работать в Android. Visual Studio для Mac не позволит добавлять несовместимые пакеты и ссылки, но следует учитывать ограничения, чтобы избежать сюрпризов позже.
Примечание. Вы увидите, что библиотеки платформы ссылок на проекты не использовались. Эти ссылки включены в состав шаблонов проектов Xamarin. Когда приложения компилируются, процесс связывания удаляет неуправляемый код, поэтому даже если System.Xml
ссылка была указана, он не будет включен в окончательное приложение, так как мы не используем какие-либо функции Xml.
Уровень данных (DL)
Уровень данных содержит код, который выполняет физическое хранилище данных , будь то база данных, неструктурированные файлы или другой механизм. Уровень данных Tasky состоит из двух частей: библиотеки SQLite-NET и пользовательского кода, добавленного для его подключения.
Задача использует пакет NuGet Sqlite-net (опубликованный Фрэнком Крюгером) для внедрения кода SQLite-NET, который предоставляет интерфейс базы данных "Сопоставление объектов-реляционных" (ORM). Класс TaskItemDatabase
наследует и SQLiteConnection
добавляет необходимые методы Create, Read, Update, Delete (CRUD) для чтения и записи данных в SQLite. Это простая реализация универсальных методов CRUD, которые можно повторно использовать в других проектах.
Это TaskItemDatabase
одноэлементный элемент, гарантирующий, что весь доступ возникает в одном экземпляре. Блокировка используется для предотвращения параллельного доступа из нескольких потоков.
SQLite на Windows Phone
В то время как iOS и Android работают с SQLite в составе операционной системы, Windows Phone не включает совместимый ядро СУБД. Для совместного использования кода на всех трех платформах требуется собственная версия SQLite для Windows Phone. Дополнительные сведения о настройке проекта Windows Phone для Sqlite см. в статье "Работа с локальной базой данных ".
Использование интерфейса для общего доступа к данным
Уровень данных зависит от BL.Contracts.IBusinessIdentity
того, чтобы он смог реализовать абстрактные методы доступа к данным, требующие первичного ключа. Затем любой класс бизнес-уровня, реализующий интерфейс, можно сохранить на уровне данных.
Интерфейс просто задает целочисленное свойство, которое будет выступать в качестве первичного ключа:
public interface IBusinessEntity {
int ID { get; set; }
}
Базовый класс реализует интерфейс и добавляет атрибуты SQLite-NET, чтобы пометить его как автоматически добавочный первичный ключ. Затем любой класс в бизнес-слое, реализующий этот базовый класс, можно сохранить в уровне данных:
public abstract class BusinessEntityBase : IBusinessEntity {
public BusinessEntityBase () {}
[PrimaryKey, AutoIncrement]
public int ID { get; set; }
}
Пример универсальных методов на уровне данных, использующих интерфейс, — это следующий GetItem<T>
метод:
public T GetItem<T> (int id) where T : BL.Contracts.IBusinessEntity, new ()
{
lock (locker) {
return Table<T>().FirstOrDefault(x => x.ID == id);
}
}
Блокировка для предотвращения параллельного доступа
Блокировка реализуется в классе, чтобы предотвратить одновременный TaskItemDatabase
доступ к базе данных. Это позволяет обеспечить сериализацию параллельного доступа из разных потоков (в противном случае компонент пользовательского интерфейса может попытаться считывать базу данных одновременно с обновлением фонового потока). Ниже показано, как реализована блокировка:
static object locker = new object ();
public IEnumerable<T> GetItems<T> () where T : BL.Contracts.IBusinessEntity, new ()
{
lock (locker) {
return (from i in Table<T> () select i).ToList ();
}
}
public T GetItem<T> (int id) where T : BL.Contracts.IBusinessEntity, new ()
{
lock (locker) {
return Table<T>().FirstOrDefault(x => x.ID == id);
}
}
Большую часть кода уровня данных можно использовать повторно в других проектах. Единственный код для конкретного приложения в слое — CreateTable<TaskItem>
вызов конструктора TaskItemDatabase
.
Уровень доступа к данным (DAL)
Класс TaskItemRepository
инкапсулирует механизм хранения данных с строго типизированным API, который позволяет TaskItem
создавать, удалять, извлекать и обновлять объекты.
Использование условной компиляции
Класс использует условную компиляцию для задания расположения файла. Это пример реализации дивергенции платформы. Свойство, возвращающее путь, компилируется в разные коды на каждой платформе. Ниже приведены директивы компилятора для конкретного кода и платформы:
public static string DatabaseFilePath {
get {
var sqliteFilename = "TaskDB.db3";
#if SILVERLIGHT
// Windows Phone expects a local path, not absolute
var path = sqliteFilename;
#else
#if __ANDROID__
// Just use whatever directory SpecialFolder.Personal returns
string libraryPath = Environment.GetFolderPath(Environment.SpecialFolder.Personal); ;
#else
// we need to put in /Library/ on iOS5.1+ to meet Apple's iCloud terms
// (they don't want non-user-generated data in Documents)
string documentsPath = Environment.GetFolderPath (Environment.SpecialFolder.Personal); // Documents folder
string libraryPath = Path.Combine (documentsPath, "..", "Library"); // Library folder
#endif
var path = Path.Combine (libraryPath, sqliteFilename);
#endif
return path;
}
}
В зависимости от платформы выходные данные будут иметь значение "<путь к приложению/библиотека/TaskDB.db3" для iOS, "<путь>> к приложению/Documents/TaskDB.db3" для Android или "TaskDB.db3" для Windows Phone.
Бизнес-уровень (BL)
Бизнес-слой реализует классы модели и фасад для управления ими.
В Tasky модель является TaskItem
классом и TaskItemManager
реализует шаблон фасада для предоставления API для управления TaskItems
.
Фасад
TaskItemManager
упаковывает DAL.TaskItemRepository
методы get, save and Delete, которые будут ссылаться на уровни приложения и пользовательского интерфейса.
Бизнес-правила и логика будут помещены здесь, если это необходимо, например любые правила проверки, которые должны быть удовлетворены перед сохранением объекта.
API для кода, зависящего от платформы
После написания общего кода пользовательский интерфейс должен быть создан для сбора и отображения данных, предоставляемых им. Класс TaskItemManager
реализует шаблон Фасада для предоставления простого API для доступа к коду приложения.
Код, написанный в каждом проекте для конкретной платформы, обычно тесно связан с собственным пакетом SDK этого устройства, и доступ к общему коду с помощью API, определенного этим TaskItemManager
api. К ним относятся методы и бизнес-классы, которые он предоставляет, например TaskItem
.
Образы не совместно используются на разных платформах, но добавляются независимо в каждый проект. Это важно, так как каждая платформа обрабатывает изображения по-разному, используя разные имена файлов, каталоги и разрешения.
В остальных разделах рассматриваются сведения о реализации для конкретной платформы пользовательского интерфейса tasky.
Приложение iOS
Существует только несколько классов, необходимых для реализации приложения задач iOS с помощью общего проекта PCL для хранения и извлечения данных. Ниже показан полный проект iOS Xamarin.iOS:
Классы показаны на этой схеме, сгруппированы на слои.
Ссылки
Приложение iOS ссылается на библиотеки пакета SDK для конкретной платформы, например. Xamarin.iOS и MonoTouch.Dialog-1.
Он также должен ссылаться на TaskyPortableLibrary
проект PCL.
Список ссылок показан здесь:
Уровень приложений и уровень пользовательского интерфейса реализованы в этом проекте с помощью этих ссылок.
Уровень приложений (AL)
Уровень приложений содержит классы, зависящие от платформы, необходимые для привязки объектов, предоставляемых PCL пользовательскому интерфейсу. Приложение для конкретного iOS имеет два класса, которые помогают отображать задачи:
- EditingSource — этот класс используется для привязки списков задач к пользовательскому интерфейсу. Так как
MonoTouch.Dialog
для списка задач используется этот вспомогательный элемент, чтобы включить функцию прокрутки для удаления в спискеUITableView
задач. Проводите пальцем к удалению обычно в iOS, но не Android или Windows Phone, поэтому конкретный проект iOS является единственным, который реализует его. - TaskDialog — этот класс используется для привязки одной задачи к пользовательскому интерфейсу. Он использует
MonoTouch.Dialog
API отражения для "оболочки"TaskItem
объекта с классом, который содержит правильные атрибуты, чтобы разрешить правильно отформатировать экран ввода.
Класс TaskDialog
использует MonoTouch.Dialog
атрибуты для создания экрана на основе свойств класса. Класс выглядит следующим образом:
public class TaskDialog {
public TaskDialog (TaskItem task)
{
Name = task.Name;
Notes = task.Notes;
Done = task.Done;
}
[Entry("task name")]
public string Name { get; set; }
[Entry("other task info")]
public string Notes { get; set; }
[Entry("Done")]
public bool Done { get; set; }
[Section ("")]
[OnTap ("SaveTask")] // method in HomeScreen
[Alignment (UITextAlignment.Center)]
public string Save;
[Section ("")]
[OnTap ("DeleteTask")] // method in HomeScreen
[Alignment (UITextAlignment.Center)]
public string Delete;
}
Обратите внимание, OnTap
что атрибуты требуют имени метода. Эти методы должны существовать в классе, где MonoTouch.Dialog.BindingContext
создается объект (в данном случае класс, HomeScreen
рассмотренный в следующем разделе).
Уровень пользовательского интерфейса (пользовательский интерфейс)
Уровень пользовательского интерфейса состоит из следующих классов:
- AppDelegate — содержит вызовы API внешнего вида для стиля шрифтов и цветов, используемых в приложении. Задача — это простое приложение, поэтому в ней
FinishedLaunching
нет других задач инициализации. - Экраны — подклассы , определяющие
UIViewController
каждый экран и его поведение. Экраны связывают пользовательский интерфейс с классами Уровня приложений и общим API (TaskItemManager
). В этом примере экраны создаются в коде, но они могли бы быть разработаны с помощью конструктора интерфейсов Xcode или конструктора раскадровки. - Изображения — визуальные элементы являются важной частью каждого приложения. Задача содержит изображения экрана-заставки и значка, которые для iOS должны быть предоставлены в регулярном разрешении и разрешении Сетчатки.
Home Screen
Начальный MonoTouch.Dialog
экран — это экран, в котором отображается список задач из базы данных SQLite. Он наследует от DialogViewController
и реализует код, чтобы задать Root
коллекцию TaskItem
объектов для отображения.
Ниже перечислены два основных метода, связанных с отображением и взаимодействием со списком задач:
- ЗаполнениеTable — использует метод бизнес-слоя
TaskManager.GetTasks
для получения коллекцииTaskItem
объектов для отображения. - Выбрано — при касании строки отображает задачу на новом экране.
Экран сведений о задаче
Сведения о задаче — это экран ввода, позволяющий изменять или удалять задачи.
Задача использует MonoTouch.Dialog
API отражения для отображения экрана, поэтому реализация отсутствует UIViewController
. Вместо этого HomeScreen
класс создает экземпляр и отображает DialogViewController
класс с помощью TaskDialog
класса из уровня приложения.
На этом снимке экрана показан пустой экран, демонстрирующий Entry
настройку атрибута в полях "Имя " и "Заметки ":
Функциональные возможности экрана сведений о задаче (например, сохранение или удаление задачи) должны быть реализованы в HomeScreen
классе, так как это место MonoTouch.Dialog.BindingContext
создания. HomeScreen
Следующие методы поддерживают экран сведений о задаче:
- ShowTaskDetails — создает экран
MonoTouch.Dialog.BindingContext
для отрисовки. Он создает экран ввода с помощью отражения для получения имен свойств и типов изTaskDialog
класса. Дополнительные сведения, такие как текст водяного знака для входных полей, реализованы с атрибутами свойств. - SaveTask — этот метод ссылается в
TaskDialog
классе с помощью атрибутаOnTap
. Он вызывается при нажатии клавиши Save и используетсяMonoTouch.Dialog.BindingContext
для извлечения введенных пользователем данных перед сохранением изменений с помощьюTaskItemManager
. - DeleteTask — этот метод ссылается в
TaskDialog
классе с помощью атрибутаOnTap
. Он используетсяTaskItemManager
для удаления данных с помощью первичного ключа (свойства ID).
Приложение Android
Полный проект Xamarin.Android показан ниже:
Схема классов с классами, сгруппированных по слоям:
Ссылки
Проект приложения Android должен ссылаться на сборку Xamarin.Android для конкретной платформы, чтобы получить доступ к классам из пакета SDK для Android.
Он также должен ссылаться на проект PCL (например. TaskyPortableLibrary) для доступа к общему коду данных и бизнес-слоя.
Уровень приложений (AL)
Аналогично версии iOS, которую мы рассмотрели ранее, уровень приложений в версии Android содержит классы, необходимые для привязки объектов, предоставляемых Core к пользовательскому интерфейсу.
TaskListAdapter — чтобы отобразить список<объектов T> , необходимо реализовать адаптер для отображения пользовательских объектов в объекте ListView
. Адаптер управляет макетом, используемым для каждого элемента в списке. В этом случае код использует встроенный макет SimpleListItemChecked
Android.
Пользовательский интерфейс
Уровень пользовательского интерфейса приложения Android — это сочетание кода и разметки XML.
- Ресурсы и макет — макеты экрана и структура ячеек строк, реализованная в виде файлов AXML. AXML можно написать вручную или выложить визуально с помощью конструктора пользовательского интерфейса Xamarin для Android.
- Resources/Drawable — изображения (значки) и настраиваемая кнопка.
- Экраны — подклассы действий, определяющие каждый экран и его поведение. Связывает пользовательский интерфейс с классами Уровня приложений и общим API (
TaskItemManager
).
Home Screen
Начальный экран состоит из подкласса HomeScreen
действия и HomeScreen.axml
файла, который определяет макет (положение кнопки и списка задач). Экран выглядит следующим образом:
Код начального экрана определяет обработчики для нажатия кнопки и нажатия элементов в списке, а также заполнение списка в методе (таким образом, что оно отражает изменения, внесенные на OnResume
экране сведений о задаче). Данные загружаются с помощью бизнес-уровня TaskItemManager
и TaskListAdapter
уровня приложений.
Экран сведений о задаче
Экран сведений о задаче также состоит из Activity
подкласса и файла макета AXML. Макет определяет расположение входных элементов управления, а класс C# определяет поведение для загрузки и сохранения TaskItem
объектов.
Все ссылки на библиотеку PCL проходят через TaskItemManager
класс.
Приложение Windows Phone
Полный проект Windows Phone:
На следующей схеме представлены классы, сгруппированные по слоям:
Ссылки
Проект для конкретной платформы должен ссылаться на необходимые библиотеки для конкретной платформы (например Microsoft.Phone
, и System.Windows
) для создания допустимого приложения Windows Phone.
Он также должен ссылаться на проект PCL (например, TaskyPortableLibrary
для использования TaskItem
класса и базы данных).
Уровень приложений (AL)
Опять же, как и в версиях iOS и Android, слой приложений состоит из не визуальных элементов, которые помогают привязать данные к пользовательскому интерфейсу.
ViewModels
ViewModels упаковывает данные из PCL ( TaskItemManager
) и предоставляет его таким образом, чтобы их можно было использовать привязкой данных Silverlight/XAML. Это пример поведения для конкретной платформы (как описано в документе кроссплатформенных приложений).
Пользовательский интерфейс
XAML имеет уникальную возможность привязки данных, которую можно объявить в разметке и уменьшить объем кода, необходимого для отображения объектов:
- Страницы — XAML-файлы и их кодовая функция определяют пользовательский интерфейс и ссылаться на ViewModels и проект PCL для отображения и сбора данных.
- Изображения — экран-заставка, фон и изображения значков являются ключевой частью пользовательского интерфейса.
MainPage
Класс MainPage использует TaskListViewModel
данные для отображения данных с помощью функций привязки данных XAML. Для страницы DataContext
задана модель представления, которая заполняется асинхронно. Синтаксис {Binding}
в XAML определяет, как отображаются данные.
TaskDetailsPage
Каждая задача отображается путем привязки TaskViewModel
xaml к XAML, определенному в taskDetailsPage.xaml. Данные задачи извлекаются с помощью TaskItemManager
бизнес-уровня.
Результаты
Полученные приложения выглядят следующим образом на каждой платформе:
iOS
Приложение использует стандартный пользовательский интерфейс iOS, например кнопку "добавить", расположенную на панели навигации и используя встроенный значок плюс (+). Он также использует поведение кнопки "назад" по умолчанию и поддерживает "проводите пальцем к удалению UINavigationController
" в таблице.
Android
Приложение Android использует встроенные элементы управления, включая встроенный макет для строк, для которых требуется отображаться "галок". В дополнение к кнопке "Назад" на экране поддерживается поведение оборудования или системы.
Windows Phone
Приложение Windows Phone использует стандартный макет, заполняя панель приложений в нижней части экрана вместо панели навигации в верхней части экрана.
Итоги
В этом документе представлено подробное описание того, как принципы многоуровневого проектирования приложений были применены к простому приложению для упрощения повторного использования кода на трех мобильных платформах: iOS, Android и Windows Phone.
Он описал процесс, используемый для проектирования слоев приложений, и обсудил, какие функции кода и функциональности реализованы на каждом уровне.
Код можно скачать с github.