Trong bài trước (https://ngocminhtran.com/2019/07/20/tai-nguyen-resources-va-ket-buoc-du-lieu-data-binding-trong-xamarin-forms/ ) chúng ta đã làm quen với kết buộc dữ liệu và cũng đã tạo một ứng dụng kết nối cơ sở dữ liệu SQLite (https://ngocminhtran.com/2019/08/16/xamarin-forms-va-sqlite/ ). Trong bài này, chúng ta sẽ tìm hiểu chi tiết hơn về các đối tượng InotifyPropertyChanged, ObservableCollection và mô hình MVVM.

Giao diện INotifyPropertyChanged

Khi bàn về chủ đề data binding, chúng ta đã kết buộc dữ liệu từ đối tượng Person:


class Person

{

   private string fullName;

   public string FullName

   {

     get

     {

      return fullName;

     }

     set

    {

     fullName = value;

    }

  }

  private DateTime dateOfBirth;
 
  public DateTime DateOfBirth

  {

   get

    {

     return dateOfBirth;

    }

    set

    {

     dateOfBirth = value;

    }

  }

  private string address;

  public string Address

  {

   get

  {

    return address;

  }

  set

  {

   address = value;

  }

 }

}

Đến các điều khiển trên giao diện ứng dụng:


<Label Text="Name:" />

<Entry Text="{Binding FullName}"/>

<Label Text="Date of birth:"/>

<DatePicker Date="{Binding DateOfBirth, Mode=TwoWay}" />

<Label Text="Address:"/>

<Entry Text="{Binding Address}"/>

Ở đây, các thuộc tính đối tượng Person (FullName, DateOfBirth, Address) kết buộc với các thuộc tính của các điều khiển (Text, Date). Các thuộc tính của điều khiển kết buộc đến các thuộc tính của đối tượng gọi là các thuộc tính có thể kết buộc (bindable properties).

Các thuộc tính có thể kết buộc sẽ tự động cập nhật giá trị của thuộc tính đối tượng được kết buộc và sẽ tự động làm mới lại nội dung trong giao diện khi đối tượng được cập nhật. Tuy nhiên, quá trình tự động làm mới nội dung này chỉ được thực hiện khi đối tượng thực thi giao diện  InotifyPropertyChanged. Lớp Person thay đổi lại như sau:


class Person : INotifyPropertyChanged

{

  private string fullName;

  public string FullName

  {

   get

   {

    return fullName;

   }

  set

  {

    fullName = value;

    OnPropertyChanged();

   }

  }

  private DateTime dateOfBirth;

  public DateTime DateOfBirth

  {

   get

   {

    return dateOfBirth;

   }

   set

   {

    dateOfBirth = value;

    OnPropertyChanged();

   }

  } 

  private string address;

  public string Address

  {

   get

   {

    return address;

   }

   set

   {

    address = value;
 
    OnPropertyChanged();

   }

 }

 public event PropertyChangedEventHandler PropertyChanged;

 private void OnPropertyChanged([CallerMemberName] string propertyName = null)

 {

  PropertyChanged?.Invoke(this,new PropertyChangedEventArgs(propertyName));

 }

}

Lưu ý, chúng ta phải khai báo hai namespace:


using System.Runtime.CompilerServices;

using System.ComponentModel;

Khi thực thi giao diện InotifyPropertyChanged, các thuộc tính của đối tượng Person sẽ phát sinh thông báo thay đổi thông qua sự kiện PropertyChanged. Các thuộc tính của điều khiển sẽ nhận thông báo khi có sự thay đổi và cập nhật lại nội dung trên giao diện.

Đối tượng ObservableCollection

Chúng ta đã làm việc với chỉ một thực thể (instance) Person trong minh họa trên, nhưng trong trường hợp phổ biến hơn, chúng ta có thể làm việc với một danh sách các thực thể Person. Lúc này, đối tượng hay collection ObservableCollection sẽ được sử dụng. Trước khi sử dụng đối tượng này, chúng ta cần khai báo namespace:


using System.Collections.ObjectModel;

Đoạn mã sau minh họa cách dùng đối tượng ObservableCollection trong data binding:


Person person1 = new Person { FullName = "Alessandro" };

Person person2 = new Person { FullName = "James" };

Person person3 = new Person { FullName = "Jacqueline" };

var people = new ObservableCollection<Person>() { person1, person2,person3 };

this.BindingContext = people;

3 thực thể person1, person2 và person3 được lưu trong đối tượng ObservableCollection và đối tượng này được gán đến thuộc tính BindingContext thông qua biến people.

Kế tiếp, để hiển thị dữ liệu từ đối tượng ObservableCollection, chúng ta dùng điều khiển có thể chứa dữ liệu dạng danh sách, ví dụ dùng ListView (https://docs.microsoft.com/en-us/xamarin/xamarin-forms/user-interface/listview/ ) như sau:


<StackLayout Orientation="Vertical" Padding="20">

  <Grid>

   <ListView x:Name="PeopleList" ItemsSource="{Binding}">

     <ListView.ItemTemplate>

       <DataTemplate>

        <EntryCell Label="Full name:" Text="{Binding FullName}"/>

       </DataTemplate>

     </ListView.ItemTemplate>

    </ListView>

   </Grid>

</StackLayout>

Kết quả:

Mô hình Model-View-ViewModel (MVVM)

Là mô hình cho phép tách biệt dữ liệu (Model), mã thực thi (logic hay ViewModel) và giao diện người dùng (View). Trong mô hình truyền thống, chúng ta có thể xử lý sự kiện Click và viết mã thực thi trực tiếp trên một Button nhưng trong mô hình MVVM không cho phép điều này. Trong mô hình MVVM, các điều khiển như Button, ListView, SearchBar, v.v. không thể kết buộc trực tiếp đến dữ liệu mà phải thông qua thuộc tính Command – là một thuộc tính kiểu ICommand.

Để dễ hình dung, chúng ta sẽ tạo một ứng dụng đơn giản tên MVVMSample. Tiếp theo, chúng ta sẽ tạo một thư mục tên Model trong dự án MVVMSample (giả sử chúng ta có hai dự án MVVMSample và MVVMSample.Android) và thêm một lớp tên Person.cs đến thư mục này

Nội dung lớp Person có thể copy từ phần trên. Kế tiếp, chúng ta sẽ tạo một thư mục khác trong dự án MVVMSample tên ViewModel và thêm một lớp tên PersonViewModel.cs đến thư mục này:

Nội dung lớp PersonViewModel như sau:


class PersonViewModel

{

  public ObservableCollection<Person> People { get; set; }

  public Person SelectedPerson { get; set; }

  public ICommand AddPerson { get; set; }

  public ICommand DeletePerson { get; set; }

  public PersonViewModel()

  {

   this.People = new ObservableCollection<Person>();

   // dữ liệu mẫu

   Person person1 = new Person

   {

     FullName = "Alessandro",

     Address = "Italy",

     DateOfBirth = new DateTime(1977, 5, 10)

   };

   Person person2 = new Person

   {

     FullName = "James",

     Address = "United States",

     DateOfBirth = new DateTime(1960, 2, 1)

    };

   Person person3 = new Person

   {

     FullName = "Jacqueline",

     Address = "France",

     DateOfBirth = new DateTime(1980, 4, 2)

   };

   this.People.Add(person1);

   this.People.Add(person2);

   this.People.Add(person3);

   this.AddPerson = new Command(

                     () => this.People.Add(new Person()));

   this.DeletePerson = new Command<Person>(
 
                     (person) => this.People.Remove(person));

  }

}

People là một tập các đối tượng Person, SelectedPerson là một đối tượng Person, AddPerson và DeletePerson là các thuộc tính kiểu ICommand. Các thuộc tính kiểu ICommand này sẽ được gán các thực thể lớp Command. Tham số chuyển cho hàm khởi tạo của lớp Command khi khởi tạo thực thể gán đến các thuộc tính ICommand là các biểu thức lambda thực thi các phương thức Add và Remove trên collection people. Một vài namespace được khai báo ở đây:


using System.Collections.ObjectModel;

using MVVMSample.Model;

using System.Windows.Input;

using Xamarin.Forms;

Namespace thứ nhất tham chiếu đến đối tượng ObservableCollection, namespace thứ hai tham chiếu đến lớp Person, namespace thứ ba tham chiếu đến giao diện ICommand và namespace cuối cùng tham chiếu đến lớp Command. Nếu bạn khai báo namspace cuối cùng bị lỗi thì có thể cài lại một plugin Xamarin.Forms bằng cách vào Tools > NuGet Package Manager > Manage NuGet Packages for Solution.

Bây giờ, trong tập tin MainPage.xml thêm ListView có nội dung sau:


<StackLayout>

   <ListView x:Name="PeopleList" ItemsSource="{Binding People}"

             HasUnevenRows="True"

             SelectedItem="{Binding SelectedPerson}">

      <ListView.ItemTemplate>

        <DataTemplate>

         <ViewCell>

          <ViewCell.View>

           <StackLayout Margin="10">

               <Label Text="Full name:"/>

               <Entry Text="{Binding FullName}"/>

               <Label Text="Date of birth:"/>

               <DatePicker Date="{Binding DateOfBirth, Mode=TwoWay}"/>

               <Label Text="Address:"/>

               <Entry Text="{Binding Address}"/>

           </StackLayout>

         </ViewCell.View>

        </ViewCell>

      </DataTemplate>

    </ListView.ItemTemplate>

  </ListView>

  <StackLayout Orientation="Horizontal">

      <Button Text="Add" Command="{Binding AddPerson}"/>

      <Button Text="Delete" Command="{Binding DeletePerson}"

            CommandParameter="{Binding Source={x:Reference PeopleList},

            Path=SelectedItem}"/>

   </StackLayout>

</StackLayout>

Chú ý các Button không dùng thuộc tính Click mà dùng thuộc tính Command để kết buộc với các thuộc tính AddPerson và DeletePerson.

Bước cuối cùng là tạo một thực thể của đối tượng PersonViewModel và gán đến thuộc tính BindingContext bằng cách mở tập tin MainPage.xaml.cs và thay đổi nội dung lớp MainPage như sau:


public partial class MainPage : ContentPage

{

  private PersonViewModel ViewModel { get; set; }

  public MainPage()

  {

    InitializeComponent();


    this.ViewModel = new PersonViewModel();

    this.BindingContext = this.ViewModel;

  }

}

Lưu và thực thi

Đây chỉ là một ví dụ về cách dùng mô hình MVVM trong Xamarin.Forms. Ứng dụng thực tế rất phức tạp đòi hỏi chúng ta phải nghiên cứu nhiều về mô hình và công cụ khi triển khai MVVM. Có thể tham khảo thêm bài viết https://blogs.msdn.microsoft.com/msgulfcommunity/2013/03/13/understanding-the-basics-of-mvvm-design-pattern/

Mã nguồn dự án MVVMSample có thể tải tại GitHub.