Help us understand the problem. What is going on with this article?

【Xamarin】Xamarin×MVVM

More than 1 year has passed since last update.

はじめに

最近趣味でXamarinを触り始めました。
普段の仕事ではSwiftとJavaで別々にスマホアプリを作ってます。
C#初心者です。

XamarinとMVVM

Xamarinに関する記事をネットで読み漁っていると
MVVMという単語によく出会います。

私は、
「MVVMってModel - View - ViewModelの頭文字をとったものでしょ?」
くらいの知識しかなかったです。
でも最近よく聞くなぁ、と思いながら放置していました。
この機会にXamarinとMVVMを一気に勉強しよう!と思い、記事にしてみました。

参考にさせて頂いたサイト
http://oxamarin.com/mvvm/

MVVMを使わない実装

ボタンを押すとラベルに表示された数字を1ずつ増やすプログラムを
XamarinでMVVMを使わずに実装してみます。

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"
             xmlns:local="clr-namespace:XamvvmPractice"
             x:Class="XamvvmPractice.MainPage">
    <StackLayout
        HorizontalOptions="Center"
        VerticalOptions="Center">
        <Button Text="カウントアップ"
                Clicked="Button_OnClicked" />
        <Label x:Name="lblCount"
               Text="0"
               HorizontalTextAlignment="Center" />
    </StackLayout>
</ContentPage>
MainPage.xaml.cs
using Xamarin.Forms;

namespace XamvvmPractice
{
    public partial class MainPage : ContentPage
    {
        public MainPage()
        {
            InitializeComponent();
        }

        void Button_OnClicked(object sender, ClickedEventArgs e)
        {
            var current = int.Parse(lblCount.Text);
            lblCount.Text = (current + 1).ToString();
        }
    }
}

まさに私がXamarinでよくやる実装です。
MainPage.xaml.csにイベントを定義し、それをコントロールに割り当てています。

ちなみに、プロジェクト名はXamarinをMVVMで実装する練習なので
安直にXamvvmPracticeとしました。ネーミングセンス磨きたいです。

この実装、参考サイトによると

プログラミングを覚えたて?であればこのように、コントロールにイベントを割り当ててこの動作を実現させると思います。しかし、クリックイベントを使った瞬間にMVVMではなくなるわけです。

とのこと。
「クリックイベントを使ったらMVVMではない」とは?

どういうことか?というと、レイアウト部分に関する事はView、つまりはXamlで完結させたい訳です。クリックイベントの紐づき先はXamlのコードビハインドであり、レイアウトとそのコードビハインドの依存関係が生まれてしまいます。このように、UIとロジックが密結合の状態であると自動テストも憚られてしまいます。

コードビハインドとは?
https://ufcpp.net/study/dotnet/wpf_xamlcode.html#codebehind

ここでは、MainPage.xamlがレイアウト、MainPage.xaml.csがコードビハインドになります。(上のサイトと微妙にコードビハインドの意味が違う気がしますが、ここはニュアンスで捉えておくことにします)
MVVMを使わない実装だと、レイアウトとコードビハインドに依存関係が生じてしまいます。
規模の小さなプログラムではそんなに問題なさそうですが、規模が大きくなってくるとレイアウト部分がデザイナ待ちになったり、コードビハインドで何を実装したのか分かりにくくなりそうです。

これを

  • ロジック・・・Model
  • UI・・・View
  • イベント処理・・・ViewModel

に完全に分業してしまうのがMVVMということですかね。

MVVMを使った実装

では、先ほどと同じプログラムをMVVMを使って実装してみます。
プロジェクトに

  • BasePages
    • ViewModelBase.cs
  • Commands
    • CountUpCommand.cs
  • Models
    • Count.cs
  • ViewModels
    • MainViewModel.cs

を追加します。
↓のようになると思います。
プロジェクト構成

ワクワクしてきました。では実装に入ります。

参考サイトと順序が違いますが、まずはViewModelBaseから

ViewModelBase.cs

ViewModelBase.cs
using System.ComponentModel;

namespace XamvvmPractice.BasePages
{
    /// <summary>
    /// ViewModelの基本クラス
    /// </summary>
    internal class ViewModelBase : INotifyPropertyChanged
    {
        /// <summary>
        /// プロパティの変更があった時に発行される
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;

