Как приручить реактивное программирование в xaml...

137
Как приручить реактивное программирование в XAML приложениях Денис Цветцих Ice Rock Dev icerockdev.com DotNext Spb 3 июня 2016 dotnext.ru/ spb /

Upload: denis-tsvettsih

Post on 19-Jan-2017

87 views

Category:

Software


3 download

TRANSCRIPT

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

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

Денис ЦветцихIce Rock Dev

icerockdev.com

DotNext Spb3 июня 2016dotnext.ru/spb/

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

2

Кто я?• 7+ лет .NET• Разработка корпоративных приложений• Люблю хипстерские библиотеки

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

3

Почему я здесь?• 6 лет назад приручил Rx• 3 года назад приручил ReactiveUI

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

4

ReactiveUI – ни одного поста на хабре

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

5

Сэмплы не показательныRx – 101 пример задач, где можно обойтись без

Rx

ReactiveUI – единственный жизненный сэмпл – поиск (на главной странице)

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

6

О чем мы поговорим?• Что такое реактивное программирование• Примеры задач из продакшен проектов,

которые проще решаются с помощью Rx и ReactiveUI

• Антипаттерны: как ReactiveUI лучше не использовать

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

ЧТО ТАКОЕ РЕАКТИВНОЕ ПРОГРАММИРОВАНИЕ

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

8

Что такое реактивное программирование?

Реактивное программирование – это парадигма программирования, ориентированная на потоки данных и распространение изменений. (Википедия)

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

9

Парадигма программированияПарадигма программирования – это система идей и понятий, определяющих стиль написания компьютерных программ.

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

10

Императивная программаvar A = 10;var B = A + 1;

// Чему равно В?11

A = A + 1;

// А теперь чему равно В?11

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

11

Реактивная программаvar A = 10;var B <- A + 1; <- оператор «судьбы»

// Чему равно В?11

A = A + 1;

// А теперь чему равно В?12

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

12

Реактивное программированиеРеактивное программирование – это парадигма программирования, ориентированная на потоки данных и распространение изменений.

Поток данных – последовательность значений переменной или свойства класса.

Распространение изменений – уведомления «заинтересованных» об изменениях.

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

13

PullPull – класс A взаимодействует с классом B и вытягивает из него необходимые данные

Class A Class B

GetData()

Interacts with

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

14

PushPush – класс B самостоятельно выталкивает данные классу A, как только они становятся доступны.

Class A

ProcessData()

Class BReacts on

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

РЕАКТИВНОЕ ПРОГРАММИРОВАНИЕ НА C#

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

16

Реактивное программирование на .NET

• Reactive Extensions (Rx)• Эрик Мейер из MS Research

• ReactiveUI• Разработана в MS Research• библиотека на базе Rx для создания элегантных UI

для всех XAML платформ

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

17

Внутри .NETpublic interface IObservable<T>{

IDisposable Subscribe(IObserver<T> observer);}

public interface IObserver<T>{

void OnNext(T value);void OnCompleted();void OnError(Exception error);

}

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

18

Внутри .NETpublic interface IObservable<T>{

IDisposable Subscribe(IObserver<T> observer);}

public interface IObserver<T>{

void OnNext(T value);void OnCompleted();void OnError(Exception error);

}

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

19

Внутри .NETpublic interface IObservable<T>{

IDisposable Subscribe(IObserver<T> observer);}

public interface IObserver<T>{

void OnNext(T value);void OnCompleted();void OnError(Exception error);

}

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

20

Внутри .NETpublic interface IObservable<T>{

IDisposable Subscribe(IObserver<T> observer);}

public interface IObserver<T>{

void OnNext(T value);void OnCompleted();void OnError(Exception error);

}

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

21

Внутри .NETpublic interface IObservable<T>{

IDisposable Subscribe(IObserver<T> observer);}

public interface IObserver<T>{

void OnNext(T value);void OnCompleted();void OnError(Exception error);

}

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

22

Оператор судьбы через Rxvar A = new Subject<int>();

// B <- A + 1var B = A.Select(a => a + 1);

A.OnNext(10);

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

23

Оператор судьбы через Rxvar A = new Subject<int>();

// B <- A + 1var B = A.Select(a => a + 1);

A.OnNext(10);

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

24

Оператор судьбы через Rxvar A = new Subject<int>();

// B <- A + 1var B = A.Select(a => a + 1);

A.OnNext(10);

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

25

Оператор судьбы через Rxvar A = new Subject<int>();

