If you are a .NET developer and you want to create an iOS or Android application with a native user experience and/or good code maintainability, using Xamarin is an attractive option. Xamarin allows you to leverage your C# + .NET skills and the Microsoft Visual Studio development tools for coding iOS and Android apps, with full access to the native controls, libraries, services and UI design tools for each platform.
Choosing a code sharing pattern
If you need to build the same app for more than one platform, you will also be looking for a coding pattern to share as much of your C# + .NET code as possible. The pattern should also allow you to benefit from the native UI design tools for each platform. Now Android and iOS platforms use an MVP-like pattern, while the Windows Store and Windows Phone platforms use the MVVM pattern. Which pattern to use?
While it is possible to use an MVP-like pattern on all platforms (e.g. as explained here), you will miss out on the productivity gains offered by the visual designers for Windows Store / Phone apps, such as using design-time data. On the other hand, using MVVM introduces a coding overhead for implementing data-bindable properties and commands on your viewmodels.
This is where MvvmQuickCross comes in handy. MvvmQuickCross eliminates the coding overhead of data binding; it is a cross-platform MVVM pattern designed to accelerate MVVM development and to maximize sharing C# code.
The app: Cloud Auction
So let’s start building an app.
Since this post is part of a blog series about a mobile website with apps and social features, we will build an auction app with similar functionality to the mobile website. Users can bid on products that are put up for sale one by one in rapid succession. While a product is on display, its price drops continually and the first user to place a bid gets the product for that price. When a user places a bid or the minimum price is reached without a bid, the auction progresses to the next product.
Here are design mockups of some of the app screens and navigation (for iPhone):
(created with Balsamiq Mockups)
Building the app for the first platform: Windows Store
We will build the app for iOS, Android, Windows Phone and Windows Store. To quickly create and test the cross-platform shared code, it is best to start with the platform that you are most productive with. For me that is Windows Store, so we will start with that. An advantage of Windows Store apps is that you can debug your app directly on your machine, no emulator needed. And if you use design-time data, you can speed up the design implementation cycle even more, because you can test the data binding without starting the app.
Note that we want to reuse our viewmodels on devices with many form factors, from small phones to tablets and PCs. For optimal code reuse, it is best to start defining the viewmodels based on the design for the smallest screen. On larger screens, you then have the option to combine several small viewmodels for display in a single (composite) view. This is why we use the iPhone design to define the first viewmodels, instead of the Windows Store design.
NOTE: The complete source for this example CloudAuction app is available here.
Creating the solution structure
We will follow the Getting Started steps as described in the MvvmQuickCross reference on GitHub. First we create the solution structure.
Since the app is named CloudAuction, we create a new Windows Store app project and a solution named CloudAuction:
Now add a Windows Store Class Library project for the shared code, and name it CloudAuction.Shared:
In the solution explorer, remove the Class1.cs file, rename the solution to CloudAuction.ws and rename the CloudAuction.Shared project to CloudAuction.Shared.ws:
Finally, rename the CloudAuction project folder to CloudAuction.ws: remove the CloudAuction project from the solution, rename the project folder in file explorer:
and re-add the existing CloudAuction project to the solution.
With this method we can add solution files and project files for other platform in the same solution folder and project folder, so we can include the existing shared source files in each platform-specific project without using linked project items. After we have added the iOS, Android and Windows Phone apps, the solution folder and the shared code project folder will look something like this:
Note that this method is a refinement of using platform-specific library projects as described in the Xamarin guidance on setting up a cross-platform solution; we avoid both file linking and file cloning.
Now we add a reference from the CloudAuction project to the CloudAuction.Shared.ws project:
and we set the default namespace and the assembly name for both projects so that the assembly name includes the .ws suffix, and the default namespace does not:
This ensures that the filename of the shared code project assembly will be different for each platform, which is useful e.g. when you are building the shared code for multiple platforms in the same working folder.
With the solution structure in place, we can install MvvmQuickCross and wire it up in the app.
First add the MvvmQuickCross NuGet package to the solution. Open the Package Manager Console in Visual Studio (menu View | Other Windows | Package Manager Console) and enter: Install-Package MvvmQuickCross
The available MvvmQuickCross commands are now displayed in the package manager console. You can type “Get-Help command -Online” for more details on a command.
Now enter this command in the Package Manager: Install-Mvvm. An MvvmQuickCross folder is now added to your library project and your application project, and a few application-specific project items are generated and opened in Visual Studio:
Note that the first part of the solution filename (before the first dot – in this case, CloudAuction) is used by default for naming the Application class and Navigator interface. This is why we used the application name as the first part of the solution name earlier.
If you inspect the generated MvvmQuickCross\App.xaml.cs partial class in the application project, you will notice this TODO comment, which is also visible in the Task List Comments:
Cut the comment lines to the clipboard (it will disappear from the Task List), then paste it over the indicated code in OnLaunched(), and uncomment it:
On application startup, this code will initialize the application instance and then navigate to the Main view, which was generated together with the MainViewModel as an example by the Install-Mvvm command. Now if you run the app, you will see a functioning Main view, exactly as it looks like in the xaml Designer:
The MvvmQuickCross.Templates folder in the CloudAuction.Shared.ws project contains a few template files and a code snippets file. We will use those code snippets to code our viewmodels, so let’s import them in Visual Studio. First select the MvvmQuickCross.snippets file in the MvvmQuickCross\Templates folder, press F4 to open the Properties window, and copy the full path of the snippets file to the clipboard:
Now open the Code Snippets Manager (Tools menu), set the language to Visual C# and click Import:
Now paste the full path of the snippets file and click Open:
Finally select the location where you want to import the snippets to (typically My Code Snippets – but you can add your own folders in the Code Snippets Manager) and then select Finish:
If you get a “Snippet With Same Name Exists” dialog, select Overwrite.
Note that you can inspect all imported MvvmQuickCross code snippets in the code snippets manager, e.g. to see what the shortcuts are:
Now build the solution – there should be no errors. We are ready to start coding J.
Adding a view and a viewmodel
In the design mockups above, we can identify three views: Auction, Order and OrderResult. Let’s create the Auction viewmodel first:
In the Package Manager Console, enter New-View Auction:
This generates and opens an AuctionView.xaml markup file, an AuctionView.xaml.cs class, an AuctionViewModel.cs class and some navigation methods in the application and navigator classes (for more details on the New-View command, see the reference).
Let’s make the Auction view the startup view. In OnLaunched() in App.xaml.cs, instead of ContinueToMain(), call the ContinueToAuction() method that we just generated with the New-View command:
With the navigation to the Auction view in place, let’s first complete the Auction viewmodel.
Completing the viewmodel
The generated AuctionViewModel.cs contains an example Count property and an InCreaseCountCommand. Let’s remove those and add our own data-bindable properties and commands. Looking at the Auction view design mockup, we can identify these properties and commands:
Properties: Name, Intro, Image, Description, CurrentPrice, TimePercentageRemaining, ListPrice, AvailableCount, Condition, TimeRemaining.
Commands: Logout, PlaceBid.
Since all properties are display-only, we will use the propdb1 code snippet to add them as one-way data-bindable properties. Here is how we add a property:
On an empty line, type the propdb1 shortcut. Notice the intellisense:
Press the TAB key to invoke the code snippet. The first code snippet parameter will be selected; notice the intellisense on the code snippet parameters:
You can cycle through the snippet parameters with the TAB key. Since string is the data type we want, TAB to the property name, type Name and press enter to complete the snippet:
As you can see, adding a data-bindable property requires no more effort than adding a field or a standard property (with the standard prop code snippet). You will notice that after the comment there are many statements of generated code; see this explanation for the reasoning behind this approach. If you prefer to use regions or other code formatting then you can easily modify the code snippets file to your liking.
It only takes a minute to add all properties this way:
Since the commands do not require a parameter, we add them with the cmd code snippet:
You only need to enter the command name:
Just type Logout and press enter to complete the command. The generated method contains a reminder to implement the command, both runtime with an exception and in the Task List in Visual Studio:
After adding all commands, I prefer to move the implementation methods outside the data-bindable properties and commands region, to keep a compact overview of available data-bindable items:
Adding design data
You can use design viewmodel classes that inherit from the normal viewmodels to test the view layout and data binding. This speeds up implementing views on platforms that support design-time data (i.e. Windows Store and Windows Phone). By initially using the design viewmodels at runtime as well, we can quickly prototype the app on all platforms. Later we can add models and services and switch to the normal viewmodels at runtime.
The AuctionViewModel.cs file already contains a ViewModels.Design sub-namespace with an AuctionViewModelDesign class that inherits from AuctionViewModel.
Note that you can add multiple design classes here, to quickly test different states of your viewmodel. E.g. you could have classes AuctionViewModelDesignLoggedIn and AuctionViewModelDesignNotLoggedIn. This is useful e.g. when the visibility of certain controls in the view is bound to a property on the viewmodel.
Let’s add some hardcoded data to the AuctionViewModelDesign class:
Build the solution; there should be no errors. Now we are ready to complete the Auction view that goes with this viewmodel.
Completing the view
The New-View command generated the AuctionView.xaml view markup file. Let’s inspect this and see what makes it tick. If you open AuctionView.xaml in the VS XAML designer, we can see in the source pane that a vm xml namespace is defined for the ViewModels.Design namespace:
Also, a design-time viewmodel is defined:
Notice how using a Design sub-namespace gives us a nice list of all design viewmodels in intellisense, when we edit this line. This allows us to quickly switch between different design scenarios without running the app, by switching between design viewmodel classes that represent different view states (i.e. different controls become visible because their visibility is data-bound to viewmodel properties).
Now we can add controls to bind all properties and commands; just enough XAML to prototype the app and to test the shared code. While you edit the XAML, you will see the data-bound property values displayed:
You need to use a debug build (because we put the design viewmodel class inside #if DEBUG … #endif) and since the designer uses the viewmodel from the last built assembly, you need to recompile when you change the (design) viewmodel. It does save time when you can create your views without starting or even compiling the app. Note that Visual Studio 2013 also provides intellisense on your viewmodel properties and commands when you type XAML data-binding paths. More speed J.
This is the XAML for the Auction view, data-bound to AuctionViewModelDesign, and how it looks in the designer:
The New-View command added some navigation methods and properties to the navigator and application classes. Let’s inspect those and to see how they work.
In the ICloudAuctionNavigator interface in the shared code project, a cross-platform method declaration was added to navigate to the auction view from shared code:
In the CloudAuction project, a CloudAuctionNavigator class was generated that implements the ICloudAuctionNavigator interface for the Windows Store platform:
The Navigator knows the navigation context object type for this platform (a Frame in Windows Store). The Navigator simply navigates to the view; it does nothing with the viewmodel – that is the responsibility of the Application class in the shared code project.
The cross-platform Application class provides access to the viewmodel instances and provides methods to navigate to each view. Each navigation method creates and initializes the viewmodel(s) for the view as needed, and then uses the INavigator interface to let the platform-specific Navigator do the actual navigation.
This is the code for the Auction viewmodel + navigation that was added to the CloudAuctionApplication class in the CloudAuction.Shared.ws project:
Note that we instantiate an AuctionViewModelDesign instead of an AuctionViewModel – we use the design viewmodels for rapid prototyping on all platforms, even before we have any models or services to initialize the normal viewmodels with.
In AuctionView.xaml.cs, code was generated to set the runtime data binding to the correct viewmodel:
The partial class in CloudAuction\MvvmQuickCross\App.xaml.cs contains the code to create the application instance:
This is the place where we instantiate any platform-specific service implementations, such as the Navigator, and pass these service instances to the Application constructor.
In the standard partial class file CloudAuction\App.xaml.cs, in the OnLaunched method, we replaced this code:
to initialize the application instance and navigate to the Auction view on application startup.
Set the CloudAuction project as the startup project and then build and run the application. You should see this:
All data comes from the design viewmodel. When you click a button, the command on the viewmodel will be invoked (and you will get a NotImplementedException; we still have some work to do).
Adding a second viewmodel with its view and navigation
Now let’s add the Order viewmodel. In the Package Manager Console, enter:
Then in the generated OrderViewModel.cs, we add the properties that we identify in the design mockup. We create properties that the user can edit with the propdb2 code snippet, which creates a two-way data bindable property.
This is what the complete viewmodel looks like:
Note that the Initialize method is an example of how the viewmodel can receive parameters from the Application when the Application navigates to the view; the bid is a simple data transfer object, defined in the ViewModels namespace:
Let’s add some design data in the OrderViewModelDesign class:
Now we build the solution, and we complete the generated Order view to bind it to OrderViewModelDesign:
The code to navigate to the Order view and to instantiate the view model was generated for us:
We only need to call the generated navigation code somewhere. Let’s navigate to the Order view when a bid is placed in the Auction viewmodel:
Now we can run the app and navigate between the Auction and Order views with the Place Bid Now and Cancel buttons:
You can edit all values and the viewmodel will be updated automatically through the two-way data binding.
Adding the third view… as a Dialog
So far, we have used the iPhone design mockup to implement our views. Now let’s see whether the MvvmQuickCross navigation pattern is flexible enough to make a very different platform specific view implementation. We will implement the OrderResult viewmodel exactly as needed for the iPhone style view in the mockup. However, we will implement the Windows Store OrderResult view and the navigator method NavigateToOrderResultView so that it will display a dialog instead of a new page.
We don’t want to generate the view, but we do want to generate the viewmodel and the cross-platform navigation code. We do this by using the New-ViewModel command instead of the New-View command. In the Package Manager Console, enter:
The shared navigation code is generated for us:
Let’s navigate to the OrderResult view from the Order viewmodel:
Now we code our custom platform specific bits, which make this view a dialog instead of a page:
As you can see, the view code simply uses the Message property and the DoneCommand on the viewmodel through code instead of data binding.
Now if we run the app, place a bid and confirm, we see this:
And when we select the Close button, we navigate back to the Auction view.
In this example, we created a prototype Windows Store app with shared code that is also usable for iOS and Android apps. Even if you only need to deliver iOS and/or Android apps, it may still be rewarding to let non-mobile .NET developers prototype the app in Windows Store: for experienced Microsoft developers it is a very productive platform. The shared code can be created and tested by them without the need for Xamarin, iPhone or Android development skills, licenses or hardware. After the functional prototyping and shared code testing, the mobile devs can quickly build the iPhone or Android platform specific app. This approach may be appealing for Microsoft development shops that have a limited number of Xamarin mobile developers.
In following blog posts, we will expand this example by adding iOS, Android and Windows Phone apps using the same shared code. We will also show how to use composite views to increase code reuse across different screen sizes and orientations.