App Development

Model-View-Presenter (MVP): an alternative to MVVM for cross-platform Xamarin projects

Silver Iphone 5s and White Samsung Android Smartphone

The biggest benefit of using Xamarin for cross-platform mobile application development projects is the ability to share code. Sharing code will reduce development time, enforce platform parity for iOS and Android apps, and result in fewer bugs and more testable code. To capitalize on this we need a way to separate the underlying business logic of the app from the platform-specific View layer. If done correctly each platform only needs to construct the layouts and hook them up.

The Model-View-ViewModel (MVVM) architecture is the most common solution for this in the Xamarin world. However, MVVM is heavily dependent on the process that binds the View and ViewModel. Usually this is accomplished through a data binding framework. These frameworks can be heavy, buggy, and constricting and can add significant risk to a project. Model-View-Presenter (MVP) is another architecture that accomplishes the same degree of code sharing as MVVM, but without the need for a binding framework.

Why does MVVM require a binding framework?

In MVVM business logic is contained in the ViewModel layer. The ViewModel contains properties that are bound directly to elements of the View and are modified by the business logic. As these properties change the View layer should immediately update to reflect it.

The problem is how we make the View respond to those changes. Imagine we have a relatively complex screen with a lot of elements. In order for the ViewModel to be shared across platforms, the ViewModel must be agnostic of how the View layer is implemented. Therefore we cannot directly call functions on those elements. We also cannot have an all-purpose ViewModelChanged event where the entire View updates because that would be horribly inefficient. We need a way to update only the relevant parts of the View layer when its state changes without the ViewModel knowing what elements are changing.

A binding framework solves this problem by letting us bind elements in the View layer to the ViewModel’s properties they depend on. When one of these properties changes, we notify the framework and it will automatically update only that part of the View. Because the binding happens from the View to the ViewModel and not the other way around, the ViewModel layer does not know anything about how it’s properties are used, so the code can be shared.

Why doesn’t MVP require a binding framework?

With MVP, the state of the View layer is not actually stored in our shared business layer. Instead of a ViewModel with properties we have a Presenter that calls into the View layer via an interface which is implemented per platform. Each View callback represents a change in the View’s state. So instead of changing a property in our ViewModel and notifying a binding framework, our business logic just calls into the View whenever necessary. For instance:

public interface ILoginView
{
	void OnWaiting();
	void OnStopWaiting();
	void OnInvalidCredentials(string message);
	void OnGoToNextScreen();
}

public class LoginPresenter
{
	private ILoginInteractor _interactor;
	private ILoginView _view;
	private string _username;
	private string _password;
	private bool _pendingLoginRequest;

	...

	public async Task Login()
	{
		if (!HasValidInput())
		{
			return;
		}
		_pendingLoginRequest = true;
		_view.OnWaiting();
		bool success = await _interactor.Login(_username, _password);
		_view.OnStopWaiting();
		_pendingLoginRequest = false;
		if (success)
		{
			_view.OnGoToNextScreen();
		}
		else
		{
                    _view.OnInvalidCredentials(Strings.InvalidCredentialsError);
		}
	}

	...
}

The use of an interface keeps the business layer agnostic of the View’s implementation, and the granularity of the View callbacks means the Presenter can update the View in an efficient way without the need for a framework.

Testing

Both MVVM and MVP separate the business layer from the View layer. By doing so, our business logic is not only easy to share, but also very easy to test.

In both cases our tests call into the business layer directly to mimic user input and then make assertions about the “output” into the View. In MVVM we just assert that our public properties have the values we expect and make the assumption that the binding will occur as intended. In MVP we instead provide a mocked implementation of the View interface and assert that callbacks happen as expected.

There are two easy ways to do this. One is to create a private TestView class in our unit tests and provide that to the Presenter. The TestView class contains public properties that are set by the View callbacks. Assertions can then be made against those properties. In our example we are using Moq to mock our backing data layer as necessary for each test case.

[TestFixture]
public class LoginPresenterTests
{
	private LoginPresenter _presenter;
	private TestView _view;
	private Mock<ILoginInteractor> _mockInteractor;

	[SetUp]
	public void Setup()
	{
		_view = new TestView();
		_mockInteractor = new Mock<ILoginInteractor>();
		_presenter = new LoginPresenter(_view, _mockInteractor.Object);

		//By default, interactor will return success for any credentials
		//Override as necessary
		_mockInteractor.Setup(interactor => interactor.Login(It.IsAny<string>(), It.IsAny<string>()))
                    .Returns(Task.FromResult(true));
	}

	[Test]
	public async Task TestLoginCorrectCredentials()
	{
		Assert.False(_view.NavigatingToNextScreen);
		_presenter.UpdateUsername("User");
		_presenter.UpdatePassword("Pass");
		await _presenter.Login();
		Assert.False(_view.InvalidCredentialsError);
		Assert.True(_view.NavigatingToNextScreen);
	}

        ...

	private class TestView : ILoginView
	{
		public bool NavigatingToNextScreen;
		public bool InvalidCredentialsError;
		public bool Waiting;

		public void OnGoToNextScreen()
		{
			NavigatingToNextScreen = true;
		}

		public void OnInvalidCredentials(string message)
		{
			InvalidCredentialsError = true;
		}

		public void OnStopWaiting()
		{
			Waiting = false;
		}
	
		public void OnWaiting()
		{
			Waiting = true;
		}
	}

}

The other way is to use Moq for our View layer as well. Moq lets us assert that specific callbacks were made including how many times it was made and with what parameters:

[Test]
public async Task TestWaitingCallbackWithMoq()
{
	Mock<ILoginView> mockView = new Mock<ILoginView>();

	_presenter = new LoginPresenter(mockView.Object, _mockInteractor.Object);
	_presenter.UpdateUsername("User");
	_presenter.UpdatePassword("Pass");

	await _presenter.Login();

	mockView.Verify(view => view.OnWaiting(), Times.Once());
	mockView.Verify(view => view.OnStopWaiting(), Times.Once());
}

Dependency Injection

A challenge with both MVP and MVVM is how the business layer is provided with its dependencies in a way that keeps these details hidden from the View. Our end goal is that refactoring the business layer or backing data should not require a single line of code to change in the View layer. This makes for more maintainable code and more efficient development. The solution for this is dependency injection.

In our sample project we used Ninject. Ninject allows us to bootstrap our Presenters in each screen by providing any number of Modules to a graph of dependencies. When we want an instance of a specific class, Ninject uses this graph to resolve the class using Modules, default constructors, and constructors containing already provided dependencies. A good route when using Ninject with MVP is to have one main Module and then one Module per screen that contains dependencies specific to that screen. For instance, the main Module could have the API and data persistence while the screen specific Modules could contain the Presenter and View interface implementation:

public class ApplicationModule : NinjectModule
{
	public override void Load()
	{
		Bind<IApi>().ToConstant(new ApiImplementation()).InSingletonScope();
	}
}

public class LoginModule : NinjectModule
{
	private ILoginView _view;

	public LoginModule(ILoginView view)
	{
		_view = view;
	}

	public override void Load()
	{
		Bind<ILoginView>().ToConstant(_view);
		Bind<ILoginInteractor>().To<LoginInteractor>();
		//NOTE: we do not have to explicilty bind LoginPresenter 
		//since we satisfy all of its constructor's dependencies
	}
}

public class LoginActivity : ILoginView
{
	private LoginPresenter _presenter;

        protected override void OnCreate(Bundle savedInstanceState)
	{
		base.OnCreate(savedInstanceState);

		...

                IKernel kernel = new StandardKernel(new ApplicationModule(), new LoginModule(this));
                _presenter = kernel.Get<LoginPresenter>(); 
	}

}

Notice that we are providing the View interface by having our Activity/ViewController pass itself to the screen Module’s. When we ask Ninject for our presenter it will use the Activity/ViewController to satisfy the View interface dependency.

Sometimes our backing data layer has dependencies that are specific per platform. To provide them we can define a single interface as an abstraction and then define a module on each platform that provides its own implementation. For example, here is how to provide a platform specific dependency that fetches phone contacts:

public interface IContactsReceiver
{
	Task<List<ContactModel>> FetchContacts();
	void Cancel();
}

public class ContactsModule : Ninject.Modules.NinjectModule
{
	private IContactsView _view;

	public ContactsModule(IContactsView view)
	{
		_view = view;
	}

	public override void Load()
	{
		Bind<IContactsView>().ToConstant(_view);
		Bind<IContactsInteractor>().To<ContactsInteractor>();

		//IContactsReceiver must be bound by platform module
	}
}
	
public class ContactReceiverModule : Ninject.Modules.NinjectModule
{
	//App context
	 private Context _context;

	public ContactReceiverModule(Context context)
	{
		_context = context;
	}

	public override void Load()
	{
		Bind<Context>().ToConstant(_context);
		Bind<IContactsReceiver>().To<AndroidContactsReceiver>();
	}
}

public class ContactsActivity : IContactsView
{
	private ContactsPresenter _presenter;

        protected override void OnCreate(Bundle savedInstanceState)
	{
		base.OnCreate(savedInstanceState);

		...

                IKernel kernel = new StandardKernel(new ApplicationModule(), new ContactsModule(this), new ContactReceiverModule(ApplicationContext));
                _presenter = kernel.Get<ContactsPresenter>(); 
	}
}

Caveats

While it does free us from the need for a binding framework, MVP will generally require more boilerplate code than MVVM. Every callback to the View needs its own interface method and implementation. This is especially noticeable with something like a text field which can be set by, but also provide input to, the business layer. When there is an especially complicated View with a lot of moving parts this becomes a bigger deal.

Also, because we are not holding the View layer’s state in our Presenter, dealing with screen rotation on Android becomes a bit trickier. Recall that the Presenter interacts with the View through a series of update callbacks. The Presenter keeps no history of these updates, and for this interaction to work correctly the View should always be in the state it was left in. For Android this means on orientation change we need to save and restore our View layer such that it looks exactly as it did before. This is only the transient, visible state of the View – things like text, RecyclerView models, visibility of different elements, etc. On iOS this is not an issue because the Presenter will always have the same lifecycle as the ViewController.

Which one should you use?

Both MVP and MVVM are great architectures for cross platform mobile application development projects. They offer the same level of test coverage and shareability. The question is whether the convenience of data binding is worth the risk. Once we commit to using an MVVM framework on a project and make some progress, it requires a huge refactor to replace it. Often times we do not discover the limitations or problems with these frameworks until it’s too late. That being said, if you are positive your MVVM framework will not cause problems later then by all means stick with it. The complexity of your Views is another factor that makes the convenience of data binding more desirable. Regardless of which route you choose, I would recommend at least trying MVP in a side project to see how it feels. It can be liberating to free yourself and your code from those huge MVVM frameworks.

Sample Code

In this repository you will find a sample cross platform Xamarin project we have created to demo how the MVP architecture works. Thanks to Ben Frye for helping with the code and implementing the iOS side. Feel free to use this code as you please.

Quickstart-Guide-to-Kotlin-Multiplatform

A Quick Start Guide to Kotlin Multiplatform

Kotlin Multiplatform, though still experimental, is a great up-and-coming solution...

Read the article