// B <- A + 1var B = A.Select(a => a + 1);

A.OnNext(10);

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

26

Observable.Select – проекция

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

27

Observable.Merge – слияние

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

КАКИЕ ЗАДАЧИ МОЖНО РЕШАТЬ

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

29

Пример

Приложение для поиска по материалам конференции DotNext

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

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); } }}

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

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); } }}

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

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); } }}

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

33

Для читабельности примеровpublic class Filter{ public DateTime FromDate { get; set; }

public DateTime ToDate { get; set; } }

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

34

Задача 1: подписка на события об изменении свойств фильтра

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

35

Обычная подписка на событие PropertyChanged

var filter = new Filter();

// Подпискаfilter.PropertyChanged += OnPropertyChanged;

// Реакция на событиеprivate void OnPropertyChanged (object sender, PropertyChangedEventArgs e){ }

// Отпискаfilter.PropertyChanged -= OnPropertyChanged;

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

36

Обычная подписка на событие PropertyChanged

var filter = new Filter();

// Подпискаfilter.PropertyChanged += OnPropertyChanged;

// Реакция на событиеprivate void OnPropertyChanged (object sender, PropertyChangedEventArgs e){ }

// Отпискаfilter.PropertyChanged -= OnPropertyChanged;

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

37

var filter = new Filter();

// Подпискаfilter.PropertyChanged += OnPropertyChanged;

// Реакция на событиеprivate void OnPropertyChanged (object sender, PropertyChangedEventArgs e){ }

// Отпискаfilter.PropertyChanged -= OnPropertyChanged;

Обычная подписка на событие PropertyChanged

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

38

Обычная подписка на событие PropertyChanged

var filter = new Filter();

// Подпискаfilter.PropertyChanged += OnPropertyChanged;

// Реакция на событиеprivate void OnPropertyChanged (object sender, PropertyChangedEventArgs e){ }

// Отпискаfilter.PropertyChanged -= OnPropertyChanged;

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

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

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

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

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

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

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

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

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

43

Rx подписка на событие PropertyChangedfilter = new Filter();

// Подписка IDisposable subscription = Observable.FromEventPattern<PropertyChangedEventArgs> (filter, "PropertyChanged") .Subscribe(OnPropertyChanged);

// Реакция на событиеprivate void OnPropertyChanged (EventPattern<PropertyChangedEventArgs> eventPattern){ //eventPattern.Sender и eventPattern.EventArgs }

// Отписка subscription.Dispose();

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

44

Rx подписка на событие PropertyChangedfilter = new Filter();

// Подписка IDisposable subscription = Observable.FromEventPattern<PropertyChangedEventArgs> (filter, "PropertyChanged") .Subscribe(OnPropertyChanged);

// Реакция на событиеprivate void OnPropertyChanged (EventPattern<PropertyChangedEventArgs> eventPattern){ //eventPattern.Sender и eventPattern.EventArgs }

// Отписка subscription.Dispose();

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

45

Достоинства Rx подписки• Подписка инкапсулируется в IDisposable

объекте, просто сделать отписку• Подписка – это поток данных, над которым

можно выполнять операции

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

46

Задача 2: подписка на изменение свойства FromDate

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

47

Обычная подписка на изменение свойства FromDate

Filter filter = new Filter();

filter.PropertyChanged += OnPropertyChanged;

