In the previous post of my series on Xamarin MVVM Architecture, I introduced some of the many pros of committing some time upfront to properly architecting your Xamarin projects. In this post, we’ll dive deeper into some techniques you can (and should!) use to make your life as an architect much easier.
What goes where?
The first thing we’ll cover is a quick look at where I think you should place your various elements. To do so, let’s take a closer look at the some projects. Now, some of these won’t be present if your project, but for the most part, it should look pretty standard for a network connected mobile application. Note that they are named oddly, to get them in logical order in the Solution explorer – don’t do this normally.
To better understand what each of the layer’s responsibilities are, at a high level, we’ll follow the path of a common procedure – logging in a user. Following are the common steps you may take, along with which layer they are occurring in.
|⇣||Forms||UI issues a LoginCommand to a ViewModel.|
|⇣||Core||ViewModel allows LoginCommand to Execute, passes form information downwards, to Data Access Object (DAO), requesting a login.|
|⇣||Data||DAO receives the user’s credentials, passes them downwards, to the Network Access Object (NAO), requesting a login request.|
|⇡||Net||NAO POSTs the credentials to some server, and if all goes well, receives some JSON back. This JSON is passed upwards to the DAO, as a raw string.|
|⇡||Data||DAO has been await-ing the JSON string, and upon receipt, deserializes it into one of its known Models. This Model may be passed upwards, back to our ViewModel. Regardless, DAO lets the VM know that a login has completed.|
|⇡||Core||The ViewModel’s wait is over, and it now knows that a login has completed. It requests that something capable of displaying a success dialog does so.|
|–||Forms||Some DialogService triggers an successful login alert.|
This should sound pretty familiar to anyone who has ever setup a login in an application before, regardless of whether or not you’re using Xamarin. Now we’ll take a look at a few interesting stops along the path we took to get this action completed.
Don’t look up!
Notice how each layer only called into the layers “below” it. Forms requested that the Core, which contains the ViewModels, perform the login. The ViewModel may know about the Models, but they are below it. This downward dependency setup allows us to better adjust to changes when need be, by not having bi-directional requirements. It also allows for each layer to be as decoupled as it possibly can be, which lends itself perfectly to the maximum cross platform reuse we’re aiming for.
Observe for a minute the route we took to turn the server response JSON into an in memory Model, because this is a prime opportunity to accidentally couple. We have the NAO (Network Access Object) returning raw JSON, as a
String. This is really good. If we wanted to do the deserialization in this layer, and have the NAO call return a completed model, the Net layer would now have an upward dependency on the Data layer’s models. That is bad.
One of the reasons that that would be very bad is that it directly violates the Separation of Concerns (SoC) principal. This principal basically states that each unit of code should only do one thing. It makes sense for our Net layer to make a request to the server, but does it really make sense for it to deserialize an object? What if the process of deserialization has to do some other stuff, like apply business rules and establish in-memory relationships? It should be quite clear that these things have nothing to do with performing a network request, so you’d do well to keep things broken apart.
Third party dependencies.
Note also in our example that we’re working with JSON. In the .NET world, that means you’re probably using Newtonsoft’s Json.NET. If we do our deserialization in the Net layer, now there is an external dependency that doesn’t make a lot of sense. Especially since the Data layer probably is also using Json.NET in various ways.
One of the hardest (or funnest) parts of software architecture is that you are always capable of breaking the rules. If you check out the Xamarin Azure Component, you’ll see that its API returns model objects. Now, when you use Azure, you’ll probably just omit the Net project, and have it function as a Data layer dependency. Since they use a nice templated approach to returning their objects, I think this is totally acceptable. They technically don’t have any hard upward dependencies, as they’re capable of returning any type that their caller requests:
MobileServiceClient client = new MobileServiceClient("AppUrl", "AppKey"); IMobileServiceTable<TodoItem> todoTable = client.GetTable<TodoItem>(); List<TodoItem> items = await todoTable.Where(i=>i.Complete==true).ToListAsync();
So, if you’re using Azure, expect this behavior. And, if you’re not, and feel the benefits are worth the extra architecture, go for it! Just always stay cognizant of which project depends on which layer.
Depend upwards, indirectly.
To wrap things up, I’d like to briefly go over how you can look upwards correctly, when you must.
The best example for this in my opinion, is that of persistence. Most apps need to persist stuff, and most apps end up using SQLite in one way or another. And, since SQLite is pretty much everywhere, that’s a great choice to make. Now, imagine, in our example, we’ve gone down to the Net layer, received some JSON, and are currently in the Data layer. We’ve deserialized the string into a proper Model, and are prepared to return it upward. But first, we want to store the logged in user to disk. Normally we’d persist their session or OAuth token, but for now assume we’ll just persist the whole user.
This seems easy enough to do, we just ask our SQLite connection to write out some table data. But.. where does the connection come from? Therein lies our problem. Each platform has different implementation expectations for working manually with databases. They have different directories, some are sandboxed, and so on. So if each platform is providing their own implementation (they must), and a layer many levels beneath them needs to access it, what can we do?
Well, the short answer is, we can use Inversion of Control (IoC). By using an IoC “container”, we get a middleman, whom the producers can register implementations of interfaces with, and consumers can access said implementations via. At the very core, it is essentially a static dictionary mapping interfaces to implementations of said interfaces.
The way that Xamarin documents doing this is using their
Dependency attribute and the static DependencyService method,
Get<T>(), as is illustrated in this tutorial.
In our case, we would simply have an
ISQLite interface, which each platform implements and registers. Then, in the Data layer, we’d access the platform specific instance via
Don’t use Xamarin.Forms.Dependency!
So, now that we just learned what DependencyService is, know that I strongly suggest you don’t use it! I promise I’m not going crazy here, let us again think about our layer dependencies now. If we use DependencyService to access our database connection, or whatever we’re doing, we can churn out some nice iOS, Android, WP, etc apps. But, what about when someone wants to drop a WPF client on top of our super-reusable code base? Or a Xamarin.Mac app? Well.. it turns out our Data layer actually is now depending on the Xamarin.Forms library, and that is a really bad upward dependency, and one that can’t be replicated on non-Xamarin.Forms client applications.
So, instead, I recommend using a “neutral” IoC container. And, I also happen to have one I really like – MVVMLight. This super lean toolkit is just incredible. And, it’s available on pretty much all platforms.
Using it is really straightforward. Rather than using the Dependency attribute, you just have to manually register the implementation you’d like with it, like so:
This actually is a good thing, as the
Register method provides a few great overloads. Then, to access it in the Data layer, or whichever layer, just use
SimpleIoc.Default.GetInstance<T>(). Or, use the ServiceLocator class that Microsoft provides, and MVVMLight depends on. By doing this, you now have all of the benefits that inversion of control affords, with none of the unnecessary dependencies.
This is some pretty cool stuff, I think. In addition to the techniques and tools I’ve shown, I hope it also stands out that every single dependency, decision, separation, and so on must be thoroughly considered. Only with this stringent architectural approach can you maximize your project’s current and future reuse capabilities.
In a future post we’ll explore further what MVVMLight brings to the table. It has many cool things in its small footprint – dependency injection, lazy IoC options, IoC caching options, navigation and dialog service interfaces, and other goodies. If you can’t wait, I highly recommend checking out this Pluralsight course on it, which MVVMLight’s creator, Laurent Bugnion, has put together.
As always, I’d just like to thank all of you who’ve thought through this with me, and welcome all comments below.