Inversion of Control and Dependency Injection are some design principles that help make our applications more flexible and scalable. They both help us separate our implementations and make it easy to substitute drastic changes to our implemented data or business logic whether it be for writing unit tests or product improvement.
Xamarin is a platform where IoC and DI fit extremely well. I’ve talked about this concept a few other times in both my blogs and videos about the Onion Architecture in Xamarin as well as how to call Platform Specific code from a Portable Class Library. You can find those posts and videos here:
- Onionizing Xamarin Part 6
- [VIDEO] Xamarin.Tips: Calling Platform-Specific Code from a PCL (Dependency Injection)
In this post, I want to talk about using DI with Mvvm Light at a VERY basic level.
First, let’s define an interface for a service we might use:
IUserService.cs
public interface IUserService { Task<User> GetCurrentUserAsync(); }
Now let’s create two different implementations. One that will be the service used in the application and the other that will be used for testing.
UserService.cs
public class UserService : IUserService { // makes a call to a web api to get a user public async Task<User> GetCurrentUserAsync() { using (var client = new HttpClient()) { var response = await client.GetAsync("https://mywebapi.mydomain/api/currentuser"); var content = await response.Content.ReadAsStringAsync(); return JsonConvert.DeserializeObject<User>(content); } } }
TestUserServices.cs
public class TestUserService : IUserService { public Task<User> GetCurrentUserAsync() { return Task.FromResult(new User { Name = "Test User" }); } }
Now we need a ViewModel
that will use this service. We define a private readonly IUserService
and then inject the implementation that we want in the constructor of the ViewModel
.
CurrentUserViewModel.cs
public class CurrentUserViewModel : ViewModelBase { // use the interface as the service and inject the implementation in the constructur private readonly IUserService _userService; private User _user; public User User { get { return _user; } set { Set(ref _user, value); } } public CurrentUserViewModel(IUserService userService) { _userService = userService; } public async Task UpdateUserAsync() { User = await _userService.GetCurrentUserAsync(); } }
Now let’s define an IoCConfig
that handles registering dependencies and implementations.
IoCConfig.cs
public class IoCConfig { public IoCConfig() { // use SimpleIoc from MvvmLight as our locator provider ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default); } // register the real implementation public void RegisterServices() { SimpleIoc.Default.Register<IUserService, UserService>(); } // register the test implementation public void RegisterTestServices() { SimpleIoc.Default.Register<IUserService, TestUserService>(); } // register the view model public void RegisterViewModels() { SimpleIoc.Default.Register<CurrentUserViewModel>(); } }
Now that we can register our Services as well as our ViewModels, the dependency resolver from SimpleIoc
can retrieve an instance of CurrentUserViewModel
with whichever version of IUserService
is registered depending on whether we call RegisterServices
or RegisterTestServices
.
Now we can retrieve our instance of the CurrentUserViewModel
by calling
var currentUserViewModel = ServiceLocator.Current.GetInstance<CurrentUserViewModel>();
MvvmLight recommends using a ViewModelLocator
to get the instance of your ViewModels:
ViewModelLocator.cs
public class ViewModelLocator { private readonly IoCConfig _iocConfig; public CurrentUserViewModel CurrentUser { get { return ServiceLocator.Current.GetInstance<CurrentUserViewModel>(); } } public ViewModelLocator() { _iocConfig = new IoCConfig(); _iocConfig.RegisterServices(); //_iocConfig.RegisterTestServices(); _iocConfig.RegisterViewModels(); } }
It’s recommended to either create your ViewModelLocator
at the app start up, or if you’re using Xamarin.Forms, register it as a Resource in your App.xaml
<Application ... xmlns:locator="clr-namespace:YOUR_LOCATOR_LOCATION"> <Application.Resources> <ResourceDictionary> <locator:ViewModelLocator x:Key="Locator"/> </ResourceDictionary> </Application.Resources> </Application>
Now in your XAML pages, you can automatically wire up your view model.
MainPage.xaml
<ContentPage ... BindingContext="{Binding Source={StaticResource Locator}, Path=CurrentUser}" Title="{Binding User.Name}"> ... </ContentPage>
In order to change to your testing data, you can just switch which call to your IoCConfig
is made for registering your dependency without having to make any changes to any of your other layers or UI!
If you like what you see, don’t forget to follow me on twitter @Suave_Pirate, check out my GitHub, and subscribe to my blog to learn more mobile developer tips and tricks!
Interested in sponsoring developer content? Message @Suave_Pirate on twitter for details.