In this series I will try to demonstrate the MVVM pattern and how it can be applied to cross platform applications using Windows Runtime and Xamarin for android. Full sample can be downloaded here.
Table of Contents
Android Application
Continuing the previous implementation that we prepare for window universal application and the core implementation of view models and the data objects, we will try to implement the android application reflecting the same structure of navigation.
First line of business would be to setup the hub page. In the hub page we used a Hub control. The easiest and quickest way to replace this view on our Android application would be to use a tabbed view together with couple of fragments, mapping the sections of the hub page to these fragments.
NOTE:We could have used a pager as well, and in fact it might have been a better fit for this implementation but let us leave the pager for another post.. |
Setting Up the Fragments
For this implementation we will need three fragments. The first fragment would display the list of group data items (Resources/layout/fragmentgroupslist.axml).
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:local="http://schemas.android.com/apk/res-auto" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent"> <Mvx.MvxListView android:padding="10dp" android:layout_width="fill_parent" android:layout_height="match_parent" android:orientation="vertical" local:MvxBind="ItemsSource Groups; ItemClick NavigateToGroupCommand" local:MvxItemTemplate="@layout/listtemplategroupitemview" /> </LinearLayout>
The highlighted lines of code are important because while the first one is creating the bindings:
- ItemsSource Binding for the source data, in our case the list of Group items (remember our viewmodel)
- ItemClick The command that is to handle the ItemClick event. Notice that we are not passing any parameter, the current binding context item is going to be passed by MVX framework.
Second line is the reference to the item data template to be used for the MvxListView control. This implementation is in fact very similar to the item template that we defined for the windows phone version of the application. In this case, we will use a simple text view control to display the title of the group.
For the second (and third) fragments, we will be creating a list view that will display the list of items that belongs to the data group. In order to do this, let us first define the group item detail data template (Resources/layout/listtemplateitemview.axml) to be used with the list view (cheating from the windows phone design – to display the item title and image):
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:local="http://schemas.android.com/apk/res-auto" android:orientation="horizontal" android:layout_width="match_parent" android:layout_height="wrap_content" android:background="#ffb300" android:padding="5dp"> <ImageView android:layout_width="50dp" android:layout_height="50dp" local:MvxBind="AssetImagePath ImagePath, Converter=AssetsConverter" /> <LinearLayout android:orientation="vertical" android:layout_width="0dp" android:layout_weight="1" android:layout_height="wrap_content"> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" local:MvxBind="Text Title" /> </LinearLayout> </LinearLayout>
Here, while we are using the standard “Swiss” binding syntax for the other items, we are using a converter for the image binding. The main reason for this was that the data source in the initial project defined the image path as “../Assets/”, while in the android project all the AndroidAssets will be retrieved only with the image name since we moved all the image items to the assets folder and marked the as “AndroidAsset”.
We will use a simple string converter to adjust the image path for our android application (Converters/AssetsPathConverter.cs):
public class AssetsPathConverter: IMvxValueConverter { #region Implementation of IMvxValueConverter public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { return value.ToString().Replace("../Assets/", string.Empty); } public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { return value; } #endregion }
Now we will need to override the ValueConverterHolders property in the MVX setup implementation returning the assets path converter.
And the complete fragment using the data template we prepared would look similar to (Resources/layout/fragmentfirstgroup.axml):
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:local="http://schemas.android.com/apk/res-auto" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent"> <Mvx.MvxListView android:layout_width="fill_parent" android:layout_height="wrap_content" android:orientation="vertical" local:MvxBind="ItemsSource Groups[0].Items; ItemClick NavigateToItemCommand" local:MvxItemTemplate="@layout/listtemplateitemview" /> </LinearLayout>
Notice that we are binding directly to the first group and we are using another fragment design for the second group view. This can be changed so that the fragment is reused by creating a viewmodel for the group data model and initializing the tab host with this new view model.
The utilization of these fragments we prepared would be a simple implementation of MvxFragment simply inflating the view using the bindings we prepared:
public class GroupsFragment : MvxFragment { public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { base.OnCreateView(inflater, container, savedInstanceState); var view = this.BindingInflate(Resource.Layout.fragmentgroupslist, null); return view; } } public class FirstSectionFragment : MvxFragment { public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { base.OnCreateView(inflater, container, savedInstanceState); var view = this.BindingInflate(Resource.Layout.fragmentfirstgroup, null); return view; } }
Creating the Hub View and Adding Tabs
Now creating the tab host and adding the fragments:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:local="http://schemas.android.com/apk/res-auto" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent"> <LinearLayout android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="wrap_content"> <TabHost android:id="@android:id/tabhost" android:layout_width="fill_parent" android:layout_height="fill_parent"> <LinearLayout android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent"> <TabWidget android:id="@android:id/tabs" android:orientation="horizontal" android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_weight="0" /> <FrameLayout android:id="@android:id/tabcontent" android:layout_width="0dp" android:layout_height="0dp" android:layout_weight="0" /> <FrameLayout android:id="@+id/actualtabcontent" android:layout_width="fill_parent" android:layout_height="0dp" android:layout_weight="1" /> </LinearLayout> </TabHost> </LinearLayout> </LinearLayout>
Here the highlighted line defines where the fragments we prepared are going to go into:
[Activity(Label = "Main", MainLauncher = true, Icon = "@drawable/icon")] public class HubPageView : MvxTabsFragmentActivity { public HubPageViewModel ViewModel { get { return (HubPageViewModel)base.ViewModel; } } public HubPageView() : base(Resource.Layout.HubPageView, Resource.Id.actualtabcontent) { } protected override void OnCreate(Bundle bundle) { base.OnCreate(bundle); } protected override void AddTabs(Bundle args) { AddTab<GroupsFragment>("1", "Groups", args, this.ViewModel); AddTab<FirstSectionFragment>("2", "Group 1", args, this.ViewModel); AddTab<SecondSectionFragment>("3", "Group 2", args, this.ViewModel); } }
As defining the actualtabcontent id as the container for the hub view, we are preparing the frame for the fragment.
The final product is what we were actually after while were beginning this implementation.
The extended implementation can be found in the code repository referenced in the beginning.
In the next post we will be preparing the iOS application.
Happy coding everyone,