MVVM with Xamarin Classic: The Basics

Xamarin makes it possible to develop apps for multiple platforms (Windows Phone, iOS, Android). The downside to this, is the need to develop a view per platform with codebehind. This results in duplicate code throughout the solution. This could partially be avoided by using a Shared Library. This library can, for example, contain classes that offer functionality to consume webservices. The part that cannot be avoided is UI specific code. Another downside of this approach is the lack of testability because you need to create unittests per platform and also need to test UI codebehind. The classes in the code behind have too many responsibilities, this is not SOLID.

The standard architecture looks like this:

xamarindefaultarchitecture

This is where MVVM provides a solution.

When applying MVVM, a viewmodel is created for each unique screen in the application. The three views (one for each platform) are bound to the corresponding ViewModel. Therefor the codebehind becomes obsolete and can be deleted. The ViewModels are located in the Shared Library. With MVVM the architecture looks like this:

xamarinmvvmarchitecture

The testability and maintainability are improved in several ways:

  • Only one class, the ViewModel, has to be created and tested instead of three code behind classes.
  • The ViewModel has no platform specific UI code.
  • Separating logic from the view gives the ability to inject dependencies into the viewmodel. The ViewModels can be tested with mocks and/or stubs.

There are some issues to resolve:

  • The View and the ViewModel are loosely coupled. The View needs to be pointed to the corresponding ViewModel.
  • You have to choose a Dependency Injection framework.

It’s not needed to reinvent the wheel. There are multiple MVVM frameworks available that offer this and much more. Besides MvvmLight you can also use MvvmCross.

The next codesnippets show samples on how the code looks like before and after applying MVVM with MvvmCross.

Classic non-MVVM way of a View is shown.

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent">
    <Button
        android:text="Previous"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/buttonPrevious"
        android:layout_alignParentBottom="true"
        android:layout_alignParentLeft="true" />
    <Button
        android:text="Next"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/buttonNext"
        android:layout_alignParentBottom="true"
        android:layout_alignParentRight="true" />
    <TextView
        android:text="Medium Text"
        android:textAppearance="?android:attr/textAppearanceMedium"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/textDescription"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="10dp" />
</RelativeLayout>

The ‘code behind’ for the View. Notice the dependency to the TodoTaskService. The same kind of code should be programmed for other platforms also.

[Activity(Label = "Naber.Tasks.UI.Droid", MainLauncher = true, Icon = "@drawable/icon")]
public class MainActivity : Activity
{
    Button buttonNext;
    Button buttonPrevious;
    TextView textDescription;
    TodoTaskService service = new TodoTaskService();
    IDataManager<TodoTask> data = null;
 
    protected async override void OnCreate(Bundle bundle)
    {
        base.OnCreate(bundle);
                    
        SetContentView(Resource.Layout.Main);
 
        var tasks = await service.GetTodoTasksAsync();
        this.data = new DataManager<TodoTask>(tasks);
                  
        buttonNext = FindViewById<Button>(Resource.Id.buttonNext);
        buttonPrevious = FindViewById<Button>(Resource.Id.buttonPrevious);
        textDescription = FindViewById<TextView>(Resource.Id.textDescription);
 
        buttonNext.Click += buttonNext_Click;
        buttonPrevious.Click += buttonPrevious_Click;
 
        UpdateUi();
    }       
 
    void buttonPrevious_Click(object sender, EventArgs e)
    {
        data.MovePrevious();
        UpdateUi();                        
    }
 
    void buttonNext_Click(object sender, EventArgs e)
    {
        data.MoveNext();
        UpdateUi();            
    }
 
    private void UpdateUi()
    {
        textDescription.Text = data.Current.Description;                      
        buttonPrevious.Enabled = data.CanMovePrevious;
        buttonNext.Enabled = data.CanMoveNext;
    }
}

When using MvvmCross the Code Behind can be removed. The View supports data binding. The bold lines are added and takes care of binding the property of the control (first string) to the property or command on the ViewModel (second string).

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:local="http://schemas.android.com/apk/res-auto"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent">
    <Button
        android:text="Previous"
        local:MvxBind="Click PreviousCommand"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/buttonPrevious"
        android:layout_alignParentBottom="true"
        android:layout_alignParentLeft="true" />
    <Button
        android:text="Next"
        local:MvxBind="Click NextCommand"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/buttonNext"
        android:layout_alignParentBottom="true"
        android:layout_alignParentRight="true" />
    <TextView
        local:MvxBind="Text Description"
        android:textAppearance="?android:attr/textAppearanceMedium"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/textDescription"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="10dp" />
</RelativeLayout>

The ViewModel is created in a shared library so all target platforms can use it. Notice there is no dependency to an instance of the TodoTaskService. It’s being injected. This can be configured in the Initialization of MvvmCross which is also reused code.

public class MainViewModel : MvxViewModel
{
    ITodoTaskService taskService;
    IDataManager<TodoTask> tasks;
 
    public MainViewModel(ITodoTaskService taskService)
    {
        this.taskService = taskService;
    }
 
    public async override void Start()
    {
        this.tasks = new DataManager<TodoTask>(await this.taskService.GetTodoTasksAsync());
        this.tasks.MoveFirst();
 
        Rebind();
        base.Start();
    }
 
    private void Rebind()
    {
        this.Description = this.tasks.Current.Description;
       
        NextCommand.RaiseCanExecuteChanged();
        PreviousCommand.RaiseCanExecuteChanged();
    }
 
    private string description;
 
    public string Description
    {
        get { return this.description; }
        set
        {
            this.description = value;
            RaisePropertyChanged(() => Description);
        }
    }   
    
    private MvxCommand nextCommand;
    public MvxCommand NextCommand
    {
        get
        {
            this.nextCommand = this.nextCommand ?? new MvxCommand(NavigateToNext, CanNavigateNext);
            return this.nextCommand;
        }
    }
 
    private bool CanNavigateNext()
    {
        return this.tasks.CanMoveNext;
    }
 
    public void NavigateToNext()
    {
        this.tasks.MoveNext();
        Rebind();
    }
 
    private MvxCommand previousCommand;
    public MvxCommand PreviousCommand
    {
        get
        {
            this.previousCommand = this.previousCommand ?? new MvxCommand(NavigateToPrevious, CanNavigatePrevious);
            return this.previousCommand;
        }
    }
 
    private bool CanNavigatePrevious()
    {
        return this.tasks.CanMovePrevious;
    }
 
    public void NavigateToPrevious()
    {
        this.tasks.MovePrevious();
        Rebind();
    }
}

This basic sample shows how to apply MVVM to Xamarin Classic. A SOLID way to develop business apps.

The full sample can be downloaded here.

Advertenties

Geef een reactie

Vul je gegevens in of klik op een icoon om in te loggen.

WordPress.com logo

Je reageert onder je WordPress.com account. Log uit /  Bijwerken )

Google+ photo

Je reageert onder je Google+ account. Log uit /  Bijwerken )

Twitter-afbeelding

Je reageert onder je Twitter account. Log uit /  Bijwerken )

Facebook foto

Je reageert onder je Facebook account. Log uit /  Bijwerken )

w

Verbinden met %s