private void OnPropertyChanged (object sender, PropertyChangedEventArgs eventArgs){ if (eventArgs.PropertyName == “FromDate") { // Действия }}

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

48

Обычная подписка на изменение свойства FromDate

Filter filter = new Filter();

filter.PropertyChanged += OnPropertyChanged;

private void OnPropertyChanged (object sender, PropertyChangedEventArgs eventArgs){ if (eventArgs.PropertyName == “FromDate") { // Действия }}

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

49

Обычная подписка на изменение свойства FromDate

Filter filter = new Filter();

filter.PropertyChanged += OnPropertyChanged;

private void OnPropertyChanged (object sender, PropertyChangedEventArgs eventArgs){ if (eventArgs.PropertyName == “FromDate") { // Действия }}

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

50

ReactiveUI подписка на изменение свойства FromDate

var filter = new Filter();

var subscription = filter .ObservableForProperty(f => f.FromDate) .Subscribe(FromDateChanged);

private void FromDateChanged (IObservedChange<Filter, DateTime> change){ // Действия}

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

51

ReactiveUI подписка на изменение свойства FromDate

var filter = new Filter();

var subscription = filter .ObservableForProperty(f => f.FromDate) .Subscribe(FromDateChanged);

private void FromDateChanged (IObservedChange<Filter, DateTime> change){ // Действия}

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

52

ReactiveUI подписка на изменение свойства FromDate

var filter = new Filter();

var subscription = filter .ObservableForProperty(f => f.FromDate) .Subscribe(FromDateChanged);

private void FromDateChanged (IObservedChange<Filter, DateTime> change){ // Действия}

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

53

ReactiveUI подписка на изменение свойства FromDate

var filter = new Filter();

var subscription = filter .ObservableForProperty(f => f.FromDate) .Subscribe(FromDateChanged);

private void FromDateChanged (IObservedChange<Filter, DateTime> change){ // Действия}

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

54

Достоинства ObservableForProperty

• Не нужно проверять, что обрабатываем событие об изменении нужного свойства

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

55

Задача 3: действия при изменении значений свойств

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

56

Добавим выбор страны и города

Добавим в фильтр:• Список стран и городов• При выборе страны актуализируем список

городов

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

57

Валидация выбранного периодаУсловие: FromDate не превосходит ToDate

Если FromDate > ToDate, тогда ToDate приравниваем к FromDate

Если ToDate < FromDate, тогда FromDate приравниваем к ToDate

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

58

Обычное решениеprivate DateTime _fromDate;

public DateTime FromDate{ get { return _fromDate; } set { _fromDate = value; OnPropertyChanged();

if (_fromDate > ToDate) ToDate = _fromDate; }}

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

59

Обычное решениеprivate DateTime _fromDate;

public DateTime FromDate{ get { return _fromDate; } set { _fromDate = value; OnPropertyChanged();

if (_fromDate > ToDate) ToDate = _fromDate; }}

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

60

ObservableForPropertyprivate 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

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

61

ObservableForPropertyprivate 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

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

62

ObservableForPropertyprivate 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

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

63

ObservableForPropertyprivate 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

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

64

ObservableForPropertyprivate 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;

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

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); } } }

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

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; }

}

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

67

Достоинства ObservableForProperty

• Удаление логики из сеттеров• Делает код на порядок более читаемым,

собирая код зависимости между свойствами в конструкторе

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

68

Задача 4: мониторинг одновременно нескольких свойств

Поля фильтра: • От• До• Страна • Город

При изменении любого – обновляем данные

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

69

ViewModel с фильтромpublic class ViewModel { public Filter Filter { get; set; }

public ViewModel() { Filter = new Filter();

// Подписка на изменение свойств фильтра

}}

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

70

ViewModel с фильтромpublic class ViewModel { public Filter Filter { get; set; }

public ViewModel() { Filter = new Filter();

// Подписка на изменение свойств фильтра

}}

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

71

ViewModel с фильтромpublic class ViewModel { public Filter Filter { get; set; }

public ViewModel() { Filter = new Filter();

// Подписка на изменение свойств фильтра

}}

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

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(); }}

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

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(); }}

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

74

ReactiveUI решениеpublic ViewModel(){ Filter = new Filter();

// Подписка на изменение свойств фильтра

_subscription = Filter.WhenAnyValue(

f => f.FromDate, f => f.ToDate, f => f.SelectedCountry, f => f.SelectedCity) .Subscribe(_ => Refresh());}

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

75

ReactiveUI решениеpublic ViewModel(){ Filter = new Filter();

// Подписка на изменение свойств фильтра

_subscription = Filter.WhenAnyValue(

f => f.FromDate, f => f.ToDate, f => f.SelectedCountry, f => f.SelectedCity) .Subscribe(_ => Refresh());}

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

76

ReactiveUI решениеpublic ViewModel(){ Filter = new Filter();

// Подписка на изменение свойств фильтра

_subscription = Filter.WhenAnyValue(

f => f.FromDate, f => f.ToDate, f => f.SelectedCountry, f => f.SelectedCity) .Subscribe(_ => Refresh());}

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

77

ReactiveUI решениеpublic ViewModel(){ Filter = new Filter();

// Подписка на изменение свойств фильтра

_subscription = Filter.WhenAnyValue(

f => f.FromDate, f => f.ToDate, f => f.SelectedCountry, f => f.SelectedCity) .Subscribe(_ => Refresh());}

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

78

Достоинства WhenAnyValue• Подписываемся только на нужные нам

свойства• Можно подписываться на изменения

вложенных свойств • Obj.Deep1.Deep2.Deep3…

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

79

Задача 5: проверка CanExecute команды

• Добавляем кнопку «Обновить» на фильтр• Страна и город – обязательные поля

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

80

RelayCommand из MvvmLightpublic RelayCommand RefreshCommand { get; private set; }

public Filter(){ RefreshCommand = new RelayCommand( OnRefresh, () => SelectedCity != null && SelectedCountry !=

null);}

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

81

RelayCommand из MvvmLightpublic RelayCommand RefreshCommand { get; private set; }

public Filter(){ RefreshCommand = new RelayCommand( OnRefresh, () => SelectedCity != null && SelectedCountry !=

null);}

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

82

RelayCommand из MvvmLightpublic RelayCommand RefreshCommand { get; private set; }

public Filter(){ RefreshCommand = new RelayCommand( OnRefresh, () => SelectedCity != null && SelectedCountry !=

null);}

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

83

RelayCommand из MvvmLightpublic RelayCommand RefreshCommand { get; private set; }

public Filter(){ RefreshCommand = new RelayCommand( OnRefresh, () => SelectedCity != null && SelectedCountry !=

null);}

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

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(); }}

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

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; }

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

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; }

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

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; }

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

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; }

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

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; }

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

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();

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

91

Достоинства ReactiveCommand • Не нужны «подергивания» в сеттерах• При изменения условия нельзя забыть убрать

или добавить «подергивание»

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

92

Задача 6: команда запускает асинхронное действие

• При выполнении команды – вызов вебсервиса• Пока не получим ответ от сервиса – команду

нельзя выполнить

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

93

Асинхронное действие RelayCommand

public bool IsBusy { get; set; } //RefreshCommand.RaiseCanExecuteChanged();

public Filter(Task refreshTask){ RefreshTask = refreshTask;

RefreshCommand = new RelayCommand(OnRefresh, () => SelectedCity != null && SelectedCountry != null && !IsBusy);}

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

94

Асинхронное действие RelayCommand

public bool IsBusy { get; set; } //RefreshCommand.RaiseCanExecuteChanged();

public Filter(Task refreshTask){ RefreshTask = refreshTask;

RefreshCommand = new RelayCommand(OnRefresh, () => SelectedCity != null && SelectedCountry != null && !IsBusy);}

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

95

Асинхронное действие RelayCommand

public bool IsBusy { get; set; } //RefreshCommand.RaiseCanExecuteChanged();

public Filter(Task refreshTask){ RefreshTask = refreshTask;

RefreshCommand = new RelayCommand(OnRefresh, () => SelectedCity != null && SelectedCountry != null && !IsBusy);}

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

96

Асинхронное обновлениеprivate async void OnRefresh(){ IsBusy = true;

await RefreshTask; // запрос на сервер

IsBusy = false;}

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

97

Асинхронное обновлениеprivate async void OnRefresh(){ IsBusy = true;

await RefreshTask; // запрос на сервер

IsBusy = false;}

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

98

Асинхронное обновлениеprivate async void OnRefresh(){ IsBusy = true;

await RefreshTask; // запрос на сервер

IsBusy = false;}

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

99

ReactiveAsyncCommandpublic 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;}

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

100

ReactiveAsyncCommandpublic 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;}

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

101

ReactiveAsyncCommandpublic 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;}

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

102

ReactiveAsyncCommandpublic 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;}

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

103

ReactiveAsyncCommandpublic 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;}

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

104

Демо

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

105

Достоинства ReactiveAsyncCommand

• Не нужно отдельного свойства для мониторинга начала/конца операции

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

106

Задача 7: поиск по подстроке

Вместо фильтра делаем поискЕсли длина строки < 3, то ждем 0,5 сек перед

поискомЕсли длина строки >= 3, то ждем 0,25 сек перед

поиском

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

107

Поиск по строкеpublic class ViewModel{ public Filter Filter { get; set; }

public string SearchText { get; set; }}

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

108

Поиск по строкеpublic class ViewModel{ public Filter Filter { get; set; }

public string SearchText { get; set; }}

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

109

Поиск по строкеpublic class ViewModel{ public Filter Filter { get; set; }

public string SearchText { get; set; }}

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

110

Where, Throttlevar 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));

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

111

Where, Throttlevar 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));

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

112

Where, Throttlevar 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));

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

113

Where, Throttlevar 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));

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

114

Where, Throttlevar 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));

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

115

Where, Throttlevar 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));

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

