codefest 2013. Никонов Г. — Как мы разрабатываем приложения...
DESCRIPTION
TRANSCRIPT
Приложения для Windows Phone
Как мы это делаем
Григорий Никонов,Actis® Wunderman.
Обо мне
На данный момент руковожу разработкой мобильных приложений агентства Actis Wunderman, в котором тружусь с момента появления на свет агентства Actis, позже присоединившегося к сети Wunderman.
Ранее занимался разработкой, созданием и внедрением интернет-проектов.
Наши работы
Мы занимаемся разработкой приложений для ведущих платформ около трех лет. Вот, что мы сделали для Windows Phone:
•КиноПоиск
•Concert.ru
•ЛитРес
Несколько неназываемых проектов скоро должны быть опубликованы, тогда мы их назовём.
Наши работы: КиноПоиск
Наши работы: Concert.ru
Наши работы: ЛитРес
Мы – ленивые
И поэтому создали несколько инструментов, которые позволяют нам экономить время и усилия:
•Фреймворк работы с сущностями и коллекциями
•Фреймворк моделей представлений
•Фреймворк представлений для Windows Phone и других платформ
•T4-генераторы кода
Кроме того, сервер непрерывной интеграции и тесты позволяют нам не заботиться о проблемах внесения изменений в код и координировать совместную работу
Данные
Сценарии использования источников данных в мобильных приложениях, в порядке уменьшения частоты использования:
•Получить коллекцию заголовков данных
•Получить конкретную сущность
•Выполнить единичное действие с источником данных
•Отправить изменённую сущность
«Мобильность» платформы накладывает некоторые ограничения на возможность и целесообразность передачи больших объёмов данных
Данные
Данные можно разделить на две категории – сущности и коллекции сущностей
И сущности и коллекции сущностей бывают идентифицируемые и неидентифицируемые
Сущности
События изменения свойств:
ObservableObject
Обновление и клонирование:
XObject, XObject<T>
Ключи для идентификации объектов:
XKey
Коллекции
Интеллектуальное обновление:
XCollection<T>
Применение фильтров:
XFilteredCollection<T>
Объединение и разделение:
XCombinedCollection<T>, XSubRangeCollection<T>
Кэширование
Запросы как идентификация коллекций:
XQuery<T>
Применение фильтров:
XFilteredCollection<T>
Собственно кэш:
XCache<T>
Пример: Свойства
class Person : XObject{
string _name;XCollection<Person> _children;
public string Name{
get { return _name; }set { SetProperty( ref _name, value, “Name” ); }
}
public XCollection<Person> Children{
get { return _children; }set { SetProperty( ref _name, value,
“Children” ); }}
}
Пример: ProcessCopy
class Person : XObject{
protected override void ProcessCopy(XObject source, bool cloning, bool deepCloning )
{base.ProcessCopy( source, cloning,
deepCloning );
Person other = (Person) source;
_name = other._name;
ProcessCopyProperty(ref _children, other._children,cloning, deepCloning );
}}
Пример: Клонирование
Person person = new Person{
Name = “John”,Children = new XCollection<Person>
{new Person { Name = “Bob” }
}};
Person softClone = (Person) person.Clone( false );Person deepClone = (Person) person.Clone( true );
person.Children[0].Name = “Mary”;
softClone.Children[0].Name // “Mary”deepClone.Children[0].Name // “Bob”
Пример: Обновление
Person person1 = new Person { Name = “John” }Person person2 = new Person { Name = “Mary” }
person1.Update( person2 );
person1.Name // “Mary”
XCollection<Person> l1 = new XCollection<Person>() { … };XCollection<Person> l2 = new XCollection<Person>() { … };
var result = l1.Update( l2 );
Пример: Коллекции
XCollection<Person> l1 = new XCollection<Person>() { … };XCollection<Person> l2 = new XCollection<Person>() { … };
var softClone = l1.Clone( false );var deepClone = l1.Clone( true );
var result = l1.Update( l2 );
Пример: Хитрые коллекции
XCollection<Person> studentsOfClassA = …;XCollection<Person> studentsOfClassB = …;
var firstThreeStudentsOfClassA =new XSubRangeCollection( studentsOfClassA, 0, 3 );
var firstTwoStudentsOfClassB =new XSubRangeCollection( studentsOfClassB, 0, 2 );
var topStudents =new XCombinedCollection(
firstThreeStudentsOfClassA,firstTwoStudentsOfClassB );
Генератор сущностей
Поддержка XML/JSON сериализации
Демонстрация
Сгенерированные классы, содержащие ключевые элементы, необходимые для эффективной работы с данными:
•Правильное обновление данных
•Правильное клонирование данных
•Выборка с помощью ключей в коллекциях и кэше
Модели представлений
Предназначены для получения и преобразования данных, необходимых представлениям, а также для выполнения других действий над этими данными.
Наиболее частые операции в мобильных приложениях – загрузка данных (списки или отдельные сущности) для представления их пользователю.
Работа с источниками данных должна быть асинхронной – нельзя блокировать поток пользовательского интерфейса.
Необходимо учитывать возможность досрочного прекращения выполняемых операций – по разным причинам.
Сессии
Всё «управляемое» общение модели (модели представления) с внешним миром происходит в рамках сессии
Сессия содержит параметры, необходимые для доступа к данным
Сессия содержит жетон, используемый для прекращения асинхронных задач (CancellationToken)
Обработкой сессии занимается модель
Работа с сессией
Создание
var session = CreateSession().AddParameter( “userName”, “John” ).AddParameter( “age”, 48 );
Доступ к параметрам
var userName = session.Parameters.Get<string>( “userName” );var age = session.Parameters.Get<int>( “age” );var sex = session.Parameters.Get<string>( “sex”, “male” );
Обработка
await viewModel.Load( session );
Откуда она знает, что делать?
Виртуальные методы
ShouldLoadSessionLoadSession
Части
RegisterPartstring part,Func<Session, Task> processor,Func<Session, bool> checker,bool loadIfNoPartsSpecified
Типичная модель
Конструктор
Получает сервисы через DI
Регистрирует части
Создает команды
Методы обработки частей
Проверяют необходимость обработки сессии
Осуществляют доступ/обновление данных
Модель сущности
Использует расширенную сессию, которая включает в себя ключ, идентифицирующий объект: EntitySession
Регистрирует методы для части, связанной с обработкой сущности: ShouldLoadEntity и LoadEntity.
Объявляет свойство Entity
Типичная модель сущности
Конструктор
Получает сервисы
Создает команды
Метод LoadEntity
Загружает данные из внешнего источника или из кэша
Присваивает значение свойству Entity
INavigationService
Один из самых важных сервисов, доступных моделям
Определяет независимый от платформы способ навигации между видами
ns.Navigate( “PersonView”, Parameters.Create( “Id”, 1 ) );
Позволяет создавать и использовать команды навигации:
ViewDetails = new NavigationCommand(ns,“DetailedView” );
PersonSelected = new NavigationCommand<Person>(ns,“PersonView”,person => person.KeyParameter() );
Другие сервисы
IDataExchangeService
Информирование об обмене данными
IViewModelExceptionHandlingServiceIExceptionHandlingService
Унифицированная обработка исключений
Представления
PhoneApplication – наследник Application
Регистрация сервисов
Обработка ошибок уровня приложения
Page
Управление жизненным циклом
OnPageCreated, OnPageDestroyed,OnPageAsleep, OnPageAwaken, OnPageResurrected
Доступ к параметрам
ViewParameters
Контекст для связывания данных
CreateDataContext
Атрибутирование для навигации
[View( “UserView” )][ViewParameter( “userName”,
typeof( string ) )]
ViewModelPage<TViewModel>
Страница с созданной моделью
Начальная загрузка данных
CreateDataSession, CreateDataSessionAsyncOnDataLoadComplete, OnDataLoadFailed
Обработка нештатных ситуаций (FAS)
Управление областью жизни модели
Связывание данных в XAML
<TextBlock Text=“{Binding ViewModel.Title}”/>
EntityPage
Автоматически выбирает ключ из параметров и создает сессию для загрузки сущности.
Но это еще не всё!
Генератор классов-заглушек и частичных классов-представлений:
<View Name=“MainView”/><View Name=“FeedChannelView” Entity=“FeedChannel”/>
Во многих случаях файл View.xaml.cs не нужен, всё делается автомагически
Генератор константных имен представлений и параметров, а также методов для создания параметров навигации
Но и это еще не всё!
Декораторы страниц используются для выполнения однообразных действий со страницами, например для показа сообщений об ошибках, исчезновении сетевого подключения и т.п.
Встроенный механизм обработки страниц, для которых нужна аутентификация – параметр RequiresAuthentication атрибута [View()]