Monday, April 07, 2014

A busy MvxViewController for MvvmCross + iOS

In my MVVMCross apps, in the shared PCL core library, I usually have a MvxViewModel derived class called ViewModelBase with a IsBusy property:


public class ViewModelBase : MvxViewModel
{
bool isBusy;
public bool IsBusy
{
get { return this.isBusy; }
set { if (this.isBusy != value) { this.isBusy = value; this.RaisePropertyChanged("IsBusy"); } }
}

// other base stuff in ViewModelBase
}
The property is to update a progress indicator control in the view.
For iOS the control can be UIActivityIndicatorView.

But instead of placing a UIActivityIndicatorView on each view, we can dynamically create it at run-time when IsBusy property changes to true.
We can have this implemented in a ViewController base class like this:

using System;
using System.ComponentModel;
using MonoTouch.ObjCRuntime;
using MonoTouch.UIKit;
using Cirrious.MvvmCross.Touch.Views;
using Cirrious.CrossCore.WeakSubscription;
using YourApp.Core.ViewModels;

namespace YourApp.Touch
{
public abstract class MvxViewControllerBase : MvxViewController
{
// weak subscription to NotifyProperty event
IDisposable npSubscription;

public override void ViewDidLoad()
{
base.ViewDidLoad();

// subscribe to view-model's PropertyChanged
this.npSubscription = ((INotifyPropertyChanged)this.ViewModel).WeakSubscribe<bool>("IsBusy", (s, e) => { this.UpdateActivityIndicatorView(); });

// at this point view-model exists, so update the indicator view
this.UpdateActivityIndicatorView();

// add other common UIViewController stuff
}

protected override void Dispose(bool disposing)
{
if (disposing && this.npSubscription != null)
{
this.npSubscription.Dispose();
this.npSubscription = null;
}
base.Dispose(disposing);
}

void UpdateActivityIndicatorView()
{
// get the activity indicator
var activityIndicatorView = (UIActivityIndicatorView)this.View.ViewWithTag(1000);

var vm
= (ViewModelBase)this.ViewModel;
if (vm.IsBusy)
{
// show busy indicator. create it first if it doesn't already exists
if (activityIndicatorView == null)
{
activityIndicatorView
= new UIActivityIndicatorView(this.View.Frame)
{
ActivityIndicatorViewStyle
= UIActivityIndicatorViewStyle.Gray,
Tag
= 1000
};

this.Add(activityIndicatorView);
this.View.BringSubviewToFront(activityIndicatorView);
activityIndicatorView.StartAnimating();
}

// show the activity indicator
activityIndicatorView.Hidden = false;
}
else
{
// hide the activity indicator
if (activityIndicatorView != null)
{
activityIndicatorView.Hidden
= true;
}
}
}
}
}
Instead of having a base class we can delegate the implementation to an extension class which has the same implementation.

Please let me know if you see any possible issues.

I am thinking to do a similar implementation for other platforms as well.
Maybe MVVMCross could support out of the box a similar implementation. A built in implementation in MVVMCross would probably require to be able to override the style for the indicator.

1 comment:

Kenton Pickard said...

This is almost a year old, but the way I've solved this is to create an IsVmBusy property in the BaseViewController and then use a binding (which will do the WeakSubscribe behind the scenes) to connect the VM.IsBusy value to the BaseViewController.IsVmBusy property. In the setter of the IsVmProperty I would call your method start or stop the activity indicator.