116

Merge, ObserveOnDispatchervar resultObservable = firstRule.Merge(secondRule);

resultObservable .ObserveOnDispatcher() .Subscribe(Refresh);

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

117

Merge, ObserveOnDispatchervar resultObservable = firstRule.Merge(secondRule);

resultObservable .ObserveOnDispatcher() .Subscribe(Refresh);

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

118

Merge, ObserveOnDispatchervar resultObservable = firstRule.Merge(secondRule);

resultObservable .ObserveOnDispatcher() .Subscribe(Refresh);

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

119

Достоинства• На порядок меньшее кода, чем написали бы

для обычного решения• Код простой и читаемый

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

АНТИПАТТЕРН: REACTIVEUI КАК MVVM

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

121

View – ViewModel маппингprivate void RegisterParts(IMutableDependencyResolver

dependencyResolver){ dependencyResolver.RegisterConstant(this, typeof(IScreen));

dependencyResolver.Register( () => new WelcomeView(), typeof(IViewFor<WelcomeViewModel>));}

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

122

View – ViewModel маппингprivate void RegisterParts(IMutableDependencyResolver

dependencyResolver){ dependencyResolver.RegisterConstant(this, typeof(IScreen));

dependencyResolver.Register( () => new WelcomeView(), typeof(IViewFor<WelcomeViewModel>));}

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

123

ViewModel First навигацияvar viewModel = (ViewModel) Locator.CurrentMutable.GetService(typeof(ViewModel));

viewModel.Title = "New ViewModel";Router.Navigate.Execute(viewModel);

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

124

ViewModel First навигацияvar viewModel = (ViewModel) Locator.CurrentMutable.GetService(typeof(ViewModel));

viewModel.Title = "New ViewModel";Router.Navigate.Execute(viewModel);

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

125

ViewModel First навигацияvar viewModel = (ViewModel) Locator.CurrentMutable.GetService(typeof(ViewModel));

viewModel.Title = "New ViewModel";Router.Navigate.Execute(viewModel);

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

126

ViewModel First навигацияvar viewModel = (ViewModel) Locator.CurrentMutable.GetService(typeof(ViewModel));

viewModel.Title = "New ViewModel";Router.Navigate.Execute(viewModel);

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

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>

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

128

Нельзя избавиться от Splat IoCВ Splat• Получить экземпляр сервиса – аналог

ServiceLocator Locator.CurrentMutable.GetService(typeof(Service))Выход• Реализовать IMutableDependencyResolver (для AutoFac)

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

129

Недостатки ReactiveUI как MVVM

• Нельзя задать конвенцию именования для маппинга View-ViewModel

• Нет API, упрощающего задание соответствия View-ViewModel

• Splat – это ServiceLocator в чистом виде• Нельзя заменить Splat на свой любимый IoC• Нельзя реализовать CompositeUI• ViewModelFirst навигация неудобная для UWP, WinRT,

Windows Phone

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

ЗАКЛЮЧЕНИЕ

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

131

Достоинства ReactiveUI• Меньше инфраструктурного кода• Повышает читаемость кода, группируя

зависимости между свойствами в конструкторе• Решение типовых задач• Подписка на изменение свойства• Подписка на изменение нескольких свойств• Автоматическая проверка состояния команды при:• Изменении любого свойства, входящего в условие• Начале/окончании асинхронной операции, запускаемой

командой

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

132

Главное достоинствоУпрощается решение типовых задач

разработчика XAML приложений, а не какой-то экзотики!

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

133

Недостатки Rx и ReactiveUI• Требует аккуратного использования, написать

что-то нечитаемое просто• ReactiveUI лучше использовать как

дополнение к MVVM фреймворку

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

134

Полезные ссылкиhttp://reactiveui.net/https://github.com/reactiveuihttp://www.magnuslindhe.com/2015/08/one-way-of-cancelling-commands-using-reactiveui

/ - отмена асинхронной операции, запущенной командойhttps://github.com/alexeyzimarev/RxUI6WithAutofac - Rx и Autofac

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

135

Что дальше делать?• Посмотреть сэмплы Rx или

ReactiveUI• Попробовать Rx и ReactiveUI на

отдельных формах, если понравится – внедрять: • ReactiveCommand • ObservableForProperty• WhenAny, WhenAnyValue• Select, Merge, Zip

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

136

История успеха – GitHub Desktop

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

137

Спасибо за внимание

Вопросы?

Денис Цветцих[email protected]