SlideShare une entreprise Scribd logo
1  sur  137
Как приручить реактивное
программирование в XAML приложениях
Денис Цветцих
Ice Rock Dev
icerockdev.com
DotNext Spb
3 июня 2016
dotnext.ru/spb/
2
Кто я?
• 7+ лет .NET
• Разработка корпоративных приложений
• Люблю хипстерские библиотеки 
3
Почему я здесь?
• 6 лет назад приручил Rx
• 3 года назад приручил ReactiveUI
4
ReactiveUI – ни одного поста на хабре
5
Сэмплы не показательны
Rx – 101 пример задач, где можно обойтись без Rx
ReactiveUI – единственный жизненный сэмпл – поиск (на
главной странице)
6
О чем мы поговорим?
• Что такое реактивное программирование
• Примеры задач из продакшен проектов, которые проще
решаются с помощью Rx и ReactiveUI
• Антипаттерны: как ReactiveUI лучше не использовать
ЧТО ТАКОЕ РЕАКТИВНОЕ ПРОГРАММИРОВАНИЕ
8
Что такое реактивное
программирование?
Реактивное программирование – это парадигма
программирования, ориентированная на потоки данных и
распространение изменений.
(Википедия)
9
Парадигма программирования
Парадигма программирования – это система идей и
понятий, определяющих стиль написания компьютерных
программ.
10
Императивная программа
var A = 10;
var B = A + 1;
// Чему равно В?
11
A = A + 1;
// А теперь чему равно В?
11
11
Реактивная программа
var A = 10;
var B <- A + 1;
<- оператор «судьбы»
// Чему равно В?
11
A = A + 1;
// А теперь чему равно В?
12
12
Реактивное программирование
Реактивное программирование – это парадигма
программирования, ориентированная на потоки данных и
распространение изменений.
Поток данных – последовательность значений переменной
или свойства класса.
Распространение изменений – уведомления
«заинтересованных» об изменениях.
13
Pull
Pull – класс A взаимодействует с классом B и вытягивает из
него необходимые данные
Class A Class B
GetData()
Interacts with
14
Push
Push – класс B самостоятельно выталкивает данные классу
A, как только они становятся доступны.
Class A
ProcessData()
Class BReacts on
РЕАКТИВНОЕ ПРОГРАММИРОВАНИЕ НА C#
16
Реактивное программирование на .NET
• Reactive Extensions (Rx)
• Эрик Мейер из MS Research
• ReactiveUI
• Разработана в MS Research
• библиотека на базе Rx для создания элегантных UI для всех
XAML платформ
17
Внутри .NET
public interface IObservable<T>
{
IDisposable Subscribe(IObserver<T> observer);
}
public interface IObserver<T>
{
void OnNext(T value);
void OnCompleted();
void OnError(Exception error);
}
18
Внутри .NET
public interface IObservable<T>
{
IDisposable Subscribe(IObserver<T> observer);
}
public interface IObserver<T>
{
void OnNext(T value);
void OnCompleted();
void OnError(Exception error);
}
19
Внутри .NET
public interface IObservable<T>
{
IDisposable Subscribe(IObserver<T> observer);
}
public interface IObserver<T>
{
void OnNext(T value);
void OnCompleted();
void OnError(Exception error);
}
20
Внутри .NET
public interface IObservable<T>
{
IDisposable Subscribe(IObserver<T> observer);
}
public interface IObserver<T>
{
void OnNext(T value);
void OnCompleted();
void OnError(Exception error);
}
21
Внутри .NET
public interface IObservable<T>
{
IDisposable Subscribe(IObserver<T> observer);
}
public interface IObserver<T>
{
void OnNext(T value);
void OnCompleted();
void OnError(Exception error);
}
22
Оператор судьбы через Rx
var A = new Subject<int>();
// B <- A + 1
var B = A.Select(a => a + 1);
A.OnNext(10);
23
Оператор судьбы через Rx
var A = new Subject<int>();
// B <- A + 1
var B = A.Select(a => a + 1);
A.OnNext(10);
24
Оператор судьбы через Rx
var A = new Subject<int>();
// B <- A + 1
var B = A.Select(a => a + 1);
A.OnNext(10);
25
Оператор судьбы через Rx
var A = new Subject<int>();
// B <- A + 1
var B = A.Select(a => a + 1);
A.OnNext(10);
26
Observable.Select – проекция
27
Observable.Merge – слияние
КАКИЕ ЗАДАЧИ МОЖНО РЕШАТЬ
29
Пример
Приложение для поиска по материалам конференции DotNext
30
Для читабельности примеров
public class Filter : ReactiveObject
{
private DateTime _fromDate;
private DateTime _toDate;
public DateTime FromDate
{
get { return _fromDate; }
set { this.RaiseAndSetIfChanged(ref _fromDate, value); }
}
public DateTime ToDate
{
get { return _toDate; }
set { this.RaiseAndSetIfChanged(ref _toDate, value); }
}
}
31
Для читабельности примеров
public class Filter : ReactiveObject
{
private DateTime _fromDate;
private DateTime _toDate;
public DateTime FromDate
{
get { return _fromDate; }
set { this.RaiseAndSetIfChanged(ref _fromDate, value); }
}
public DateTime ToDate
{
get { return _toDate; }
set { this.RaiseAndSetIfChanged(ref _toDate, value); }
}
}
32
Для читабельности примеров
public class Filter : ReactiveObject
{
private DateTime _fromDate;
private DateTime _toDate;
public DateTime FromDate
{
get { return _fromDate; }
set { this.RaiseAndSetIfChanged(ref _fromDate, value); }
}
public DateTime ToDate
{
get { return _toDate; }
set { this.RaiseAndSetIfChanged(ref _toDate, value); }
}
}
33
Для читабельности примеров
public class Filter
{
public DateTime FromDate { get; set; }
public DateTime ToDate { get; set; }
}
34
Задача 1: подписка на события об изменении
свойств фильтра
35
Обычная подписка на событие PropertyChanged
var filter = new Filter();
// Подписка
filter.PropertyChanged += OnPropertyChanged;
// Реакция на событие
private void OnPropertyChanged
(object sender, PropertyChangedEventArgs e)
{
}
// Отписка
filter.PropertyChanged -= OnPropertyChanged;
36
Обычная подписка на событие PropertyChanged
var filter = new Filter();
// Подписка
filter.PropertyChanged += OnPropertyChanged;
// Реакция на событие
private void OnPropertyChanged
(object sender, PropertyChangedEventArgs e)
{
}
// Отписка
filter.PropertyChanged -= OnPropertyChanged;
37
var filter = new Filter();
// Подписка
filter.PropertyChanged += OnPropertyChanged;
// Реакция на событие
private void OnPropertyChanged
(object sender, PropertyChangedEventArgs e)
{
}
// Отписка
filter.PropertyChanged -= OnPropertyChanged;
Обычная подписка на событие PropertyChanged
38
Обычная подписка на событие PropertyChanged
var filter = new Filter();
// Подписка
filter.PropertyChanged += OnPropertyChanged;
// Реакция на событие
private void OnPropertyChanged
(object sender, PropertyChangedEventArgs e)
{
}
// Отписка
filter.PropertyChanged -= OnPropertyChanged;
39
filter = new Filter();
// Подписка
IDisposable subscription =
Observable.FromEventPattern<PropertyChangedEventArgs>
(filter, "PropertyChanged")
.Subscribe(OnPropertyChanged);
// Реакция на событие
private void OnPropertyChanged
(EventPattern<PropertyChangedEventArgs> eventPattern)
{
//eventPattern.Sender и eventPattern.EventArgs
}
// Отписка
subscription.Dispose();
Rx подписка на событие PropertyChanged
40
filter = new Filter();
// Подписка
IDisposable subscription =
Observable.FromEventPattern<PropertyChangedEventArgs>
(filter, "PropertyChanged")
.Subscribe(OnPropertyChanged);
// Реакция на событие
private void OnPropertyChanged
(EventPattern<PropertyChangedEventArgs> eventPattern)
{
//eventPattern.Sender и eventPattern.EventArgs
}
// Отписка
subscription.Dispose();
Rx подписка на событие PropertyChanged
41
filter = new Filter();
// Подписка
IDisposable subscription =
Observable.FromEventPattern<PropertyChangedEventArgs>
(filter, "PropertyChanged")
.Subscribe(OnPropertyChanged);
// Реакция на событие
private void OnPropertyChanged
(EventPattern<PropertyChangedEventArgs> eventPattern)
{
//eventPattern.Sender и eventPattern.EventArgs
}
// Отписка
subscription.Dispose();
Rx подписка на событие PropertyChanged
42
filter = new Filter();
// Подписка
IDisposable subscription =
Observable.FromEventPattern<PropertyChangedEventArgs>
(filter, "PropertyChanged")
.Subscribe(OnPropertyChanged);
// Реакция на событие
private void OnPropertyChanged
(EventPattern<PropertyChangedEventArgs> eventPattern)
{
//eventPattern.Sender и eventPattern.EventArgs
}
// Отписка
subscription.Dispose();
Rx подписка на событие PropertyChanged
43
Rx подписка на событие PropertyChanged
filter = new Filter();
// Подписка
IDisposable subscription =
Observable.FromEventPattern<PropertyChangedEventArgs>
(filter, "PropertyChanged")
.Subscribe(OnPropertyChanged);
// Реакция на событие
private void OnPropertyChanged
(EventPattern<PropertyChangedEventArgs> eventPattern)
{
//eventPattern.Sender и eventPattern.EventArgs
}
// Отписка
subscription.Dispose();
44
Rx подписка на событие PropertyChanged
filter = new Filter();
// Подписка
IDisposable subscription =
Observable.FromEventPattern<PropertyChangedEventArgs>
(filter, "PropertyChanged")
.Subscribe(OnPropertyChanged);
// Реакция на событие
private void OnPropertyChanged
(EventPattern<PropertyChangedEventArgs> eventPattern)
{
//eventPattern.Sender и eventPattern.EventArgs
}
// Отписка
subscription.Dispose();
45
Достоинства Rx подписки
• Подписка инкапсулируется в IDisposable объекте, просто
сделать отписку
• Подписка – это поток данных, над которым можно
выполнять операции
46
Задача 2: подписка на изменение свойства
FromDate
47
Обычная подписка на изменение свойства FromDate
Filter filter = new Filter();
filter.PropertyChanged += OnPropertyChanged;
private void OnPropertyChanged
(object sender, PropertyChangedEventArgs eventArgs)
{
if (eventArgs.PropertyName == “FromDate")
{
// Действия
}
}
48
Обычная подписка на изменение свойства FromDate
Filter filter = new Filter();
filter.PropertyChanged += OnPropertyChanged;
private void OnPropertyChanged
(object sender, PropertyChangedEventArgs eventArgs)
{
if (eventArgs.PropertyName == “FromDate")
{
// Действия
}
}
49
Обычная подписка на изменение свойства FromDate
Filter filter = new Filter();
filter.PropertyChanged += OnPropertyChanged;
private void OnPropertyChanged
(object sender, PropertyChangedEventArgs eventArgs)
{
if (eventArgs.PropertyName == “FromDate")
{
// Действия
}
}
50
ReactiveUI подписка на изменение свойства FromDate
var filter = new Filter();
var subscription = filter
.ObservableForProperty(f => f.FromDate)
.Subscribe(FromDateChanged);
private void FromDateChanged
(IObservedChange<Filter, DateTime> change)
{
// Действия
}
51
ReactiveUI подписка на изменение свойства FromDate
var filter = new Filter();
var subscription = filter
.ObservableForProperty(f => f.FromDate)
.Subscribe(FromDateChanged);
private void FromDateChanged
(IObservedChange<Filter, DateTime> change)
{
// Действия
}
52
ReactiveUI подписка на изменение свойства FromDate
var filter = new Filter();
var subscription = filter
.ObservableForProperty(f => f.FromDate)
.Subscribe(FromDateChanged);
private void FromDateChanged
(IObservedChange<Filter, DateTime> change)
{
// Действия
}
53
ReactiveUI подписка на изменение свойства FromDate
var filter = new Filter();
var subscription = filter
.ObservableForProperty(f => f.FromDate)
.Subscribe(FromDateChanged);
private void FromDateChanged
(IObservedChange<Filter, DateTime> change)
{
// Действия
}
54
Достоинства ObservableForProperty
• Не нужно проверять, что обрабатываем событие об
изменении нужного свойства
55
Задача 3: действия при изменении значений
свойств
56
Добавим выбор страны и города
Добавим в фильтр:
• Список стран и городов
• При выборе страны актуализируем список городов
57
Валидация выбранного периода
Условие: FromDate не превосходит ToDate
Если FromDate > ToDate, тогда ToDate приравниваем к
FromDate
Если ToDate < FromDate, тогда FromDate приравниваем к
ToDate
58
Обычное решение
private DateTime _fromDate;
public DateTime FromDate
{
get { return _fromDate; }
set
{
_fromDate = value;
OnPropertyChanged();
if (_fromDate > ToDate) ToDate = _fromDate;
}
}
59
Обычное решение
private DateTime _fromDate;
public DateTime FromDate
{
get { return _fromDate; }
set
{
_fromDate = value;
OnPropertyChanged();
if (_fromDate > ToDate) ToDate = _fromDate;
}
}
60
ObservableForProperty
private IDisposable _subscription;
public Filter()
{
_subscription =
this.ObservableForProperty(vm => vm.FromDate)
.Where(fromDate => fromDate.Value > ToDate)
.Subscribe(fromDate => ToDate = fromDate.Value);
}
public DateTime FromDate { get; set; } // PropertyChanged
61
ObservableForProperty
private IDisposable _subscription;
public Filter()
{
_subscription =
this.ObservableForProperty(vm => vm.FromDate)
.Where(fromDate => fromDate.Value > ToDate)
.Subscribe(fromDate => ToDate = fromDate.Value);
}
public DateTime FromDate { get; set; } // PropertyChanged
62
ObservableForProperty
private IDisposable _subscription;
public Filter()
{
_subscription =
this.ObservableForProperty(vm => vm.FromDate)
.Where(fromDate => fromDate.Value > ToDate)
.Subscribe(fromDate => ToDate = fromDate.Value);
}
public DateTime FromDate { get; set; } // PropertyChanged
63
ObservableForProperty
private IDisposable _subscription;
public Filter()
{
_subscription =
this.ObservableForProperty(vm => vm.FromDate)
.Where(fromDate => fromDate.Value > ToDate)
.Subscribe(fromDate => ToDate = fromDate.Value);
}
public DateTime FromDate { get; set; } // PropertyChanged
64
ObservableForProperty
private IDisposable _subscription;
public Filter()
{
_subscription =
this.ObservableForProperty(vm => vm.FromDate)
.Where(fromDate => fromDate.Value > ToDate)
.Subscribe(fromDate => ToDate = fromDate.Value);
}
public DateTime FromDate { get; set; } // PropertyChanged
if (_fromDate > ToDate) ToDate = _fromDate;
65
Зависимости между свойствами в обычном решении
public class Filter
{
public DateTime FromDate
{
get { return _fromDate; }
set
{
_fromDate = value;
if (_fromDate > ToDate) ToDate = FromDate;
}
}
public DateTime ToDate
{
get { return _toDate; }
set
{
_toDate = value;
if (_toDate < FromDate) FromDate = _toDate;
}
}
public List<string> Cities
{
get { return _cities; }
set
{
_cities = value;
SelectedCity = null;
}
}
public string SelectedCountry
{
get { return _selectedCountry; }
set
{
_selectedCountry = value;
Cities = GetCitesForCountry(_selectedCountry);
}
}
}
66
Зависимости между свойствами в ReactiveUI решении
public class Filter
{
public Filter()
{
_fromDateSubscription =
this.ObservableForProperty(filter => filter.FromDate)
.Where(fromDate => fromDate.Value > ToDate)
.Subscribe(fromDate => ToDate = fromDate.Value);
_toDateSubscription =
this.ObservableForProperty(filter => filter.ToDate)
.Where(toDate => toDate.Value < FromDate)
.Subscribe(toDate => FromDate = toDate.Value);
_selectedCountrySubscription =
this.ObservableForProperty(filter => filter.SelectedCountry)
.Subscribe(country => Cities = GetCitesForCountry(country.Value));
_citiesSubscription = this.ObservableForProperty(filter => Cities)
.Subscribe(_ => SelectedCity = null);
}
public DateTime FromDate { get; set; }
public DateTime ToDate { get; set; }
public List<string> Countries { get; set; }
public List<string> Cities { get; set; }
public string SelectedCountry { get; set; }
public string SelectedCity { get; set; }
}
67
Достоинства ObservableForProperty
• Удаление логики из сеттеров
• Делает код на порядок более читаемым, собирая код
зависимости между свойствами в конструкторе
68
Задача 4: мониторинг одновременно нескольких
свойств
Поля фильтра:
• От
• До
• Страна
• Город
При изменении любого – обновляем данные
69
ViewModel с фильтром
public class ViewModel
{
public Filter Filter { get; set; }
public ViewModel()
{
Filter = new Filter();
// Подписка на изменение свойств фильтра
}
}
70
ViewModel с фильтром
public class ViewModel
{
public Filter Filter { get; set; }
public ViewModel()
{
Filter = new Filter();
// Подписка на изменение свойств фильтра
}
}
71
ViewModel с фильтром
public class ViewModel
{
public Filter Filter { get; set; }
public ViewModel()
{
Filter = new Filter();
// Подписка на изменение свойств фильтра
}
}
72
Обычное решение
public ViewModel()
{
Filter = new Filter();
// Подписка на изменение свойств фильтра
Filter.PropertyChanged += OnFilterPropertyChanged;
}
private void OnFilterPropertyChanged
(object sender, PropertyChangedEventArgs eventArgs)
{
if (eventArgs.PropertyName == "FromDate" ||
eventArgs.PropertyName == "ToDate" ||
eventArgs.PropertyName == "SelectedCountry" ||
eventArgs.PropertyName == "SelectedCity")
{
Refresh();
}
}
73
Обычное решение
public ViewModel()
{
Filter = new Filter();
// Подписка на изменение свойств фильтра
Filter.PropertyChanged += OnFilterPropertyChanged;
}
private void OnFilterPropertyChanged
(object sender, PropertyChangedEventArgs eventArgs)
{
if (eventArgs.PropertyName == "FromDate" ||
eventArgs.PropertyName == "ToDate" ||
eventArgs.PropertyName == "SelectedCountry" ||
eventArgs.PropertyName == "SelectedCity")
{
Refresh();
}
}
74
ReactiveUI решение
public ViewModel()
{
Filter = new Filter();
// Подписка на изменение свойств фильтра
_subscription =
Filter.WhenAnyValue(
f => f.FromDate,
f => f.ToDate,
f => f.SelectedCountry,
f => f.SelectedCity)
.Subscribe(_ => Refresh());
}
75
ReactiveUI решение
public ViewModel()
{
Filter = new Filter();
// Подписка на изменение свойств фильтра
_subscription =
Filter.WhenAnyValue(
f => f.FromDate,
f => f.ToDate,
f => f.SelectedCountry,
f => f.SelectedCity)
.Subscribe(_ => Refresh());
}
76
ReactiveUI решение
public ViewModel()
{
Filter = new Filter();
// Подписка на изменение свойств фильтра
_subscription =
Filter.WhenAnyValue(
f => f.FromDate,
f => f.ToDate,
f => f.SelectedCountry,
f => f.SelectedCity)
.Subscribe(_ => Refresh());
}
77
ReactiveUI решение
public ViewModel()
{
Filter = new Filter();
// Подписка на изменение свойств фильтра
_subscription =
Filter.WhenAnyValue(
f => f.FromDate,
f => f.ToDate,
f => f.SelectedCountry,
f => f.SelectedCity)
.Subscribe(_ => Refresh());
}
78
Достоинства WhenAnyValue
• Подписываемся только на нужные нам свойства
• Можно подписываться на изменения вложенных свойств
• Obj.Deep1.Deep2.Deep3…
79
Задача 5: проверка CanExecute
команды
• Добавляем кнопку «Обновить» на фильтр
• Страна и город – обязательные поля
80
RelayCommand из MvvmLight
public RelayCommand RefreshCommand { get; private set; }
public Filter()
{
RefreshCommand = new RelayCommand(
OnRefresh,
() => SelectedCity != null && SelectedCountry != null);
}
81
RelayCommand из MvvmLight
public RelayCommand RefreshCommand { get; private set; }
public Filter()
{
RefreshCommand = new RelayCommand(
OnRefresh,
() => SelectedCity != null && SelectedCountry != null);
}
82
RelayCommand из MvvmLight
public RelayCommand RefreshCommand { get; private set; }
public Filter()
{
RefreshCommand = new RelayCommand(
OnRefresh,
() => SelectedCity != null && SelectedCountry != null);
}
83
RelayCommand из MvvmLight
public RelayCommand RefreshCommand { get; private set; }
public Filter()
{
RefreshCommand = new RelayCommand(
OnRefresh,
() => SelectedCity != null && SelectedCountry != null);
}
84
Подергивания 
public string SelectedCountry {
get { return _selectedCountry; }
set {
_selectedCountry = value; OnPropertyChanged();
RefreshCommand.RaiseCanExecuteChanged();
}
}
public string SelectedCity {
get { return _selectedCity; }
set {
_selectedCity = value; OnPropertyChanged();
RefreshCommand.RaiseCanExecuteChanged();
}
}
85
ReactiveCommand
public Filter()
{
var canExecute = this.WhenAny(
f => f.SelectedCity,
f => f.SelectedCountry,
(city, country) =>
city.Value != null && country.Value != null);
var command = ReactiveCommand.Create(canExecute);
command.Subscribe(_ => OnRefresh());
RefreshCommand = command;
}
public string SelectedCountry { get; set; }
86
ReactiveCommand
public Filter()
{
var canExecute = this.WhenAny(
f => f.SelectedCity,
f => f.SelectedCountry,
(city, country) =>
city.Value != null && country.Value != null);
var command = ReactiveCommand.Create(canExecute);
command.Subscribe(_ => OnRefresh());
RefreshCommand = command;
}
public string SelectedCountry { get; set; }
87
ReactiveCommand
public Filter()
{
var canExecute = this.WhenAny(
f => f.SelectedCity,
f => f.SelectedCountry,
(city, country) =>
city.Value != null && country.Value != null);
var command = ReactiveCommand.Create(canExecute);
command.Subscribe(_ => OnRefresh());
RefreshCommand = command;
}
public string SelectedCountry { get; set; }
88
ReactiveCommand
public Filter()
{
var canExecute = this.WhenAny(
f => f.SelectedCity,
f => f.SelectedCountry,
(city, country) =>
city.Value != null && country.Value != null);
var command = ReactiveCommand.Create(canExecute);
command.Subscribe(_ => OnRefresh());
RefreshCommand = command;
}
public string SelectedCountry { get; set; }
89
ReactiveCommand
public Filter()
{
var canExecute = this.WhenAny(
f => f.SelectedCity,
f => f.SelectedCountry,
(city, country) =>
city.Value != null && country.Value != null);
var command = ReactiveCommand.Create(canExecute);
command.Subscribe(_ => OnRefresh());
RefreshCommand = command;
}
public string SelectedCountry { get; set; }
90
ReactiveCommand
public Filter()
{
var canExecute = this.WhenAny(
f => f.SelectedCity,
f => f.SelectedCountry,
(city, country) =>
city.Value != null && country.Value != null);
var command = ReactiveCommand.Create(canExecute);
command.Subscribe(_ => OnRefresh());
RefreshCommand = command;
}
public string SelectedCountry { get; set; }
// RefreshCommand.RaiseCanExecuteChanged();
91
Достоинства ReactiveCommand
• Не нужны «подергивания» в сеттерах
• При изменения условия нельзя забыть убрать или
добавить «подергивание»
92
Задача 6: команда запускает асинхронное
действие
• При выполнении команды – вызов вебсервиса
• Пока не получим ответ от сервиса – команду нельзя
выполнить
93
Асинхронное действие RelayCommand
public bool IsBusy { get; set; }
//RefreshCommand.RaiseCanExecuteChanged();
public Filter(Task refreshTask)
{
RefreshTask = refreshTask;
RefreshCommand = new RelayCommand(OnRefresh,
() => SelectedCity != null &&
SelectedCountry != null &&
!IsBusy);
}
94
Асинхронное действие RelayCommand
public bool IsBusy { get; set; }
//RefreshCommand.RaiseCanExecuteChanged();
public Filter(Task refreshTask)
{
RefreshTask = refreshTask;
RefreshCommand = new RelayCommand(OnRefresh,
() => SelectedCity != null &&
SelectedCountry != null &&
!IsBusy);
}
95
Асинхронное действие RelayCommand
public bool IsBusy { get; set; }
//RefreshCommand.RaiseCanExecuteChanged();
public Filter(Task refreshTask)
{
RefreshTask = refreshTask;
RefreshCommand = new RelayCommand(OnRefresh,
() => SelectedCity != null &&
SelectedCountry != null &&
!IsBusy);
}
96
Асинхронное обновление
private async void OnRefresh()
{
IsBusy = true;
await RefreshTask; // запрос на сервер
IsBusy = false;
}
97
Асинхронное обновление
private async void OnRefresh()
{
IsBusy = true;
await RefreshTask; // запрос на сервер
IsBusy = false;
}
98
Асинхронное обновление
private async void OnRefresh()
{
IsBusy = true;
await RefreshTask; // запрос на сервер
IsBusy = false;
}
99
ReactiveAsyncCommand
public Filter(Func<object, Task> refreshFunc)
{
var canExecute = this.WhenAny(
f => f.SelectedCity, f => f.SelectedCountry,
(city, country) =>
city.Value != null && country.Value != null);
var command = ReactiveCommand.CreateAsyncTask(
canExecute,
refreshFunc);
RefreshCommand = command;
}
100
ReactiveAsyncCommand
public Filter(Func<object, Task> refreshFunc)
{
var canExecute = this.WhenAny(
f => f.SelectedCity, f => f.SelectedCountry,
(city, country) =>
city.Value != null && country.Value != null);
var command = ReactiveCommand.CreateAsyncTask(
canExecute,
refreshFunc);
RefreshCommand = command;
}
101
ReactiveAsyncCommand
public Filter(Func<object, Task> refreshFunc)
{
var canExecute = this.WhenAny(
f => f.SelectedCity, f => f.SelectedCountry,
(city, country) =>
city.Value != null && country.Value != null);
var command = ReactiveCommand.CreateAsyncTask(
canExecute,
refreshFunc);
RefreshCommand = command;
}
102
ReactiveAsyncCommand
public Filter(Func<object, Task> refreshFunc)
{
var canExecute = this.WhenAny(
f => f.SelectedCity, f => f.SelectedCountry,
(city, country) =>
city.Value != null && country.Value != null);
var command = ReactiveCommand.CreateAsyncTask(
canExecute,
refreshFunc);
RefreshCommand = command;
}
103
ReactiveAsyncCommand
public Filter(Func<object, Task> refreshFunc)
{
var canExecute = this.WhenAny(
f => f.SelectedCity, f => f.SelectedCountry,
(city, country) =>
city.Value != null && country.Value != null);
var command = ReactiveCommand.CreateAsyncTask(
canExecute,
refreshFunc);
RefreshCommand = command;
}
104
Демо
105
Достоинства ReactiveAsyncCommand
• Не нужно отдельного свойства для мониторинга
начала/конца операции
106
Задача 7: поиск по подстроке
Вместо фильтра делаем поиск
Если длина строки < 3, то ждем 0,5 сек перед поиском
Если длина строки >= 3, то ждем 0,25 сек перед поиском
107
Поиск по строке
public class ViewModel
{
public Filter Filter { get; set; }
public string SearchText { get; set; }
}
108
Поиск по строке
public class ViewModel
{
public Filter Filter { get; set; }
public string SearchText { get; set; }
}
109
Поиск по строке
public class ViewModel
{
public Filter Filter { get; set; }
public string SearchText { get; set; }
}
110
Where, Throttle
var textObservable =
this.ObservableForProperty(s => s.SearchText);
var firstRule = textObservable
.Where(text => text.Value == null || text.Value.Length < 3)
.Throttle(TimeSpan.FromMilliseconds(500));
var secondRule = textObservable
.Where(text => text.Value != null && text.Value.Length >= 3)
.Throttle(TimeSpan.FromMilliseconds(250));
111
Where, Throttle
var textObservable =
this.ObservableForProperty(s => s.SearchText);
var firstRule = textObservable
.Where(text => text.Value == null || text.Value.Length < 3)
.Throttle(TimeSpan.FromMilliseconds(500));
var secondRule = textObservable
.Where(text => text.Value != null && text.Value.Length >= 3)
.Throttle(TimeSpan.FromMilliseconds(250));
112
Where, Throttle
var textObservable =
this.ObservableForProperty(s => s.SearchText);
var firstRule = textObservable
.Where(text => text.Value == null || text.Value.Length < 3)
.Throttle(TimeSpan.FromMilliseconds(500));
var secondRule = textObservable
.Where(text => text.Value != null && text.Value.Length >= 3)
.Throttle(TimeSpan.FromMilliseconds(250));
113
Where, Throttle
var textObservable =
this.ObservableForProperty(s => s.SearchText);
var firstRule = textObservable
.Where(text => text.Value == null || text.Value.Length < 3)
.Throttle(TimeSpan.FromMilliseconds(500));
var secondRule = textObservable
.Where(text => text.Value != null && text.Value.Length >= 3)
.Throttle(TimeSpan.FromMilliseconds(250));
114
Where, Throttle
var textObservable =
this.ObservableForProperty(s => s.SearchText);
var firstRule = textObservable
.Where(text => text.Value == null || text.Value.Length < 3)
.Throttle(TimeSpan.FromMilliseconds(500));
var secondRule = textObservable
.Where(text => text.Value != null && text.Value.Length >= 3)
.Throttle(TimeSpan.FromMilliseconds(250));
115
Where, Throttle
var textObservable =
this.ObservableForProperty(s => s.SearchText);
var firstRule = textObservable
.Where(text => text.Value == null || text.Value.Length < 3)
.Throttle(TimeSpan.FromMilliseconds(500));
var secondRule = textObservable
.Where(text => text.Value != null && text.Value.Length >= 3)
.Throttle(TimeSpan.FromMilliseconds(250));
116
Merge, ObserveOnDispatcher
var resultObservable =
firstRule.Merge(secondRule);
resultObservable
.ObserveOnDispatcher()
.Subscribe(Refresh);
117
Merge, ObserveOnDispatcher
var resultObservable =
firstRule.Merge(secondRule);
resultObservable
.ObserveOnDispatcher()
.Subscribe(Refresh);
118
Merge, ObserveOnDispatcher
var resultObservable =
firstRule.Merge(secondRule);
resultObservable
.ObserveOnDispatcher()
.Subscribe(Refresh);
119
Достоинства
• На порядок меньшее кода, чем написали бы для
обычного решения
• Код простой и читаемый
АНТИПАТТЕРН: REACTIVEUI КАК MVVM
121
View – ViewModel маппинг
private void RegisterParts(IMutableDependencyResolver dependencyResolver)
{
dependencyResolver.RegisterConstant(this, typeof(IScreen));
dependencyResolver.Register(
() => new WelcomeView(), typeof(IViewFor<WelcomeViewModel>));
}
122
View – ViewModel маппинг
private void RegisterParts(IMutableDependencyResolver dependencyResolver)
{
dependencyResolver.RegisterConstant(this, typeof(IScreen));
dependencyResolver.Register(
() => new WelcomeView(), typeof(IViewFor<WelcomeViewModel>));
}
123
ViewModel First навигация
var viewModel =
(ViewModel) Locator.CurrentMutable.GetService(typeof(ViewModel));
viewModel.Title = "New ViewModel";
Router.Navigate.Execute(viewModel);
124
ViewModel First навигация
var viewModel =
(ViewModel) Locator.CurrentMutable.GetService(typeof(ViewModel));
viewModel.Title = "New ViewModel";
Router.Navigate.Execute(viewModel);
125
ViewModel First навигация
var viewModel =
(ViewModel) Locator.CurrentMutable.GetService(typeof(ViewModel));
viewModel.Title = "New ViewModel";
Router.Navigate.Execute(viewModel);
126
ViewModel First навигация
var viewModel =
(ViewModel) Locator.CurrentMutable.GetService(typeof(ViewModel));
viewModel.Title = "New ViewModel";
Router.Navigate.Execute(viewModel);
127
Нет CompositeUI
<Grid UseLayoutRounding="True">
<!--
COOLSTUFF: RoutedViewHost
RoutedViewHost will take a Router and display whatever ViewModel
is at the front of the route stack.
-->
<rx:RoutedViewHost
Router="{Binding Router}"
HorizontalContentAlignment="Stretch"
VerticalContentAlignment="Stretch" />
</Grid>
128
Нельзя избавиться от Splat IoC
В Splat
• Получить экземпляр сервиса – аналог ServiceLocator
Locator.CurrentMutable.GetService(typeof(Service))
Выход
• Реализовать IMutableDependencyResolver (для AutoFac)
129
Недостатки ReactiveUI как MVVM
• Нельзя задать конвенцию именования для маппинга View-
ViewModel
• Нет API, упрощающего задание соответствия View-ViewModel
• Splat – это ServiceLocator в чистом виде
• Нельзя заменить Splat на свой любимый IoC
• Нельзя реализовать CompositeUI
• ViewModelFirst навигация неудобная для UWP, WinRT, Windows
Phone
ЗАКЛЮЧЕНИЕ
131
Достоинства ReactiveUI
• Меньше инфраструктурного кода
• Повышает читаемость кода, группируя зависимости между
свойствами в конструкторе
• Решение типовых задач
• Подписка на изменение свойства
• Подписка на изменение нескольких свойств
• Автоматическая проверка состояния команды при:
• Изменении любого свойства, входящего в условие
• Начале/окончании асинхронной операции, запускаемой командой
132
Главное достоинство
Упрощается решение типовых задач разработчика XAML
приложений, а не какой-то экзотики!
133
Недостатки Rx и ReactiveUI
• Требует аккуратного использования, написать что-то
нечитаемое просто
• ReactiveUI лучше использовать как дополнение к MVVM
фреймворку
134
Полезные ссылки
http://reactiveui.net/
https://github.com/reactiveui
http://www.magnuslindhe.com/2015/08/one-way-of-cancelling-commands-using-reactiveui/ - отмена
асинхронной операции, запущенной командой
https://github.com/alexeyzimarev/RxUI6WithAutofac - Rx и Autofac
135
Что дальше делать?
• Посмотреть сэмплы Rx или ReactiveUI
• Попробовать Rx и ReactiveUI на отдельных
формах, если понравится – внедрять:
• ReactiveCommand
• ObservableForProperty
• WhenAny, WhenAnyValue
• Select, Merge, Zip
136
История успеха – GitHub Desktop
137
Спасибо за внимание
Вопросы?
Денис Цветцих
den.tsvettsih@yandex.ru

Contenu connexe

Tendances

Moxy – реализация MVP под Android. С щепоткой магии
Moxy – реализация MVP под Android. С щепоткой магииMoxy – реализация MVP под Android. С щепоткой магии
Moxy – реализация MVP под Android. С щепоткой магииYuri Shmakov
 
Windows Azure and node js
Windows Azure and node jsWindows Azure and node js
Windows Azure and node jsAlex Tumanoff
 
Performance optimization: effective interaction with virtual machine
Performance optimization: effective interaction with virtual machinePerformance optimization: effective interaction with virtual machine
Performance optimization: effective interaction with virtual machineReturn on Intelligence
 
«Как я научился не волноваться и полюбил Android-MVP», Никита Бартишок, ABBYY
«Как я научился не волноваться и полюбил Android-MVP», Никита Бартишок, ABBYY«Как я научился не волноваться и полюбил Android-MVP», Никита Бартишок, ABBYY
«Как я научился не волноваться и полюбил Android-MVP», Никита Бартишок, ABBYYMail.ru Group
 
Шишки, набитые за 15 лет использования акторов в C++
Шишки, набитые за 15 лет использования акторов в C++Шишки, набитые за 15 лет использования акторов в C++
Шишки, набитые за 15 лет использования акторов в C++Yauheni Akhotnikau
 
Moxy. Из чего состоит и как этим пользоваться
Moxy. Из чего состоит и как этим пользоватьсяMoxy. Из чего состоит и как этим пользоваться
Moxy. Из чего состоит и как этим пользоватьсяYuri Shmakov
 
HTML 5: будущее уже сегодня, Сергей Байдачный, Microsoft Ukraine
HTML 5: будущее уже сегодня, Сергей Байдачный, Microsoft UkraineHTML 5: будущее уже сегодня, Сергей Байдачный, Microsoft Ukraine
HTML 5: будущее уже сегодня, Сергей Байдачный, Microsoft UkraineVolha Banadyseva
 
JavaScript Базовый. Занятие 03.
JavaScript Базовый. Занятие 03.JavaScript Базовый. Занятие 03.
JavaScript Базовый. Занятие 03.Igor Shkulipa
 
C# Web. Занятие 02.
C# Web. Занятие 02.C# Web. Занятие 02.
C# Web. Занятие 02.Igor Shkulipa
 
Лекция Android. Fragments, ActionBar, Drawer
Лекция Android. Fragments, ActionBar, DrawerЛекция Android. Fragments, ActionBar, Drawer
Лекция Android. Fragments, ActionBar, DrawerАлександр Брич
 
Dependency Injection на примере Unity и NInject
Dependency Injection на примере Unity и NInjectDependency Injection на примере Unity и NInject
Dependency Injection на примере Unity и NInjectakrakovetsky
 
МАИ, Сети ЭВМ, Лекция №4
МАИ, Сети ЭВМ, Лекция №4МАИ, Сети ЭВМ, Лекция №4
МАИ, Сети ЭВМ, Лекция №4Dima Dzuba
 
C++ STL & Qt. Занятие 10.
C++ STL & Qt. Занятие 10.C++ STL & Qt. Занятие 10.
C++ STL & Qt. Занятие 10.Igor Shkulipa
 

Tendances (18)

Moxy – реализация MVP под Android. С щепоткой магии
Moxy – реализация MVP под Android. С щепоткой магииMoxy – реализация MVP под Android. С щепоткой магии
Moxy – реализация MVP под Android. С щепоткой магии
 
Windows Azure and node js
Windows Azure and node jsWindows Azure and node js
Windows Azure and node js
 
Обзор SObjectizer 5.5
Обзор SObjectizer 5.5Обзор SObjectizer 5.5
Обзор SObjectizer 5.5
 
Performance optimization: effective interaction with virtual machine
Performance optimization: effective interaction with virtual machinePerformance optimization: effective interaction with virtual machine
Performance optimization: effective interaction with virtual machine
 
Deep Dive in Magento DI
Deep Dive in Magento DIDeep Dive in Magento DI
Deep Dive in Magento DI
 
«Как я научился не волноваться и полюбил Android-MVP», Никита Бартишок, ABBYY
«Как я научился не волноваться и полюбил Android-MVP», Никита Бартишок, ABBYY«Как я научился не волноваться и полюбил Android-MVP», Никита Бартишок, ABBYY
«Как я научился не волноваться и полюбил Android-MVP», Никита Бартишок, ABBYY
 
Шишки, набитые за 15 лет использования акторов в C++
Шишки, набитые за 15 лет использования акторов в C++Шишки, набитые за 15 лет использования акторов в C++
Шишки, набитые за 15 лет использования акторов в C++
 
Moxy. Из чего состоит и как этим пользоваться
Moxy. Из чего состоит и как этим пользоватьсяMoxy. Из чего состоит и как этим пользоваться
Moxy. Из чего состоит и как этим пользоваться
 
HTML 5: будущее уже сегодня, Сергей Байдачный, Microsoft Ukraine
HTML 5: будущее уже сегодня, Сергей Байдачный, Microsoft UkraineHTML 5: будущее уже сегодня, Сергей Байдачный, Microsoft Ukraine
HTML 5: будущее уже сегодня, Сергей Байдачный, Microsoft Ukraine
 
JavaScript Базовый. Занятие 03.
JavaScript Базовый. Занятие 03.JavaScript Базовый. Занятие 03.
JavaScript Базовый. Занятие 03.
 
C# Web. Занятие 02.
C# Web. Занятие 02.C# Web. Занятие 02.
C# Web. Занятие 02.
 
Лекция Android. Fragments, ActionBar, Drawer
Лекция Android. Fragments, ActionBar, DrawerЛекция Android. Fragments, ActionBar, Drawer
Лекция Android. Fragments, ActionBar, Drawer
 
Лекция 2. Activity.
Лекция 2. Activity.Лекция 2. Activity.
Лекция 2. Activity.
 
Dependency Injection на примере Unity и NInject
Dependency Injection на примере Unity и NInjectDependency Injection на примере Unity и NInject
Dependency Injection на примере Unity и NInject
 
МАИ, Сети ЭВМ, Лекция №4
МАИ, Сети ЭВМ, Лекция №4МАИ, Сети ЭВМ, Лекция №4
МАИ, Сети ЭВМ, Лекция №4
 
Обзор возможностей HTML5
Обзор возможностей HTML5Обзор возможностей HTML5
Обзор возможностей HTML5
 
Fragments, ActionBar, Drawer
Fragments, ActionBar, DrawerFragments, ActionBar, Drawer
Fragments, ActionBar, Drawer
 
C++ STL & Qt. Занятие 10.
C++ STL & Qt. Занятие 10.C++ STL & Qt. Занятие 10.
C++ STL & Qt. Занятие 10.
 

Similaire à Как приручить реактивное программирование в XAML приложениях

Working with API
Working with APIWorking with API
Working with APIMad Devs
 
RxJava + Retrofit
RxJava + RetrofitRxJava + Retrofit
RxJava + RetrofitDev2Dev
 
Где кончается react native? / Павел Кондратенко (Rambler&Co)
Где кончается react native? / Павел Кондратенко (Rambler&Co)Где кончается react native? / Павел Кондратенко (Rambler&Co)
Где кончается react native? / Павел Кондратенко (Rambler&Co)Ontico
 
Реактивный двигатель для вашего Android-приложения — Матвей Мальков, 2ГИС
Реактивный двигатель для вашего Android-приложения — Матвей Мальков, 2ГИСРеактивный двигатель для вашего Android-приложения — Матвей Мальков, 2ГИС
Реактивный двигатель для вашего Android-приложения — Матвей Мальков, 2ГИС2ГИС Технологии
 
Реактивный двигатель вашего Android приложения
Реактивный двигатель вашего Android приложенияРеактивный двигатель вашего Android приложения
Реактивный двигатель вашего Android приложенияMatvey Malkov
 
Roslyn API : SyntaxTree vs CodeDom, SemanticModel vs Reflection
Roslyn API: SyntaxTree vs CodeDom, SemanticModel vs ReflectionRoslyn API: SyntaxTree vs CodeDom, SemanticModel vs Reflection
Roslyn API : SyntaxTree vs CodeDom, SemanticModel vs ReflectionDenis Tsvettsih
 
C# Desktop. Занятие 16.
C# Desktop. Занятие 16.C# Desktop. Занятие 16.
C# Desktop. Занятие 16.Igor Shkulipa
 
Как навести порядок в коде вашего web-приложения, Андрей Чебукин
Как навести порядок в коде вашего web-приложения, Андрей Чебукин Как навести порядок в коде вашего web-приложения, Андрей Чебукин
Как навести порядок в коде вашего web-приложения, Андрей Чебукин Sigma Software
 
RxJava+RxAndroid (Lecture 20 – rx java)
RxJava+RxAndroid (Lecture 20 – rx java)RxJava+RxAndroid (Lecture 20 – rx java)
RxJava+RxAndroid (Lecture 20 – rx java)Noveo
 
View как чистая функция от состояния базы данных - Илья Беда, bro.agency
View как чистая функция от состояния базы данных  - Илья Беда, bro.agencyView как чистая функция от состояния базы данных  - Илья Беда, bro.agency
View как чистая функция от состояния базы данных - Илья Беда, bro.agencyit-people
 
Java осень 2014 занятие 6
Java осень 2014 занятие 6Java осень 2014 занятие 6
Java осень 2014 занятие 6Technopark
 
Опыт разработки сложных клиент-серверных приложений на TypeScript и ASP.NET
Опыт разработки сложных клиент-серверных приложений на TypeScript и ASP.NETОпыт разработки сложных клиент-серверных приложений на TypeScript и ASP.NET
Опыт разработки сложных клиент-серверных приложений на TypeScript и ASP.NETGoSharp
 
Rempl — крутая платформа для крутых инструментов - Роман Дворнов (Avito)
Rempl — крутая платформа для крутых инструментов - Роман Дворнов (Avito)Rempl — крутая платформа для крутых инструментов - Роман Дворнов (Avito)
Rempl — крутая платформа для крутых инструментов - Роман Дворнов (Avito)AvitoTech
 
Превышаем скоростные лимиты с Angular 2 / Алексей Охрименко (IPONWEB)
Превышаем скоростные лимиты с Angular 2 / Алексей Охрименко (IPONWEB)Превышаем скоростные лимиты с Angular 2 / Алексей Охрименко (IPONWEB)
Превышаем скоростные лимиты с Angular 2 / Алексей Охрименко (IPONWEB)Ontico
 
Превышаем скоростные лимиты с Angular 2
Превышаем скоростные лимиты с Angular 2Превышаем скоростные лимиты с Angular 2
Превышаем скоростные лимиты с Angular 2Oleksii Okhrymenko
 
Что нам стоит DAL построить? Акуляков Артём D2D Just.NET
Что нам стоит DAL построить? Акуляков Артём D2D Just.NETЧто нам стоит DAL построить? Акуляков Артём D2D Just.NET
Что нам стоит DAL построить? Акуляков Артём D2D Just.NETDev2Dev
 
Codefest-2015 Reactive Streams
Codefest-2015 Reactive StreamsCodefest-2015 Reactive Streams
Codefest-2015 Reactive StreamsAlexey Romanchuk
 
Реактивные микросервисы с Apache Kafka / Денис Иванов (2ГИС)
Реактивные микросервисы с Apache Kafka / Денис Иванов (2ГИС)Реактивные микросервисы с Apache Kafka / Денис Иванов (2ГИС)
Реактивные микросервисы с Apache Kafka / Денис Иванов (2ГИС)Ontico
 
Юрий Ефимочев, Компилируемые в реальном времени DSL для С++
Юрий Ефимочев, Компилируемые в реальном времени DSL для С++ Юрий Ефимочев, Компилируемые в реальном времени DSL для С++
Юрий Ефимочев, Компилируемые в реальном времени DSL для С++ Sergey Platonov
 
Apollo GraphQL Federation
Apollo GraphQL FederationApollo GraphQL Federation
Apollo GraphQL FederationVitebsk Miniq
 

Similaire à Как приручить реактивное программирование в XAML приложениях (20)

Working with API
Working with APIWorking with API
Working with API
 
RxJava + Retrofit
RxJava + RetrofitRxJava + Retrofit
RxJava + Retrofit
 
Где кончается react native? / Павел Кондратенко (Rambler&Co)
Где кончается react native? / Павел Кондратенко (Rambler&Co)Где кончается react native? / Павел Кондратенко (Rambler&Co)
Где кончается react native? / Павел Кондратенко (Rambler&Co)
 
Реактивный двигатель для вашего Android-приложения — Матвей Мальков, 2ГИС
Реактивный двигатель для вашего Android-приложения — Матвей Мальков, 2ГИСРеактивный двигатель для вашего Android-приложения — Матвей Мальков, 2ГИС
Реактивный двигатель для вашего Android-приложения — Матвей Мальков, 2ГИС
 
Реактивный двигатель вашего Android приложения
Реактивный двигатель вашего Android приложенияРеактивный двигатель вашего Android приложения
Реактивный двигатель вашего Android приложения
 
Roslyn API : SyntaxTree vs CodeDom, SemanticModel vs Reflection
Roslyn API: SyntaxTree vs CodeDom, SemanticModel vs ReflectionRoslyn API: SyntaxTree vs CodeDom, SemanticModel vs Reflection
Roslyn API : SyntaxTree vs CodeDom, SemanticModel vs Reflection
 
C# Desktop. Занятие 16.
C# Desktop. Занятие 16.C# Desktop. Занятие 16.
C# Desktop. Занятие 16.
 
Как навести порядок в коде вашего web-приложения, Андрей Чебукин
Как навести порядок в коде вашего web-приложения, Андрей Чебукин Как навести порядок в коде вашего web-приложения, Андрей Чебукин
Как навести порядок в коде вашего web-приложения, Андрей Чебукин
 
RxJava+RxAndroid (Lecture 20 – rx java)
RxJava+RxAndroid (Lecture 20 – rx java)RxJava+RxAndroid (Lecture 20 – rx java)
RxJava+RxAndroid (Lecture 20 – rx java)
 
View как чистая функция от состояния базы данных - Илья Беда, bro.agency
View как чистая функция от состояния базы данных  - Илья Беда, bro.agencyView как чистая функция от состояния базы данных  - Илья Беда, bro.agency
View как чистая функция от состояния базы данных - Илья Беда, bro.agency
 
Java осень 2014 занятие 6
Java осень 2014 занятие 6Java осень 2014 занятие 6
Java осень 2014 занятие 6
 
Опыт разработки сложных клиент-серверных приложений на TypeScript и ASP.NET
Опыт разработки сложных клиент-серверных приложений на TypeScript и ASP.NETОпыт разработки сложных клиент-серверных приложений на TypeScript и ASP.NET
Опыт разработки сложных клиент-серверных приложений на TypeScript и ASP.NET
 
Rempl — крутая платформа для крутых инструментов - Роман Дворнов (Avito)
Rempl — крутая платформа для крутых инструментов - Роман Дворнов (Avito)Rempl — крутая платформа для крутых инструментов - Роман Дворнов (Avito)
Rempl — крутая платформа для крутых инструментов - Роман Дворнов (Avito)
 
Превышаем скоростные лимиты с Angular 2 / Алексей Охрименко (IPONWEB)
Превышаем скоростные лимиты с Angular 2 / Алексей Охрименко (IPONWEB)Превышаем скоростные лимиты с Angular 2 / Алексей Охрименко (IPONWEB)
Превышаем скоростные лимиты с Angular 2 / Алексей Охрименко (IPONWEB)
 
Превышаем скоростные лимиты с Angular 2
Превышаем скоростные лимиты с Angular 2Превышаем скоростные лимиты с Angular 2
Превышаем скоростные лимиты с Angular 2
 
Что нам стоит DAL построить? Акуляков Артём D2D Just.NET
Что нам стоит DAL построить? Акуляков Артём D2D Just.NETЧто нам стоит DAL построить? Акуляков Артём D2D Just.NET
Что нам стоит DAL построить? Акуляков Артём D2D Just.NET
 
Codefest-2015 Reactive Streams
Codefest-2015 Reactive StreamsCodefest-2015 Reactive Streams
Codefest-2015 Reactive Streams
 
Реактивные микросервисы с Apache Kafka / Денис Иванов (2ГИС)
Реактивные микросервисы с Apache Kafka / Денис Иванов (2ГИС)Реактивные микросервисы с Apache Kafka / Денис Иванов (2ГИС)
Реактивные микросервисы с Apache Kafka / Денис Иванов (2ГИС)
 
Юрий Ефимочев, Компилируемые в реальном времени DSL для С++
Юрий Ефимочев, Компилируемые в реальном времени DSL для С++ Юрий Ефимочев, Компилируемые в реальном времени DSL для С++
Юрий Ефимочев, Компилируемые в реальном времени DSL для С++
 
Apollo GraphQL Federation
Apollo GraphQL FederationApollo GraphQL Federation
Apollo GraphQL Federation
 

Как приручить реактивное программирование в XAML приложениях

  • 1. Как приручить реактивное программирование в XAML приложениях Денис Цветцих Ice Rock Dev icerockdev.com DotNext Spb 3 июня 2016 dotnext.ru/spb/
  • 2. 2 Кто я? • 7+ лет .NET • Разработка корпоративных приложений • Люблю хипстерские библиотеки 
  • 3. 3 Почему я здесь? • 6 лет назад приручил Rx • 3 года назад приручил ReactiveUI
  • 4. 4 ReactiveUI – ни одного поста на хабре
  • 5. 5 Сэмплы не показательны Rx – 101 пример задач, где можно обойтись без Rx ReactiveUI – единственный жизненный сэмпл – поиск (на главной странице)
  • 6. 6 О чем мы поговорим? • Что такое реактивное программирование • Примеры задач из продакшен проектов, которые проще решаются с помощью Rx и ReactiveUI • Антипаттерны: как ReactiveUI лучше не использовать
  • 7. ЧТО ТАКОЕ РЕАКТИВНОЕ ПРОГРАММИРОВАНИЕ
  • 8. 8 Что такое реактивное программирование? Реактивное программирование – это парадигма программирования, ориентированная на потоки данных и распространение изменений. (Википедия)
  • 9. 9 Парадигма программирования Парадигма программирования – это система идей и понятий, определяющих стиль написания компьютерных программ.
  • 10. 10 Императивная программа var A = 10; var B = A + 1; // Чему равно В? 11 A = A + 1; // А теперь чему равно В? 11
  • 11. 11 Реактивная программа var A = 10; var B <- A + 1; <- оператор «судьбы» // Чему равно В? 11 A = A + 1; // А теперь чему равно В? 12
  • 12. 12 Реактивное программирование Реактивное программирование – это парадигма программирования, ориентированная на потоки данных и распространение изменений. Поток данных – последовательность значений переменной или свойства класса. Распространение изменений – уведомления «заинтересованных» об изменениях.
  • 13. 13 Pull Pull – класс A взаимодействует с классом B и вытягивает из него необходимые данные Class A Class B GetData() Interacts with
  • 14. 14 Push Push – класс B самостоятельно выталкивает данные классу A, как только они становятся доступны. Class A ProcessData() Class BReacts on
  • 16. 16 Реактивное программирование на .NET • Reactive Extensions (Rx) • Эрик Мейер из MS Research • ReactiveUI • Разработана в MS Research • библиотека на базе Rx для создания элегантных UI для всех XAML платформ
  • 17. 17 Внутри .NET public interface IObservable<T> { IDisposable Subscribe(IObserver<T> observer); } public interface IObserver<T> { void OnNext(T value); void OnCompleted(); void OnError(Exception error); }
  • 18. 18 Внутри .NET public interface IObservable<T> { IDisposable Subscribe(IObserver<T> observer); } public interface IObserver<T> { void OnNext(T value); void OnCompleted(); void OnError(Exception error); }
  • 19. 19 Внутри .NET public interface IObservable<T> { IDisposable Subscribe(IObserver<T> observer); } public interface IObserver<T> { void OnNext(T value); void OnCompleted(); void OnError(Exception error); }
  • 20. 20 Внутри .NET public interface IObservable<T> { IDisposable Subscribe(IObserver<T> observer); } public interface IObserver<T> { void OnNext(T value); void OnCompleted(); void OnError(Exception error); }
  • 21. 21 Внутри .NET public interface IObservable<T> { IDisposable Subscribe(IObserver<T> observer); } public interface IObserver<T> { void OnNext(T value); void OnCompleted(); void OnError(Exception error); }
  • 22. 22 Оператор судьбы через Rx var A = new Subject<int>(); // B <- A + 1 var B = A.Select(a => a + 1); A.OnNext(10);
  • 23. 23 Оператор судьбы через Rx var A = new Subject<int>(); // B <- A + 1 var B = A.Select(a => a + 1); A.OnNext(10);
  • 24. 24 Оператор судьбы через Rx var A = new Subject<int>(); // B <- A + 1 var B = A.Select(a => a + 1); A.OnNext(10);
  • 25. 25 Оператор судьбы через Rx var A = new Subject<int>(); // B <- A + 1 var B = A.Select(a => a + 1); A.OnNext(10);
  • 29. 29 Пример Приложение для поиска по материалам конференции DotNext
  • 30. 30 Для читабельности примеров public class Filter : ReactiveObject { private DateTime _fromDate; private DateTime _toDate; public DateTime FromDate { get { return _fromDate; } set { this.RaiseAndSetIfChanged(ref _fromDate, value); } } public DateTime ToDate { get { return _toDate; } set { this.RaiseAndSetIfChanged(ref _toDate, value); } } }
  • 31. 31 Для читабельности примеров public class Filter : ReactiveObject { private DateTime _fromDate; private DateTime _toDate; public DateTime FromDate { get { return _fromDate; } set { this.RaiseAndSetIfChanged(ref _fromDate, value); } } public DateTime ToDate { get { return _toDate; } set { this.RaiseAndSetIfChanged(ref _toDate, value); } } }
  • 32. 32 Для читабельности примеров public class Filter : ReactiveObject { private DateTime _fromDate; private DateTime _toDate; public DateTime FromDate { get { return _fromDate; } set { this.RaiseAndSetIfChanged(ref _fromDate, value); } } public DateTime ToDate { get { return _toDate; } set { this.RaiseAndSetIfChanged(ref _toDate, value); } } }
  • 33. 33 Для читабельности примеров public class Filter { public DateTime FromDate { get; set; } public DateTime ToDate { get; set; } }
  • 34. 34 Задача 1: подписка на события об изменении свойств фильтра
  • 35. 35 Обычная подписка на событие PropertyChanged var filter = new Filter(); // Подписка filter.PropertyChanged += OnPropertyChanged; // Реакция на событие private void OnPropertyChanged (object sender, PropertyChangedEventArgs e) { } // Отписка filter.PropertyChanged -= OnPropertyChanged;
  • 36. 36 Обычная подписка на событие PropertyChanged var filter = new Filter(); // Подписка filter.PropertyChanged += OnPropertyChanged; // Реакция на событие private void OnPropertyChanged (object sender, PropertyChangedEventArgs e) { } // Отписка filter.PropertyChanged -= OnPropertyChanged;
  • 37. 37 var filter = new Filter(); // Подписка filter.PropertyChanged += OnPropertyChanged; // Реакция на событие private void OnPropertyChanged (object sender, PropertyChangedEventArgs e) { } // Отписка filter.PropertyChanged -= OnPropertyChanged; Обычная подписка на событие PropertyChanged
  • 38. 38 Обычная подписка на событие PropertyChanged var filter = new Filter(); // Подписка filter.PropertyChanged += OnPropertyChanged; // Реакция на событие private void OnPropertyChanged (object sender, PropertyChangedEventArgs e) { } // Отписка filter.PropertyChanged -= OnPropertyChanged;
  • 39. 39 filter = new Filter(); // Подписка IDisposable subscription = Observable.FromEventPattern<PropertyChangedEventArgs> (filter, "PropertyChanged") .Subscribe(OnPropertyChanged); // Реакция на событие private void OnPropertyChanged (EventPattern<PropertyChangedEventArgs> eventPattern) { //eventPattern.Sender и eventPattern.EventArgs } // Отписка subscription.Dispose(); Rx подписка на событие PropertyChanged
  • 40. 40 filter = new Filter(); // Подписка IDisposable subscription = Observable.FromEventPattern<PropertyChangedEventArgs> (filter, "PropertyChanged") .Subscribe(OnPropertyChanged); // Реакция на событие private void OnPropertyChanged (EventPattern<PropertyChangedEventArgs> eventPattern) { //eventPattern.Sender и eventPattern.EventArgs } // Отписка subscription.Dispose(); Rx подписка на событие PropertyChanged
  • 41. 41 filter = new Filter(); // Подписка IDisposable subscription = Observable.FromEventPattern<PropertyChangedEventArgs> (filter, "PropertyChanged") .Subscribe(OnPropertyChanged); // Реакция на событие private void OnPropertyChanged (EventPattern<PropertyChangedEventArgs> eventPattern) { //eventPattern.Sender и eventPattern.EventArgs } // Отписка subscription.Dispose(); Rx подписка на событие PropertyChanged
  • 42. 42 filter = new Filter(); // Подписка IDisposable subscription = Observable.FromEventPattern<PropertyChangedEventArgs> (filter, "PropertyChanged") .Subscribe(OnPropertyChanged); // Реакция на событие private void OnPropertyChanged (EventPattern<PropertyChangedEventArgs> eventPattern) { //eventPattern.Sender и eventPattern.EventArgs } // Отписка subscription.Dispose(); Rx подписка на событие PropertyChanged
  • 43. 43 Rx подписка на событие PropertyChanged filter = new Filter(); // Подписка IDisposable subscription = Observable.FromEventPattern<PropertyChangedEventArgs> (filter, "PropertyChanged") .Subscribe(OnPropertyChanged); // Реакция на событие private void OnPropertyChanged (EventPattern<PropertyChangedEventArgs> eventPattern) { //eventPattern.Sender и eventPattern.EventArgs } // Отписка subscription.Dispose();
  • 44. 44 Rx подписка на событие PropertyChanged filter = new Filter(); // Подписка IDisposable subscription = Observable.FromEventPattern<PropertyChangedEventArgs> (filter, "PropertyChanged") .Subscribe(OnPropertyChanged); // Реакция на событие private void OnPropertyChanged (EventPattern<PropertyChangedEventArgs> eventPattern) { //eventPattern.Sender и eventPattern.EventArgs } // Отписка subscription.Dispose();
  • 45. 45 Достоинства Rx подписки • Подписка инкапсулируется в IDisposable объекте, просто сделать отписку • Подписка – это поток данных, над которым можно выполнять операции
  • 46. 46 Задача 2: подписка на изменение свойства FromDate
  • 47. 47 Обычная подписка на изменение свойства FromDate Filter filter = new Filter(); filter.PropertyChanged += OnPropertyChanged; private void OnPropertyChanged (object sender, PropertyChangedEventArgs eventArgs) { if (eventArgs.PropertyName == “FromDate") { // Действия } }
  • 48. 48 Обычная подписка на изменение свойства FromDate Filter filter = new Filter(); filter.PropertyChanged += OnPropertyChanged; private void OnPropertyChanged (object sender, PropertyChangedEventArgs eventArgs) { if (eventArgs.PropertyName == “FromDate") { // Действия } }
  • 49. 49 Обычная подписка на изменение свойства FromDate Filter filter = new Filter(); filter.PropertyChanged += OnPropertyChanged; private void OnPropertyChanged (object sender, PropertyChangedEventArgs eventArgs) { if (eventArgs.PropertyName == “FromDate") { // Действия } }
  • 50. 50 ReactiveUI подписка на изменение свойства FromDate var filter = new Filter(); var subscription = filter .ObservableForProperty(f => f.FromDate) .Subscribe(FromDateChanged); private void FromDateChanged (IObservedChange<Filter, DateTime> change) { // Действия }
  • 51. 51 ReactiveUI подписка на изменение свойства FromDate var filter = new Filter(); var subscription = filter .ObservableForProperty(f => f.FromDate) .Subscribe(FromDateChanged); private void FromDateChanged (IObservedChange<Filter, DateTime> change) { // Действия }
  • 52. 52 ReactiveUI подписка на изменение свойства FromDate var filter = new Filter(); var subscription = filter .ObservableForProperty(f => f.FromDate) .Subscribe(FromDateChanged); private void FromDateChanged (IObservedChange<Filter, DateTime> change) { // Действия }
  • 53. 53 ReactiveUI подписка на изменение свойства FromDate var filter = new Filter(); var subscription = filter .ObservableForProperty(f => f.FromDate) .Subscribe(FromDateChanged); private void FromDateChanged (IObservedChange<Filter, DateTime> change) { // Действия }
  • 54. 54 Достоинства ObservableForProperty • Не нужно проверять, что обрабатываем событие об изменении нужного свойства
  • 55. 55 Задача 3: действия при изменении значений свойств
  • 56. 56 Добавим выбор страны и города Добавим в фильтр: • Список стран и городов • При выборе страны актуализируем список городов
  • 57. 57 Валидация выбранного периода Условие: FromDate не превосходит ToDate Если FromDate > ToDate, тогда ToDate приравниваем к FromDate Если ToDate < FromDate, тогда FromDate приравниваем к ToDate
  • 58. 58 Обычное решение private DateTime _fromDate; public DateTime FromDate { get { return _fromDate; } set { _fromDate = value; OnPropertyChanged(); if (_fromDate > ToDate) ToDate = _fromDate; } }
  • 59. 59 Обычное решение private DateTime _fromDate; public DateTime FromDate { get { return _fromDate; } set { _fromDate = value; OnPropertyChanged(); if (_fromDate > ToDate) ToDate = _fromDate; } }
  • 60. 60 ObservableForProperty private IDisposable _subscription; public Filter() { _subscription = this.ObservableForProperty(vm => vm.FromDate) .Where(fromDate => fromDate.Value > ToDate) .Subscribe(fromDate => ToDate = fromDate.Value); } public DateTime FromDate { get; set; } // PropertyChanged
  • 61. 61 ObservableForProperty private IDisposable _subscription; public Filter() { _subscription = this.ObservableForProperty(vm => vm.FromDate) .Where(fromDate => fromDate.Value > ToDate) .Subscribe(fromDate => ToDate = fromDate.Value); } public DateTime FromDate { get; set; } // PropertyChanged
  • 62. 62 ObservableForProperty private IDisposable _subscription; public Filter() { _subscription = this.ObservableForProperty(vm => vm.FromDate) .Where(fromDate => fromDate.Value > ToDate) .Subscribe(fromDate => ToDate = fromDate.Value); } public DateTime FromDate { get; set; } // PropertyChanged
  • 63. 63 ObservableForProperty private IDisposable _subscription; public Filter() { _subscription = this.ObservableForProperty(vm => vm.FromDate) .Where(fromDate => fromDate.Value > ToDate) .Subscribe(fromDate => ToDate = fromDate.Value); } public DateTime FromDate { get; set; } // PropertyChanged
  • 64. 64 ObservableForProperty private IDisposable _subscription; public Filter() { _subscription = this.ObservableForProperty(vm => vm.FromDate) .Where(fromDate => fromDate.Value > ToDate) .Subscribe(fromDate => ToDate = fromDate.Value); } public DateTime FromDate { get; set; } // PropertyChanged if (_fromDate > ToDate) ToDate = _fromDate;
  • 65. 65 Зависимости между свойствами в обычном решении public class Filter { public DateTime FromDate { get { return _fromDate; } set { _fromDate = value; if (_fromDate > ToDate) ToDate = FromDate; } } public DateTime ToDate { get { return _toDate; } set { _toDate = value; if (_toDate < FromDate) FromDate = _toDate; } } public List<string> Cities { get { return _cities; } set { _cities = value; SelectedCity = null; } } public string SelectedCountry { get { return _selectedCountry; } set { _selectedCountry = value; Cities = GetCitesForCountry(_selectedCountry); } } }
  • 66. 66 Зависимости между свойствами в ReactiveUI решении public class Filter { public Filter() { _fromDateSubscription = this.ObservableForProperty(filter => filter.FromDate) .Where(fromDate => fromDate.Value > ToDate) .Subscribe(fromDate => ToDate = fromDate.Value); _toDateSubscription = this.ObservableForProperty(filter => filter.ToDate) .Where(toDate => toDate.Value < FromDate) .Subscribe(toDate => FromDate = toDate.Value); _selectedCountrySubscription = this.ObservableForProperty(filter => filter.SelectedCountry) .Subscribe(country => Cities = GetCitesForCountry(country.Value)); _citiesSubscription = this.ObservableForProperty(filter => Cities) .Subscribe(_ => SelectedCity = null); } public DateTime FromDate { get; set; } public DateTime ToDate { get; set; } public List<string> Countries { get; set; } public List<string> Cities { get; set; } public string SelectedCountry { get; set; } public string SelectedCity { get; set; } }
  • 67. 67 Достоинства ObservableForProperty • Удаление логики из сеттеров • Делает код на порядок более читаемым, собирая код зависимости между свойствами в конструкторе
  • 68. 68 Задача 4: мониторинг одновременно нескольких свойств Поля фильтра: • От • До • Страна • Город При изменении любого – обновляем данные
  • 69. 69 ViewModel с фильтром public class ViewModel { public Filter Filter { get; set; } public ViewModel() { Filter = new Filter(); // Подписка на изменение свойств фильтра } }
  • 70. 70 ViewModel с фильтром public class ViewModel { public Filter Filter { get; set; } public ViewModel() { Filter = new Filter(); // Подписка на изменение свойств фильтра } }
  • 71. 71 ViewModel с фильтром public class ViewModel { public Filter Filter { get; set; } public ViewModel() { Filter = new Filter(); // Подписка на изменение свойств фильтра } }
  • 72. 72 Обычное решение public ViewModel() { Filter = new Filter(); // Подписка на изменение свойств фильтра Filter.PropertyChanged += OnFilterPropertyChanged; } private void OnFilterPropertyChanged (object sender, PropertyChangedEventArgs eventArgs) { if (eventArgs.PropertyName == "FromDate" || eventArgs.PropertyName == "ToDate" || eventArgs.PropertyName == "SelectedCountry" || eventArgs.PropertyName == "SelectedCity") { Refresh(); } }
  • 73. 73 Обычное решение public ViewModel() { Filter = new Filter(); // Подписка на изменение свойств фильтра Filter.PropertyChanged += OnFilterPropertyChanged; } private void OnFilterPropertyChanged (object sender, PropertyChangedEventArgs eventArgs) { if (eventArgs.PropertyName == "FromDate" || eventArgs.PropertyName == "ToDate" || eventArgs.PropertyName == "SelectedCountry" || eventArgs.PropertyName == "SelectedCity") { Refresh(); } }
  • 74. 74 ReactiveUI решение public ViewModel() { Filter = new Filter(); // Подписка на изменение свойств фильтра _subscription = Filter.WhenAnyValue( f => f.FromDate, f => f.ToDate, f => f.SelectedCountry, f => f.SelectedCity) .Subscribe(_ => Refresh()); }
  • 75. 75 ReactiveUI решение public ViewModel() { Filter = new Filter(); // Подписка на изменение свойств фильтра _subscription = Filter.WhenAnyValue( f => f.FromDate, f => f.ToDate, f => f.SelectedCountry, f => f.SelectedCity) .Subscribe(_ => Refresh()); }
  • 76. 76 ReactiveUI решение public ViewModel() { Filter = new Filter(); // Подписка на изменение свойств фильтра _subscription = Filter.WhenAnyValue( f => f.FromDate, f => f.ToDate, f => f.SelectedCountry, f => f.SelectedCity) .Subscribe(_ => Refresh()); }
  • 77. 77 ReactiveUI решение public ViewModel() { Filter = new Filter(); // Подписка на изменение свойств фильтра _subscription = Filter.WhenAnyValue( f => f.FromDate, f => f.ToDate, f => f.SelectedCountry, f => f.SelectedCity) .Subscribe(_ => Refresh()); }
  • 78. 78 Достоинства WhenAnyValue • Подписываемся только на нужные нам свойства • Можно подписываться на изменения вложенных свойств • Obj.Deep1.Deep2.Deep3…
  • 79. 79 Задача 5: проверка CanExecute команды • Добавляем кнопку «Обновить» на фильтр • Страна и город – обязательные поля
  • 80. 80 RelayCommand из MvvmLight public RelayCommand RefreshCommand { get; private set; } public Filter() { RefreshCommand = new RelayCommand( OnRefresh, () => SelectedCity != null && SelectedCountry != null); }
  • 81. 81 RelayCommand из MvvmLight public RelayCommand RefreshCommand { get; private set; } public Filter() { RefreshCommand = new RelayCommand( OnRefresh, () => SelectedCity != null && SelectedCountry != null); }
  • 82. 82 RelayCommand из MvvmLight public RelayCommand RefreshCommand { get; private set; } public Filter() { RefreshCommand = new RelayCommand( OnRefresh, () => SelectedCity != null && SelectedCountry != null); }
  • 83. 83 RelayCommand из MvvmLight public RelayCommand RefreshCommand { get; private set; } public Filter() { RefreshCommand = new RelayCommand( OnRefresh, () => SelectedCity != null && SelectedCountry != null); }
  • 84. 84 Подергивания  public string SelectedCountry { get { return _selectedCountry; } set { _selectedCountry = value; OnPropertyChanged(); RefreshCommand.RaiseCanExecuteChanged(); } } public string SelectedCity { get { return _selectedCity; } set { _selectedCity = value; OnPropertyChanged(); RefreshCommand.RaiseCanExecuteChanged(); } }
  • 85. 85 ReactiveCommand public Filter() { var canExecute = this.WhenAny( f => f.SelectedCity, f => f.SelectedCountry, (city, country) => city.Value != null && country.Value != null); var command = ReactiveCommand.Create(canExecute); command.Subscribe(_ => OnRefresh()); RefreshCommand = command; } public string SelectedCountry { get; set; }
  • 86. 86 ReactiveCommand public Filter() { var canExecute = this.WhenAny( f => f.SelectedCity, f => f.SelectedCountry, (city, country) => city.Value != null && country.Value != null); var command = ReactiveCommand.Create(canExecute); command.Subscribe(_ => OnRefresh()); RefreshCommand = command; } public string SelectedCountry { get; set; }
  • 87. 87 ReactiveCommand public Filter() { var canExecute = this.WhenAny( f => f.SelectedCity, f => f.SelectedCountry, (city, country) => city.Value != null && country.Value != null); var command = ReactiveCommand.Create(canExecute); command.Subscribe(_ => OnRefresh()); RefreshCommand = command; } public string SelectedCountry { get; set; }
  • 88. 88 ReactiveCommand public Filter() { var canExecute = this.WhenAny( f => f.SelectedCity, f => f.SelectedCountry, (city, country) => city.Value != null && country.Value != null); var command = ReactiveCommand.Create(canExecute); command.Subscribe(_ => OnRefresh()); RefreshCommand = command; } public string SelectedCountry { get; set; }
  • 89. 89 ReactiveCommand public Filter() { var canExecute = this.WhenAny( f => f.SelectedCity, f => f.SelectedCountry, (city, country) => city.Value != null && country.Value != null); var command = ReactiveCommand.Create(canExecute); command.Subscribe(_ => OnRefresh()); RefreshCommand = command; } public string SelectedCountry { get; set; }
  • 90. 90 ReactiveCommand public Filter() { var canExecute = this.WhenAny( f => f.SelectedCity, f => f.SelectedCountry, (city, country) => city.Value != null && country.Value != null); var command = ReactiveCommand.Create(canExecute); command.Subscribe(_ => OnRefresh()); RefreshCommand = command; } public string SelectedCountry { get; set; } // RefreshCommand.RaiseCanExecuteChanged();
  • 91. 91 Достоинства ReactiveCommand • Не нужны «подергивания» в сеттерах • При изменения условия нельзя забыть убрать или добавить «подергивание»
  • 92. 92 Задача 6: команда запускает асинхронное действие • При выполнении команды – вызов вебсервиса • Пока не получим ответ от сервиса – команду нельзя выполнить
  • 93. 93 Асинхронное действие RelayCommand public bool IsBusy { get; set; } //RefreshCommand.RaiseCanExecuteChanged(); public Filter(Task refreshTask) { RefreshTask = refreshTask; RefreshCommand = new RelayCommand(OnRefresh, () => SelectedCity != null && SelectedCountry != null && !IsBusy); }
  • 94. 94 Асинхронное действие RelayCommand public bool IsBusy { get; set; } //RefreshCommand.RaiseCanExecuteChanged(); public Filter(Task refreshTask) { RefreshTask = refreshTask; RefreshCommand = new RelayCommand(OnRefresh, () => SelectedCity != null && SelectedCountry != null && !IsBusy); }
  • 95. 95 Асинхронное действие RelayCommand public bool IsBusy { get; set; } //RefreshCommand.RaiseCanExecuteChanged(); public Filter(Task refreshTask) { RefreshTask = refreshTask; RefreshCommand = new RelayCommand(OnRefresh, () => SelectedCity != null && SelectedCountry != null && !IsBusy); }
  • 96. 96 Асинхронное обновление private async void OnRefresh() { IsBusy = true; await RefreshTask; // запрос на сервер IsBusy = false; }
  • 97. 97 Асинхронное обновление private async void OnRefresh() { IsBusy = true; await RefreshTask; // запрос на сервер IsBusy = false; }
  • 98. 98 Асинхронное обновление private async void OnRefresh() { IsBusy = true; await RefreshTask; // запрос на сервер IsBusy = false; }
  • 99. 99 ReactiveAsyncCommand public Filter(Func<object, Task> refreshFunc) { var canExecute = this.WhenAny( f => f.SelectedCity, f => f.SelectedCountry, (city, country) => city.Value != null && country.Value != null); var command = ReactiveCommand.CreateAsyncTask( canExecute, refreshFunc); RefreshCommand = command; }
  • 100. 100 ReactiveAsyncCommand public Filter(Func<object, Task> refreshFunc) { var canExecute = this.WhenAny( f => f.SelectedCity, f => f.SelectedCountry, (city, country) => city.Value != null && country.Value != null); var command = ReactiveCommand.CreateAsyncTask( canExecute, refreshFunc); RefreshCommand = command; }
  • 101. 101 ReactiveAsyncCommand public Filter(Func<object, Task> refreshFunc) { var canExecute = this.WhenAny( f => f.SelectedCity, f => f.SelectedCountry, (city, country) => city.Value != null && country.Value != null); var command = ReactiveCommand.CreateAsyncTask( canExecute, refreshFunc); RefreshCommand = command; }
  • 102. 102 ReactiveAsyncCommand public Filter(Func<object, Task> refreshFunc) { var canExecute = this.WhenAny( f => f.SelectedCity, f => f.SelectedCountry, (city, country) => city.Value != null && country.Value != null); var command = ReactiveCommand.CreateAsyncTask( canExecute, refreshFunc); RefreshCommand = command; }
  • 103. 103 ReactiveAsyncCommand public Filter(Func<object, Task> refreshFunc) { var canExecute = this.WhenAny( f => f.SelectedCity, f => f.SelectedCountry, (city, country) => city.Value != null && country.Value != null); var command = ReactiveCommand.CreateAsyncTask( canExecute, refreshFunc); RefreshCommand = command; }
  • 105. 105 Достоинства ReactiveAsyncCommand • Не нужно отдельного свойства для мониторинга начала/конца операции
  • 106. 106 Задача 7: поиск по подстроке Вместо фильтра делаем поиск Если длина строки < 3, то ждем 0,5 сек перед поиском Если длина строки >= 3, то ждем 0,25 сек перед поиском
  • 107. 107 Поиск по строке public class ViewModel { public Filter Filter { get; set; } public string SearchText { get; set; } }
  • 108. 108 Поиск по строке public class ViewModel { public Filter Filter { get; set; } public string SearchText { get; set; } }
  • 109. 109 Поиск по строке public class ViewModel { public Filter Filter { get; set; } public string SearchText { get; set; } }
  • 110. 110 Where, Throttle var textObservable = this.ObservableForProperty(s => s.SearchText); var firstRule = textObservable .Where(text => text.Value == null || text.Value.Length < 3) .Throttle(TimeSpan.FromMilliseconds(500)); var secondRule = textObservable .Where(text => text.Value != null && text.Value.Length >= 3) .Throttle(TimeSpan.FromMilliseconds(250));
  • 111. 111 Where, Throttle var textObservable = this.ObservableForProperty(s => s.SearchText); var firstRule = textObservable .Where(text => text.Value == null || text.Value.Length < 3) .Throttle(TimeSpan.FromMilliseconds(500)); var secondRule = textObservable .Where(text => text.Value != null && text.Value.Length >= 3) .Throttle(TimeSpan.FromMilliseconds(250));
  • 112. 112 Where, Throttle var textObservable = this.ObservableForProperty(s => s.SearchText); var firstRule = textObservable .Where(text => text.Value == null || text.Value.Length < 3) .Throttle(TimeSpan.FromMilliseconds(500)); var secondRule = textObservable .Where(text => text.Value != null && text.Value.Length >= 3) .Throttle(TimeSpan.FromMilliseconds(250));
  • 113. 113 Where, Throttle var textObservable = this.ObservableForProperty(s => s.SearchText); var firstRule = textObservable .Where(text => text.Value == null || text.Value.Length < 3) .Throttle(TimeSpan.FromMilliseconds(500)); var secondRule = textObservable .Where(text => text.Value != null && text.Value.Length >= 3) .Throttle(TimeSpan.FromMilliseconds(250));
  • 114. 114 Where, Throttle var textObservable = this.ObservableForProperty(s => s.SearchText); var firstRule = textObservable .Where(text => text.Value == null || text.Value.Length < 3) .Throttle(TimeSpan.FromMilliseconds(500)); var secondRule = textObservable .Where(text => text.Value != null && text.Value.Length >= 3) .Throttle(TimeSpan.FromMilliseconds(250));
  • 115. 115 Where, Throttle var textObservable = this.ObservableForProperty(s => s.SearchText); var firstRule = textObservable .Where(text => text.Value == null || text.Value.Length < 3) .Throttle(TimeSpan.FromMilliseconds(500)); var secondRule = textObservable .Where(text => text.Value != null && text.Value.Length >= 3) .Throttle(TimeSpan.FromMilliseconds(250));
  • 116. 116 Merge, ObserveOnDispatcher var resultObservable = firstRule.Merge(secondRule); resultObservable .ObserveOnDispatcher() .Subscribe(Refresh);
  • 117. 117 Merge, ObserveOnDispatcher var resultObservable = firstRule.Merge(secondRule); resultObservable .ObserveOnDispatcher() .Subscribe(Refresh);
  • 118. 118 Merge, ObserveOnDispatcher var resultObservable = firstRule.Merge(secondRule); resultObservable .ObserveOnDispatcher() .Subscribe(Refresh);
  • 119. 119 Достоинства • На порядок меньшее кода, чем написали бы для обычного решения • Код простой и читаемый
  • 121. 121 View – ViewModel маппинг private void RegisterParts(IMutableDependencyResolver dependencyResolver) { dependencyResolver.RegisterConstant(this, typeof(IScreen)); dependencyResolver.Register( () => new WelcomeView(), typeof(IViewFor<WelcomeViewModel>)); }
  • 122. 122 View – ViewModel маппинг private void RegisterParts(IMutableDependencyResolver dependencyResolver) { dependencyResolver.RegisterConstant(this, typeof(IScreen)); dependencyResolver.Register( () => new WelcomeView(), typeof(IViewFor<WelcomeViewModel>)); }
  • 123. 123 ViewModel First навигация var viewModel = (ViewModel) Locator.CurrentMutable.GetService(typeof(ViewModel)); viewModel.Title = "New ViewModel"; Router.Navigate.Execute(viewModel);
  • 124. 124 ViewModel First навигация var viewModel = (ViewModel) Locator.CurrentMutable.GetService(typeof(ViewModel)); viewModel.Title = "New ViewModel"; Router.Navigate.Execute(viewModel);
  • 125. 125 ViewModel First навигация var viewModel = (ViewModel) Locator.CurrentMutable.GetService(typeof(ViewModel)); viewModel.Title = "New ViewModel"; Router.Navigate.Execute(viewModel);
  • 126. 126 ViewModel First навигация var viewModel = (ViewModel) Locator.CurrentMutable.GetService(typeof(ViewModel)); viewModel.Title = "New ViewModel"; Router.Navigate.Execute(viewModel);
  • 127. 127 Нет CompositeUI <Grid UseLayoutRounding="True"> <!-- COOLSTUFF: RoutedViewHost RoutedViewHost will take a Router and display whatever ViewModel is at the front of the route stack. --> <rx:RoutedViewHost Router="{Binding Router}" HorizontalContentAlignment="Stretch" VerticalContentAlignment="Stretch" /> </Grid>
  • 128. 128 Нельзя избавиться от Splat IoC В Splat • Получить экземпляр сервиса – аналог ServiceLocator Locator.CurrentMutable.GetService(typeof(Service)) Выход • Реализовать IMutableDependencyResolver (для AutoFac)
  • 129. 129 Недостатки ReactiveUI как MVVM • Нельзя задать конвенцию именования для маппинга View- ViewModel • Нет API, упрощающего задание соответствия View-ViewModel • Splat – это ServiceLocator в чистом виде • Нельзя заменить Splat на свой любимый IoC • Нельзя реализовать CompositeUI • ViewModelFirst навигация неудобная для UWP, WinRT, Windows Phone
  • 131. 131 Достоинства ReactiveUI • Меньше инфраструктурного кода • Повышает читаемость кода, группируя зависимости между свойствами в конструкторе • Решение типовых задач • Подписка на изменение свойства • Подписка на изменение нескольких свойств • Автоматическая проверка состояния команды при: • Изменении любого свойства, входящего в условие • Начале/окончании асинхронной операции, запускаемой командой
  • 132. 132 Главное достоинство Упрощается решение типовых задач разработчика XAML приложений, а не какой-то экзотики!
  • 133. 133 Недостатки Rx и ReactiveUI • Требует аккуратного использования, написать что-то нечитаемое просто • ReactiveUI лучше использовать как дополнение к MVVM фреймворку
  • 134. 134 Полезные ссылки http://reactiveui.net/ https://github.com/reactiveui http://www.magnuslindhe.com/2015/08/one-way-of-cancelling-commands-using-reactiveui/ - отмена асинхронной операции, запущенной командой https://github.com/alexeyzimarev/RxUI6WithAutofac - Rx и Autofac
  • 135. 135 Что дальше делать? • Посмотреть сэмплы Rx или ReactiveUI • Попробовать Rx и ReactiveUI на отдельных формах, если понравится – внедрять: • ReactiveCommand • ObservableForProperty • WhenAny, WhenAnyValue • Select, Merge, Zip

Notes de l'éditeur

  1. Случайно начали использовать Думали что все другие тоже используют Оказалось, что нет
  2. их почти нет, они слабые, искусственные и показывают далеко не все возможности(Codefest приложение). Е
  3. Цель – показать реальные задачи, решаемые через Rx и ReactiveUI
  4. Push уведомления для телефонов – пример реактивного программирования
  5. чтобы не перегружать примеры, считаем что классы реализуют INPC а свойства бросают события об изменении
  6. чтобы не перегружать примеры, считаем что классы реализуют INPC а свойства бросают события об изменении
  7. чтобы не перегружать примеры, считаем что классы реализуют INPC а свойства бросают события об изменении
  8. чтобы не перегружать примеры, считаем что классы реализуют INPC а свойства бросают события об изменении
  9. Чем этот метод лучше – создается Disposable объект подписки Говорить ли о том, как он устроен?
  10. Чем этот метод лучше – создается Disposable объект подписки Говорить ли о том, как он устроен?
  11. Чем этот метод лучше – создается Disposable объект подписки Говорить ли о том, как он устроен?
  12. Чем этот метод лучше – создается Disposable объект подписки Говорить ли о том, как он устроен?
  13. Чем этот метод лучше – создается Disposable объект подписки Говорить ли о том, как он устроен?
  14. Чем этот метод лучше – создается Disposable объект подписки Говорить ли о том, как он устроен?
  15. Для ToDate - аналогично
  16. Для ToDate - аналогично
  17. Это достоинство проявляется только на больших решениях
  18. То же для DelegateCommand из Prism
  19. То же для DelegateCommand из Prism
  20. То же для DelegateCommand из Prism
  21. То же для DelegateCommand из Prism
  22. Команда неактивна и по условию, и во время действия
  23. Демо
  24. Отмена – добавить в Демо
  25. Рассказать зачем нужен ObserveOnDispatcher. Может быть не UI поток из-за таймера
  26. Нет отдельного класса для маппинга, API не самое удобное
  27. Нет отдельного класса для маппинга, API не самое удобное
  28. Неудобно для UWP, WinRT (Противоречит API, можно потерять стек при восстановлении приложения) Нет специальной фабрики ViewModel
  29. Неудобно для UWP, WinRT (Противоречит API, можно потерять стек при восстановлении приложения) Нет специальной фабрики ViewModel
  30. Неудобно для UWP, WinRT (Противоречит API, можно потерять стек при восстановлении приложения) Нет специальной фабрики ViewModel
  31. Неудобно для UWP, WinRT (Противоречит API, можно потерять стек при восстановлении приложения) Нет специальной фабрики ViewModel
  32. Отображается только последняя показанная ViewModel, сделать CompositeUI в принципе нельзя.