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.
In part 1 of this blog post series, we created the cross-platform C# code of an example Cloud Auction app using the MVVM pattern. We used the open-source MvvmQuickCross to quickly code the viewmodels and to share as much of the code as possible across platforms.
In this post we are going to build the Android version of the Cloud Auction app, which will use the Android data binding and View scaffolder feature of MvvmQuickCross and the cross-platform Cloud Auction code that we created in part 1. We will create a composite view, a data-bound form with lists, and add data binding support for a custom view type – with very little code. Note that the complete source code for this app is available here.
What we have as input is the cross-platform code and the design mockups of the app screens and navigation (for iPhone):
And this is what the Android app will look like:
So let’s get started.
Creating the solution structure
We will follow the MvvmQuickCross guidance for adding a platform. First, we will create a separate solution for the Android version of the app.
In a temporary folder, create an Android Application project named CloudAuction, in a solution with the same name:
Remove the Activity1.cs and the Resources\Layout\Main.axml files, and set the API Level to 12 (or higher) in the project properties:
Add an Android Class Library project to the solution, and name it CloudAuction.Shared:
Remove the Class1.cs file, and set the API Level of the library project to the same level as that of the application project:
In the solution explorer, rename the solution to CloudAuction.android and rename the CloudAuction.Shared project to CloudAuction.Shared.android:
Finally, rename the CloudAuction project folder to CloudAuction.android: remove the CloudAuction project from the solution, rename the project folder in file explorer:
and re-add the existing CloudAuction project to your solution.
Now add a reference from the CloudAuction project to the CloudAuction.Shared.android project:
and we set the default namespace and the assembly name for both projects so that the assembly name includes the .android 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.
Download the MvvmQuickCross repository as ZIP from here (select the DownLoad ZIP button at the bottom right of the page).
In Windows Explorer, right-click the downloaded ZIP file and select Properties. If you see an Unblock button in the Properties window, select it:
Now open the ZIP, navigate to the Examples\CloudAuction folder and copy the .nuget and CloudAuction.Shared folders:
In Windows Explorer, create a new CloudAuction solution folder (e.g. C:\Projects\CloudAuction) and then paste the .nuget and CloudAuction.Shared folders in it.
Now copy the Android solution file and the Android application project folder from our temporary CloudAuction solution folder to the new CloudAuction solution folder:
Copy the Resources folder and the Android project file from the CloudAuction.Shared project folder as well:
Now open the copied Android solution, and in the solution explorer set the CloudAuction project as startup project, and include the existing shared code files in the CloudAuction.Shared.android project:
From the Visual Studio Package Manager, make sure that the MvvmQuickCross NuGet package is installed in the solution by running Install-Package MvvmQuickCross, and then run the Install-Mvvm command:
Amongst other things, the Install-Mvvm command adds android data binding support to the application project and it and sets the __ANDROID__ conditional compilation symbol in the project properties. We need this symbol because the Properties\AssemblyInfo.cs file in the temporary CloudAuction.Shared.android project contains some Android-specific attributes:
So now we can add these attributes to the existing cross-platform AssemblyInfo.cs, within an #if __ANDROID__:
In the CloudAuctionNavigator.cs in the CloudAuction project, remove the NavigateToMainView method; it was generated as an example but we don’t need that, since we already have a complete ICloudAuctionNavigator in our shared code.
Now implement the ICloudAuctionNavigator interface in the CloudAuctionNavigator:
This code will be added:
We will complete this code later; for now you should be able to build the solution without errors.
At this point, we have arrived at the last step of adding a platform, where the real work is done: coding the views and the navigator.
Using MvvmQuickCross Commands in the Package Manager Console
Before we start creating the app, here are some tips for working with MvvmQuickCross commands in the Visual Studio Package Manager Console.
- When the Package Manager window loads for the first time in Visual Studio, it displays the syntax of the available commands in the MvvmQuickCross module:
- To view the available MvvmQuickCross commands and specify command parameters in a GUI, enter Show-Command and select the module MvvmQuickCross:
- In the console, enter Get-Help <command > -Online to view detailed online command help. E.g.:
Get-Help New-View -Online - The package manager console has tab completion; you can type New-<tab>, or New-View -<tab> to get a list of command names or command parameters:
Creating the Views
In the design mockups above, we can identify these views:
- A composite Main view with contained views Auction, Product and Help (the Product and Help views are not detailed in the design, we won’t build them in this blog post).
- An Order view
- An OrderResult view
Creating the Main view
The Main view initializes the application and provides navigation between the Auction, Products and Help sub-views through a standard Android ActionBar with tabs. The Main view also shows the Logout command in the Options menu.
The Main view is a composite view, i.e. it contains other views. You can use view composition to re-use the same markup elements for different screen orientations and sizes. I.e., you can design your sub-views, view models and navigation for the smallest screens, and on larger screens you can then display more sub-views on the same screen.
An example Main view was generated for us by the Install-Mvvm command, so we do not need to add the view files. Replace the example Main view code in the file MainView.cs with this code:
Note that most code in this view is standard Android code for the ActionBar and the Options menu. The only code that is specific to MvvmQuickCross is:
- The view base class, which specifies the view type and the viewmodel type:
- The MvvmQuickCross application and helpers initialization, which only needs to be done once at the start of the app lifecycle:
-
The view data binding initialization, which is done in the view’s OnCreate() or OnCreateView():
As parameters we pass:- The root view, which is used by MvvmQuickCross to find data-bound child views with FindViewById().
- The viewmodel instance, which is available after the call to Initialize() in the ViewModel property of the view base class:
- More optional parameters exist, e.g. to specify data binding in code instead of in markup. For a complete description, see Android Binding Parameters in Code in the MvvmQuickCross Readme.
-
The code that executes the LogoutCommand in the Main viewmodel:
(Note that it would be fairly straightforward to implement data binding of commands to Android menus in MvvmQuickCross – then we would not need to write this code)
Implement the NavigateToMainView() method in CloudAuctionNavigator.cs, so we can navigate to the Main view from code, like this:
As you can see, we tell the Main view which sub-view we want displayed before we navigate to it, with a static variable. Then when the Main view activity’s OnResume() method is called, it will switch the active tab and corresponding fragment to the indicated sub-view, if needed.
Replace the example Main view markup in the file Resources\Layout\MainView.axml with this markup:
Also, create a subfolder Menu in the Resources folder and add an xml file named MainMenu.xml there:
Put this markup in the MainMenu.xml file:
Now you can run the app. Since we did not yet implement the Auction sub-view, it will simply display the three tabs and the Logout command:
Creating the Auction View
The Auction view is a sub-view which is shown in the Auction tab of the Main view. Since the Auction view is not a full-screen view but part of a screen, we implement it as an Android Fragment.
To create the Auction view files, open the Package Manager Console in Visual Studio (menu View | Other Windows) and enter:
New-View Auction -ViewType Fragment -WithoutNavigation
We specified the –WithoutNavigation switch because the CloudAuctionApplication and ICloudAuctionNavigator in our shared code already contain navigation code for the Auction view.
We do need to modify the generated AuctionView.cs slightly to use our existing navigation method. Change this line:
CloudAuctionApplication.Instance.ContinueToAuction(skipNavigation: true);
into this:
CloudAuctionApplication.Instance.ContinueToMain(MainViewModel.SubView.Auction, skipNavigation: true);
Now that we have an Auction sub-view, we can create it in the Main view. Change this line in MainView.cs:
ActionBar.NewTab().SetText(“Auction”).SetTag(new Fragment()),
into this:
ActionBar.NewTab().SetText(“Auction”).SetTag(new AuctionView()),
Now if you run the app, the Auction tab will display the example markup that was generated in the Auction sub-view:
So let’s replace the example markup in Resources\Layout\AuctionView.axml with the real deal:
Note that in the Auction view markup, we use the MultiImageView component from the Xamarin store:
So let’s install that: in the CloudAuction project in the Solution Explorer, right-click the Components folder and select Get More Components
The Xamarin Components Store window will be displayed. Search for MultiImageView, select it and select Add to App:
Now if you run the app, you will see an (almost) fully functioning Auction sub-view:
All information in this view actually comes from the viewmodel, without any specific code in the view.
Note how the auction price, the progress bar and the time remaining are updated each second.
What makes this work, is the Android Id naming convention that MvvmQuickCross uses for data binding.
Note how the view id’s in the view markup correspond to the viewmodel properties:
MvvmQuickCross lets you specify data bindings with (a combination of) naming convention, parameters in Tag attributes in the markup, and/or code. See here for details.
The only thing lacking in the Auction view is that the MultiImageView displays a default image instead of the image whose Uri is set in the Image viewmodel property. This is because MvvmQuickCross does not have built-in data-binding support for views of type MultiImageView (these view types are supported out of the box). So let’s add that.
Adding data binding support for MultiImageView
Fortunately, adding data binding support to MvvmQuickCross for new view types is simple. To add one-way data binding support for MultiImageView:
in the CloudAuction project,
in the MvvmQuickCross\ViewDataBindings.UI.cs file,
in the UpdateView(View view, object value) method,
in the switch (viewTypeName) statement,
below the comment that says // TODO: Add cases here for specialized view types, as needed
insert this case statement:
Note that it only takes a couple lines of code to add data-binding support for a specific view type; see here for more details.
Run the app again; you will see the actual product image displayed. The Auction sub-view is now fully functional.
Creating the Order view
Looking at the design mockups, we see that the Order view is a fullscreen view, so we will create it as an Android Activity. If you type Get-Help New-View –Online in the Package Manager Console, you will see that Activity is the default for Android views, so we can simply enter New-View Order –WithoutNavigation:
The view code and markup files are generated and opened for you. The generated view code is all we need to make this Order form fully functional. It contains only one MvvmQuickCross-specific line of code:
Now we can implement the navigation code for the Order view; build the solution and implement the NavigateToOrderView() method in CloudAuctionNavigator.cs like this:
Replace the example markup in OrderView.axml with this markup:
The Order view contains two lists, so we also need markup for a list item. In the Resources\Layout folder, add a new Android Layout named TextListItem.axml and put this markup in it:
Run the app and select Place Bid Now to navigate to the Order view. The Order view is now fully functional:
All lists, editable items and actions are data-bound to the Order viewmodel, through id naming convention and binding parameters in the markup tags. Let’s look at some examples of data binding in this markup.
This is how the First name EditText is two-way data-bound in markup:
And this is how the Confirm Button is data-bound to a command in markup:
This command binding will cause the Confirm() method on the viewmodel to be called when the button is clicked, and it will also set the button enabled / disabled as indicated by the IsEnabled property of the ConfirmCommand.
Finally, this is how the Deliver Spinner is data-bound in markup:
If we include the default values for all binding parameters in the tag attribute, it looks like this:
And this is how the binding parameters relate to the viewmodel and item template:
Here is how MvvmQuickCross handles data binding. First, the id attribute determines that this spinner is the view that is bound to the DeliveryLocation property in the viewmodel. Then, the actual binding parameters are read from the tag attribute. Any parameters that are not specified have smart default values, based on naming conventions. If an indicated viewmodel property, template file or view id is not found, there will be no errors at runtime; the view will display as well as it can.
In this case, the Spinner is bound to two properties: the DeliveryLocation for the selected item, and the DeliveryLocationList for the list items. The selected item of the spinner is bound two-way to the DeliveryLocation property. Because it is two-way, the Spinner will not only show the DeliveryLocation as the selected item when the viewmodel property is set from code (this is one-way binding), but also the viewmodel property will be set when the user selects an item in the Spinner.
For more details on binding parameters, see the reference in the MvvmQuickCross readme.
Creating the OrderResult view
Looking at the design mockups, we see that the OrderResult view is also a fullscreen view, so we will create it as an Android Activity. In the Package Manager Console, enter New-View OrderResult –WithoutNavigation:
Build the solution and implement the NavigateToOrderResultView() method in CloudAuctionNavigator.cs like this:
Replace the example markup in OrderResultView.axml with this markup:
This markup refers to a Checkmark image. Download the Checkmark.png file to the Resources\Drawable folder and include it in the project:
Run the app, select Place Bid Now in the Main view to navigate to the Order view, and then select Confirm to navigate to the OrderResult view:
When you click the Done button, you will navigate back to the Main view. The Android version of the Cloud Auction application is now fully functional.
Closing Remarks
In this example, we created the Android version of the Cloud Auction application with MvvmQuickCross. We also created a composite view, and we added data binding support for a view type. All this required little android-specific coding.
In following blog posts, we will expand this example by adding iOS and Windows Phone apps using the same shared code.
I like the idea of this “small” MVVM framework very much.
May I make a suggestion (perhaps for the V2 release): instead of the “PROPERTYNAME_” public fields I would suggest the use of an attribute on the bound properties. Concise and clear.
For instance, suppose you introduce a [QMVVMCross] attribute (not on 1 line here for clarity):
[QMVVMCross]
public string Description { /* Two-way data-bindable property generated with propdb2 snippet */
get { return _Description; }
set {
if (_Description != value) {
_Description = value;
RaisePropertyChanged (“Description”);
}
}
}
Besides the trivial new QMVVMCrossAttribute class you only need the following change in RaisePropertiesChanged():
…snip…
foreach (var propInfo in this.GetType().GetProperties(BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance | BindingFlags.FlattenHierarchy)) {
if (propInfo.IsDefined(typeof(QMVVMCrossAttribute))) {
propertyNames.Add((string)propInfo.Name);
}
}
…snip…
What do you think?
Danny
Good to hear that you like the framework, Danny! Your suggestion would indeed result in a cleaner implementation of the properties and of the RaisePropertiesChanged() method.
However the PROPERTYNAME_ fields serve mainly to provide intellisense (and to avoid typing property names in strings) when you write custom code in the OnPropertyChanged(string propertyName) method of your view, e.g. like here: https://github.com/MacawNL/MvvmQuickCross#customizing-data-binding-in-android-views
I’m trying to make MvvmQuickCross as developer friendly as possible, whether you use markup, code or customization code. Eliminating the PROPERTYNAME_ fields would go against that.
However, I really appreciate any feedback or ideas so feel free if you have more!
Thx, Vincent
Hi Vincent,
Good to see that QuickCross is now available with IOS support.
And your arguments in favor of the PROPERTYNAME_ fields are valid, not having to type the property names as a string is a good thing!
Thanks for sharing!
Danny