        /// <summary>
        /// PropertyChangedイベントを発行する
        /// </summary>
        /// <param name="propertyName">Property name</param>
        protected virtual void OnPropertyChanged(string propertyName)
        {
            this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

ここで全ViewModelに共通となる、プロパティ値に変更があった時に呼ばれるOnPropertyChangedを実装しています。
ViewModelはこのViewModelBaseを継承して実装していくことになります。

次にMainViewModelとCountUpCommandを実装します。

MainViewModel.cs

MainViewModel.cs
using System.Windows.Input;
using XamvvmPractice.BasePages;
using XamvvmPractice.Models;
using XamvvmPractice.Commands;

namespace XamvvmPractice.ViewModels
{
    internal class MainViewModel : ViewModelBase
    {
        #region メンバ変数
        private int _currentNumber;
        #endregion

        #region プロパティ
        public ICommand CountUpCommand { get; }
        public int CurrentNumber
        {
            get { return _currentNumber; }
            set
            {
                this._currentNumber = value;
                this.OnPropertyChanged(nameof(CurrentNumber));
            }
        }
        #endregion

        #region コンストラクタ
        public MainViewModel()
        {
            CountUpCommand = new CountUpCommand(Increment);
        }
        #endregion

        #region メソッド
        private void Increment()
        {
            CurrentNumber = Count.Increment(CurrentNumber);
        }
        #endregion

    }
}

CountUpCommand.cs

CountUpCommand.cs
using System;
using System.Windows.Input;

namespace XamvvmPractice.Commands
{
    internal class CountUpCommand : ICommand
    {
        #region メンバ変数
        private readonly Action _action;
        #endregion

        #region イベント
        public event EventHandler CanExecuteChanged;
        #endregion

        #region コンストラクタ
        public CountUpCommand(Action action)
        {
            this._action = action;
        }
        #endregion

        #region メソッド
        /// <summary>
        /// ボタンクリック時に呼び出される
        /// [メモ] 今回は常にtrueを返しているが、ここで実行可能かどうか判断するロジックを組み込むと便利そう
        /// </summary>
        /// <returns</returns>
        /// <param name="parameter"></param>
        public bool CanExecute(object parameter)
        {
            return true;
        }

        /// <summary>
        /// CanExecuteがtrueだったら呼び出される
        /// </summary>
        /// <param name="parameter"></param>
        public void Execute(object parameter)
        {
            this._action();
        }
        #endregion
    }
}

Countが未実装なのでCount.Incrementにエラーが出ますが、このあと実装するのでとりあえず気にしないでおきます。

MainViewModelのプロパティCurrentNumberのsetterで

MainViewModel.cs
this.OnPropertyChanged(nameof(CurrentNumber));

としてプロパティ値が変更されたことを通知し、PropertyChangedイベントを発行しています。
何となくMVVMらしさが出てきた気がします。

次はCountを実装します。

Count.cs

Count.cs
namespace XamvvmPractice.Models
{
    internal class Count
    {
        internal static int Increment(int number)
        {
            return number + 1;
        }
    }
}

このCountがModelにあたる部分です。
今回はカウントを上げる処理だけなので寂しい感じになっていますが、実際にアプリを作る時には、Modelにあたる部分が一番膨らむのかなと想像しています。

最後に、MVVMを使わない実装になっていたMainPage.xamlをViewModelと紐付けて、不要になったMainPage.xaml.csのイベント処理を消します。

MainPage.xaml

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"
             xmlns:local="clr-namespace:XamvvmPractice"
             xmlns:vm="clr-namespace:XamvvmPractice.ViewModels"
             x:Class="XamvvmPractice.MainPage">

    <!-- ViewModelのクラスを指定して画面にバインド -->
    <ContentPage.BindingContext>
        <vm:MainViewModel />
    </ContentPage.BindingContext>

    <!-- レイアウト -->
    <StackLayout
        HorizontalOptions="Center"
        VerticalOptions="Center">
        <!-- MainViewModelのCountUpCommandをバインド -->
        <Button Text="カウントアップ"
                Command="{Binding CountUpCommand}" />
        <!-- MainViewModelのCurrentNumberをバインド -->
        <Label x:Name="lblCount"
               Text="{Binding CurrentNumber}"
               HorizontalTextAlignment="Center" />
    </StackLayout>
</ContentPage>

MainPage.xaml.cs

MainPage.xaml.cs
using Xamarin.Forms;

namespace XamvvmPractice
{
    public partial class MainPage : ContentPage
    {
        public MainPage()
        {
            InitializeComponent();
        }
    }
}

これで実行するとMVVMを使わない実装と同様に動作することが確認できると思います。

まとめ

今回参考にさせて頂いたプログラムでは

  • Model(ロジック)
    • Count.cs
  • View(UI)
    • MainPage.xaml
    • MainPage.xaml.cs
  • ViewModel(イベント処理)
    • MainViewModel.cs
    • CountUpCommand.cs

として、ボタンを押すとラベルに表示された数字を1ずつ増やすプログラムを
Xamarin × MVVM で実装していました。

プログラムの保守性/可読性が上がる、テストがしやすいなどのメリットや
コーディング量が増える、動作が重くなるなどデメリットもあるようですが、
個人的な感想としてはなんとなくスマートに書けている感じがして好きです。笑

ネットで調べているとXamarin向けのMVVMフレームワークなるものもあるんですね。
Prismくらい触ってみたいなぁと思っています。

拙文でしたがご覧頂いた方々、ありがとうございました。
不備等ございましたらコメント頂けますと幸いです。

y-mimura
主にAndroid/iOSアプリ設計開発をしているフリーランスエンジニアです。
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした