Following on from my LinkedIn article of Adapting to Enterprise and B2E Xamarin Forms App Development, this post will go into how I structure and architect an app, that will have a large code base, and multiple developers. When we are dealing with a large code base, across multiple developers, it’s wise to start favoring control and consistency above initial speed of development. Setting up your CI/CD process, is a must, to gain speed in pushing out updates.
First, lets look at the project structure. Portable Class Libraries, or .NET Standard Libraries are a must use in these scenario’s. Shared code is far too hard to maintain over long periods, with multiple developers. MVVM and Dependency Injection are essential, and while Xamarin Forms comes with these features baked in, it is recommended to use an MVVM Framework. There are plenty of good MVVM Frameworks out there. For myself, and the style of architecture I wanted to use, none of them quite fit as well as I wanted them to. Which is why I created Exrin, and even a template that mirrors this architecture style.
The native projects are kept rather light. I only put Custom Renderers, or platform specific code, that I will use with dependency injection. Any configuration, will be loaded or setup here as well. This may be different depending upon your MVVM framework, and dependencies.
Configuration files are normally kept in the native project, for any platform specific configuration. You may also keep configuration in your App project, for cross-platform configuration. To ensure you have configuration files that are easier to work with Continuous Deployment, have a look at Configuration Files in Xamarin Forms, to see how to set this up. Configuration is then also injected into the app via an IConfiguration object, which is fantastic when unit testing.
This is your main starting point, and where the Xamarin Forms Application (App.xaml) will be located. This project has a dependency on Xamarin Forms, and will provide any bootstrapping as required. Depending upon your MVVM Framework, this is normally where I map Views to ViewModels, and initialize the Dependency Injection Container.
The framework project is a simple library, that contains only definitions, or generic helper code for the entire app. Keeping this project with zero dependencies is key to allow it to be available across all libraries in your app. Think of the framework app, as the generic contracts that allow you to pass data between projects. This project will have NO reference to Xamarin Forms.
Your main business logic, including ViewModels and Models, will be located in here. The interesting point to note here, is there will be NO Xamarin Forms reference. This is key, if you want an easy time setting up Unit Tests. Inside the Logic app you will also want to include a Service Layer and Repository Layer. These can be branched out into separate projects if you need to, but generally it’s easy to keep them in the same app.
Service Layer & Repository
The next approach I take, when dealing with services, such as connecting to API’s, is to first create an aggregate service layer. This service layer acts as a service that connects with repositories to retrieve the information, from multiple repositories and give them back to the Model. Sometimes the Service layer may simply be a pass through layer, but often its great in combining and manipulating data from various sources to your app. This also means that any changes to the repository are shielded from your app. It becomes increasingly great when you are in control of your own API and numerous modifications are made during your development lifecycle, which were out of your control.
The repository layer is simply a connection to a database, or an API. It gets the data from the source and passes it back to the service layer, for mapping into an app friendly model, for consumption.
This project, will contain all your Xamarin Forms views, behaviors, converters, effects and any other UI related code. It is completely separated from your business logic and the two projects won’t reference each other.
While I know its perfectly acceptable to place UI related code, in the code behind of a view, I take a hard line and don’t allow anything in the code behind file. Behaviors, Effects, Converters or writing a Custom Control, is preferable. It keeps your Pages, completely XAML, encourages you to reuse controls, and other UI related code. It also alleviates having to monitor and track which views have code, and if it needs to have a corresponding unit test.
As important as always, adding a unit test project is essential to quickly spot potential breaking code, as you develop the app. While I won’t go over various approaches of how to code Unit Tests, the unit tests here will only be testing the Logic project of your app. The Framework and App projects should only have bootstrapping code, or definitions. It’s unlikely to contain any business logic. The View project will also have no unit tests, unless you want to test behaviors, or converters. Even so, these behaviors and converters can run code in the Logic app for anything substantial. If your converters, behaviors etc aren’t substantial, you can normally avoid unit testing these.
Because the Logic project is just a standard library, with no reference to Xamarin Forms or native Xamarin, it is extremely easy to unit test. I normally create the unit test as a Full .NET Framework project, and run with xUnit. Though NUnit is also another common alternative.
If you wish to unit test code in your native Xamarin project files, you will need special runners, that some unit testing frameworks provide. However these are normally a little trickier to setup. As such if all business logic can be contained in the logic project, you will have a very easy time.
UI Tests are important to test the Views in your app and complete integration tests between your views and code. I would recommend Xamarin UITest, though developing scripts in Appium is another common approach I have found. Ensure you code the AutomationId on each visual element, and any mobile testing framework will be able to pick up these ID’s to perform actions and read values.
Larger projects, tend not to work well with certain patterns and technologies. In this case I would advise you steer clear of Messaging Center, or any events that are global in nature. Their interactions become increasingly hard to track when you reach a certain scale. You can read the post Misuses of MessagingCenter to see the various cases against this pattern.
However, I do recommend Reactive Extensions, to handle events between two objects, such as the Model and ViewModel, or within the ViewModel. You can look at an Introduction to Rx.NET to see how it can apply to your project.