Introduction
This article is about async MVVM model for Xamarin Forms.
Background
While playing with Xamarin Forms, the problem I've had is async operations on first page load. We cannot use async in constructor properly, so what I need is an async call after page load. A method come to my mind and it's working without problem, and I wanted to share it. I hope you like it.
I'll write everything from scratch. These are the current versions I've used:
- Visual Studio 2015 Community Edition
- Xamarin for Visual Studio (4.1.1)
Using the Code
Let's create a project and add our base model and use it. It'll take just three steps.
You can view the source code in GitHub.
1. Create a New Xamarin Forms Project (Xaml App)
And we're ready.
2. Create Base Model
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace AsyncTest.Models
{
internal abstract class BaseModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
For async operations, this base model has to know which page it is bind to. So let's add it:
protected Page CurrentPage { get; private set; }
Now, we need an initializer:
public void Initialize(Page page)
{
CurrentPage = page;
CurrentPage.Appearing += CurrentPageOnAppearing;
CurrentPage.Disappearing += CurrentPageOnDisappearing;
}
The complete code is as follows:
using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using Xamarin.Forms;
namespace AsyncTest.Models
{
internal abstract class BaseModel : INotifyPropertyChanged
{
protected Page CurrentPage { get; private set; }
public event PropertyChangedEventHandler PropertyChanged;
public void Initialize(Page page)
{
CurrentPage = page;
CurrentPage.Appearing += CurrentPageOnAppearing;
CurrentPage.Disappearing += CurrentPageOnDisappearing;
}
protected virtual void CurrentPageOnAppearing(object sender, EventArgs eventArgs) {}
protected virtual void CurrentPageOnDisappearing(object sender, EventArgs eventArgs) {}
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
As you see, base model takes page as property and creates virtual methods for "Appearing
" and "Disappearing
" events. If you need other events, just add them as virtual
methods.
3. Create Page and Use It
We already have "MainPage.xaml", so let's use it.
First, we need a model for page.
namespace AsyncTest.Models
{
internal class MainModel : BaseModel {}
}
I'll use http://jsonplaceholder.typicode.com/ for testing.
using System;
using System.Net.Http;
namespace AsyncTest.Models
{
internal class MainModel : BaseModel
{
private string _responseText;
public string ResponseText
{
get { return _responseText; }
set
{
_responseText = value;
OnPropertyChanged();
}
}
protected override async void CurrentPageOnAppearing(object sender, EventArgs eventArgs)
{
ResponseText = "Loading, please wait...";
try
{
await Task.Delay(TimeSpan.FromSeconds(3));
using (var client = new HttpClient())
{
var responseMessage = await client.GetAsync
("http://jsonplaceholder.typicode.com/posts/1");
if (responseMessage.IsSuccessStatusCode)
ResponseText = await responseMessage.Content.ReadAsStringAsync();
else
ResponseText = $"StatusCode: {responseMessage.StatusCode}";
}
}
catch (Exception exception)
{
ResponseText = exception.ToString();
}
}
}
}
I like intellisense in designer so I'll add model into designer.
="1.0"="utf-8"
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:models="clr-namespace:AsyncTest.Models;assembly=AsyncTest"
x:Class="AsyncTest.MainPage">
<ContentPage.BindingContext>
<models:MainModel />
</ContentPage.BindingContext>
<ContentPage.Content>
<Label Text="{Binding ResponseText}" />
</ContentPage.Content>
</ContentPage>
And now we have to tell the model about this page so it'll initialize events.
using AsyncTest.Models;
namespace AsyncTest
{
public partial class MainPage
{
public MainPage()
{
InitializeComponent();
((BaseModel) BindingContext).Initialize(this);
}
}
}
That's it. Hit F5 and enjoy!