Hello Xamarin! MVVM編

この記事は前回の<a href="https://qiita.com/jimadaiki/items/050f87e4e8d20f31e6ee">Hello Xamarin!</a>の続きです。

<h1>目的</h1>
前回の記事で書いた四則演算をするアプリをMVVMで作る。
<h1>MVVMとは</h1>
MVVMとは、Model - View - ViewModel の頭文字を取った設計パターンです。
Modelはビジネスロジックを、Viewは表示、入力を、ViewModelはViewのロジックを担当します。
こちらの記事が詳しいです。
<a href="https://www.atmarkit.co.jp/fdotnet/chushin/greatblogentry_02/greatblogentry_02_01.html">MVVMパターンの常識 ― 「M」「V」「VM」の役割とは？</a>

ViewModelとModelを適切に設計できれば、View(プラットフォーム)に依存しない再利用性が高いコードができあがります。

<h1>できたもの</h1>
![スクリーンショット 2021-01-21 0.41.35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1033036/0a4117fa-df54-2ebf-9a9e-1053edefe752.png)

![スクリーンショット 2021-01-21 0.39.09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1033036/b85d147f-05fc-037e-3743-d2b6afd023fa.png)

前回作ったものに、0で割ろうとすると計算できないようにする機能をつけました。

<h1>コード</h1>
<h3>View(Xaml)</h3>
```Xml:MainPage.Xaml
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="MvvmCalculater.MainPage"
             xmlns:local="clr-namespace:MvvmCalculater">

    <ContentPage.BindingContext>
        <local:MainPageViewModel/>
    </ContentPage.BindingContext>

    <StackLayout Orientation="Vertical" Margin="30,50,0,0">
        <StackLayout Orientation="Horizontal">
            <Entry Text="{Binding LeftNumber}" WidthRequest="100" VerticalOptions="Start"/>
            <Picker ItemsSource="{Binding Operators}" SelectedIndex="{Binding SelectedIndex}"/>
            <Entry Text="{Binding RightNumber}" WidthRequest="100" VerticalOptions="Start" >
            </Entry>
            <Label Text="=" VerticalOptions="Center"/>
            <Label Text="{Binding Answer}" WidthRequest="100" VerticalOptions="Center"/>
        </StackLayout>
        <Button Text="計算" Command="{Binding CalculateCommand}"/>
    </StackLayout> 


</ContentPage>
```
ContentPageにMainPageViewModelをバインドします。

<h3>ViewModel(C#)</h3>
```C#:MainPageViewModel.cs
 public class MainPageViewModel : INotifyPropertyChanged
    {
        enum Operator
        {
            Add = 0,
            Sub = 1,
            Mul = 2,
            Div = 3
        }

        static readonly string[] operators = new[] { "+", "-", "×", "÷" };

        public DelegateCommand CalculateCommand { get; private set; }

        public event PropertyChangedEventHandler PropertyChanged;

        readonly Calculater calculater = new Calculater();

        public double LeftNumber { get; set; }
        
        
        double rightNumber;
        public double RightNumber
        {
            get => rightNumber;
            set
            {
                rightNumber = value;
                UpdateIsValid();
            }
        }

        double answer;      
        public double Answer
        {
            get => answer;
            set
            {
                answer = value;
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Answer)));
            }
        }

        public string[] Operators { get; private set; }

        int selectedIndex;
        public int SelectedIndex
        {
            get => selectedIndex;
            set
            {
                selectedIndex = value;
                UpdateIsValid();
            }
        }

        public bool IsValid { get; private set; } = true;

        public MainPageViewModel()
        {
            Operators = operators;
            CalculateCommand = new DelegateCommand(Calculate, () => IsValid);   
        }

        void Calculate() => Answer = calculater.Calculate(LeftNumber, RightNumber, Operators[SelectedIndex]);

        void UpdateIsValid()
        {
            IsValid = !(selectedIndex == (int)Operator.Div && rightNumber == 0.0);
            CalculateCommand.RaiseCanExecuteChanged();
        }
    }
```

ViewModelのプロパティが変更したことをViewに通知するため、INotifyPropertyChangedを実装し、イベントを発火させます。
今回はプロパティの数が少ないのでそんなに苦ではないですが、数が増えるとsetterで毎回同じようなことを書かないといけないのでだるいです。
そこで普通はINotifyPropertyChangedを実装したベースクラスを作ってそれを継承します。

```C#:BindableBase
public abstract class BindableBase : INotifyPropertyChanged
{
        public event PropertyChangedEventHandler PropertyChanged;

        protected bool SetProperty<T>(ref T oldValue, T value, [CallerMemberName] string propertyName = null)
        {
            if (oldValue.Equals(value)) {
                return false;
            } else {
                oldValue = value;
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
                return true;
            }
        }

        protected virtual void RaisePropertyChanged([CallerMemberName] string propertyName = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }
```

```C#:BindableBaseを継承したViewModel
public class ViewModel : BindableBase
{
        double answer;
        public double Answer
        {
            get => answer;
            set => SetProperty(ref answer, value);
        }
    }
```
setterでSetProperty<T>()を呼ぶことで変更も通知もしてくれます。

<h3>Command</h3>
ボタンを押した時の処理などはICommandインターフェースを実装したCommandクラスにやらせます。actionに処理、canExecuteに実行できるかどうか、デリゲートで渡してあげます。

```C#:DelegateCommand
public class DelegateCommand : ICommand
{
        Action action;
        Func<bool> canExecute;
        public event EventHandler CanExecuteChanged;

        public DelegateCommand(Action action, Func<bool> canExecute)
        {
            this.action = action;
            this.canExecute = canExecute;
        }

        public bool CanExecute(object parameter)
        {
            return canExecute();
        }

        public void Execute(object parameter)
        {
            action?.Invoke();
        }

        public void RaiseCanExecuteChanged() => CanExecuteChanged?.Invoke(this, EventArgs.Empty);  
    }

```
これらのBindableBaseやDelegateCommandといったクラスはPrismというライブラリにあるので、それを素直に使いましょう。

<h3>Model(C#)</h3>
```C#:Calculater.cs
public class Calculater
{
        public double Calculate(double leftNumber, double rightNumber, string ope)
        {
            switch (ope) {
                case "+":
                    return leftNumber + rightNumber;
                case "-":
                    return leftNumber - rightNumber;
                case "×":
                    return leftNumber * rightNumber;
                case "÷":
                    if (rightNumber == 0) {
                        throw new InvalidOperationException();
                    } else {
                        return leftNumber / rightNumber;
                    }

                default:
                    throw new InvalidOperationException();
            }
        }
    }
```

<h1>まとめ</h1>
四則演算アプリをMVVMパターンで作成しました。
前回のコードビハインドでやった場合と比べてコード量がかなり増えました。
小規模のアプリなら素直にコードビハインドで書いた方が楽ですね。
本当は入力チェックをやろうと思ったんですが、WPFと違いINotifyDataErrorInfoを実装するだけじゃできないぽい。(調査不足で違うこと言ってたらすみません)
ダルかったので今回は省きました。

人生で2回目のQiitaですが、記事を書くのってかなり大変なんですね。
知識を共有してくれる世の中の素晴らしい先輩エンジニアの方々には頭があがりません。
いつもありがとうございます。

次はWPFで作ったアプリをXamarinに移行してみたいと思います。(できるかは知らん)