For those who just want code: https://github.com/SuavePirate/Xamarin.Onion
Don’t forget:
- Part 1 on the general project structure: Onionizing Xamarin Part 1
- Part 2 on our Domain and Application layers: Onionizing Xamarin Part 2
- Part 3 on our Infrastructure layer: Onionizing Xamarin Part 3
A strong and scale-able architecture is important in applications, especially in Mobile Apps. APIs and SDKs are constantly changing, new technology is constantly released, and team sizes are always changing. A solid Onion Architecture can save a development team a lot of time by making it simple to change service implementations, restrict access to certain areas, making logic flow easy to follow, and making testing isolated blocks of code easier.
Some of the important topics this will cover:
- Separation of Concerns
- Inversion of Control
- Dependency Injection
- Model-View-ViewModel
- Testability
- Why all these things are important
Part 4
In this section, we will look at the code for our actual Xamarin.Forms client implementation along with talking about building other Non-Xamarin clients into our solution, and sharing as much code between them as possible. This are our Client layer, and will include setting up our Views, ViewModels, IoC container, and start up process.
Client Layer
First thing is first, let’s build our ViewModels
. These ViewModels
are going to interface with our Application layer by making calls to our defined Service Interfaces that will be injected into the constructors of our ViewModels
.
Some things to note: We are using MVVM Light in this example to make our MVVM and IoC easier to get going. So things such as the ViewModelBase
class and the Set()
method are coming from that library. You can choose to utilize a different library, or roll your own pretty easily. In either case, the principles are the same.
Let’s first abstract some universal properties into a BasePageViewModel.cs
public class BasePageViewModel : ViewModelBase { private bool _isLoading; private bool _isEnabled; private string _title; private ObservableCollection<string> _errors; public bool IsLoading { get { return _isLoading; } set { Set(() => IsLoading, ref _isLoading, value); } } public bool IsEnabled { get { return _isEnabled; } set { Set(() => IsEnabled, ref _isEnabled, value); } } public ObservableCollection<string> Errors { get { return _errors; } set { Set(() => Errors, ref _errors, value); } } public string Title { get { return _title; } set { Set(() => Title, ref _title, value); } } public BasePageViewModel() { IsEnabled = true; IsLoading = false; } public override void Cleanup() { base.Cleanup(); Errors = null; } }
From here let’s make a ViewModel
for each of our pages (in this example, we will just look at one “MainPage”)
MainPageViewModel.cs
public class MainPageViewModel : BaseViewModel { private readonly IUserService _userService; private string _bodyTitle; private string _bodyText; public string BodyTitle { get { return _bodyTitle; } set { Set(() => BodyTitle, ref _bodyTitle, value); } } public string BodyText { get { return _bodyText; } set { Set(() => BodyText, ref _bodyText, value); } } private async void Initialize() { IsLoading = true; await Task.Delay(2000); // simulate load time var users = await _userService.GetValidUsers(); if(users?.Data == null || users.Data.Count() == 0) { var user = await _userService.CreateUserAsync(new NewUser { FullName = "Felipe Fancybottom", Email = "feffancy@fancybottoms.com" }); BodyText = user.Data.Email; BodyTitle = user.Data.FullName; } else { BodyText = users.Data.First().Email; BodyTitle = users.Data.First().FullName; } IsLoading = false; } public MainPageViewModel(IUserService userService) { _userService = userService; Title = "Onion Template"; BodyTitle = "Loading Name"; BodyText = "Loading Email"; Initialize(); } }
Notice how we injected the IUserService
in the constructor, and use that to lazy load some data into our bindable properties. When we create our view and set the BindingContext
to this ViewModel
, we will see the UI automatically update when those do. This example does it async right from the constructor, but you can load your data and set up your initial properties any way you’d like.
The next step is to initialize our Inversion of Control, Dependency Injection, and ViewModelLocator
to tie all our layers together and allow us to automatically set the BindingContext
of our Page
.
If it makes sense to you, you can break the IoC set up into a separate project that references all the previous layers. For the sake of simplicity, we are going to do it in the same project as our Xamarin.Forms project.
IoCConfig.cs
public class IoCConfig { public IoCConfig() { ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default); } public void RegisterViewModels() { SimpleIoc.Default.Register<MainViewModel>(); } public void RegisterRepositories() { SimpleIoc.Default.Register<IUserRepository, UserRepository>(); // this is where you would change the registration to use a different repository } public void RegisterServices() { SimpleIoc.Default.Register<IUserService, UserService>(); } public void RegisterProviders() { SimpleIoc.Default.Register<IUserDataProvider, UserDataProvider>(); SimpleIoc.Default.Register<ICloudStorageProvider, AzureStorageDataProvider>(); // this is where you would change the registration to use a different provider } public void RegisterStores() { SimpleIoc.Default.Register<IUserStore, UserStore>(); SimpleIoc.Default.Register<IStoreManager, StoreManager>(); } }
The purpose of this class is to wire up our dependencies as well as our actual container for the ServiceLocator
. This example is using SimpleIoc which is packaged with MVVM Light.
Now that we have our other layers glued together, we just need to create our ViewModelLocator
to automatically handle our bindings, then make calls to the IoCConfig
when the ViewModelLocator
is initialized.
ViewModelLocator.cs
public class ViewModelLocator { public MainPageViewModel MainPage { get { return ServiceLocator.Current.GetInstance<MainPageViewModel >(); } } public ViewModelLocator() { var iocConfig = new IoCConfig(); iocConfig.RegisterRepositories(); iocConfig.RegisterProviders(); iocConfig.RegisterServices(); iocConfig.RegisterViewModels(); iocConfig.RegisterStores(); } }
In our constructor, we initialize our IoC, and also provide properties for each of our ViewModels
, so that we can bind it easily in our XAML.
The last two steps here are to add a Resource in our App.xaml to our ViewModelLocator
, and create our Page.
App.xaml
<?xml version="1.0" encoding="utf-8"?> <Application xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="OnionTemplate.App"> <Application.Resources> <!-- Application resource dictionary --> <ResourceDictionary> <vm:ViewModelLocator xmlns:vm="clr-namespace:NAMESPACEOF.VIEWMODELLOCATOR;assembly=YOURPACKAGENAME" x:Key="Locator" /> </ResourceDictionary> </Application.Resources> </Application>
Now that we have our resource, let’s create our page and wire up the BindingContext
in our XAML.
MainPage.xaml
<?xml version="1.0" encoding="UTF-8"?> <ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="NAMESPACE.MainPage" BindingContext="{Binding Source={StaticResource Locator}, Path=Main}" Title="{Binding Title}"> <ContentPage.Content> <StackLayout Orientation="Vertical" HorizontalOptions="Center" VerticalOptions="Center"> <Label Text="{Binding BodyTitle}"/> <Label Text="{Binding BodyText}"/> <ActivityIndicator IsRunning="True" IsVisible="{Binding IsLoading}"/> </StackLayout> </ContentPage.Content> </ContentPage>
There is nothing required to write in our code behind (MainPage.xaml.cs) since it is all automatically wired up.
Last but not least, set our page in our App.xaml.cs:
App.xaml.cs
public partial class App : Xamarin.Forms.Application { public App() { InitializeComponent(); MainPage = new NavigationPage(new MainPage()); } }
At this point, we should be able to run the application (assuming that the Xamarin.Forms app is started off in each platform the way the template sets it up).
So we have our Xamarin.Forms implementation. But what about other applications that can’t use Xamarin.Forms? Web apps? Xamarin.Mac apps? Cloud apps? WPF?
Here is one of the coolest parts of the entire Onion pattern. We can go ahead and add more projects into our Client layer. These layers can use the same models, interfaces, and in some cases, implementations! For projects where we would need completely different logic, such as a Web App for example, we can implement multiple versions of the Domain and Application layers.
In our web app, we could create another project in the Infrastructure layer (say Infrastructure.WebData) that uses Entity Framework and SQL. Then in our IoCConfig of our Web App, we call to register our Infrastructure.WebData implementations for our Domain.Interfaces.
As long as each project in the Client layer serves the same purpose of configuring Views, and starting up our application with our Inversion of Control, any type of application can live here and follow the same pattern.
The Client layer can also contain abstractions of controls or other utilities that can be referenced by the core Client projects.
What’s Next
In the next segment, we will look at how to integrate our individual mobile platforms, and how to inject custom platform-specific code with some examples using the HockeyApp SDK.
3 thoughts on “Onionizing Xamarin Part 4”