MvvmCross Unified Xamarin.Android ViewPager and Xamarin.iOS UIPageControl/UIScrollView Example

This article provides a walkthrough for implementing a common MvvmCross.Core project for a Xamarin.Android and Xamarin.iOS applications allowing one to bind to Android’s ViewPager and use a UIPageControl/UIScrollView in iOS.

Motivation

In my app Goal Weight, I used a UIPageControl/UIScrollView combination to show the user three different graph views on the weight entry page.

graphs

Goal Weight was published before I discovered MvvmCross, so when I needed to implement a UIPageControl/UIScrollView in my latest MvvmCross application I did not have any source code to pull on.

My goal with my new application is to get the same functionality I had in Goal Weight using the MvvmCross framework.

Resources

Xamarin.Android Implementation

@Cheesebaron did all of the leg work creating a custom binding for the Android ViewPager. My goal with the iOS implementation was to take the ViewModels he created and make sure I can use them in iOS. I did not dive deep in to his Android binding implementation, I did test his sample application, and it worked as of the writing of this article.

Xamarin.iOS Implementation

Initially, I thought I was going to have to create a custom binding for the UIPageControl and UIScrollView controls, turns out I only needed to implement a custom view presenter.

ViewModels

We have two ViewModels in the sample application:
* SimpleListViewModel – holds all of the pages we are about to create
* SimpleViewModel – the page we are showing in the UIPageControl/UIScrollView

SimpleView

SimpleView is the page we are going to show, in this example it binds to the ViewModel's Name property, just to show that we have successfully bound to the ViewModel.

SimpleViewPagerView

SimpleViewPagerView holds the UIPageControl/UIScrollView and all of the logic to create the given number of SimpleViews defined in SimpleListViewModel.

All of the setup work takes place in ViewWillAppear. Below is a modified listing for brevity, see the GitHub repo for the full code listing.

public override void ViewWillAppear(bool animated)     
{
    base.ViewDidAppear(animated);   
    pageControl.ValueChanged += HandlePageControlValueChanged;
    scrollView = new UIScrollView()
    {
        ShowsHorizontalScrollIndicator = false,
        ShowsVerticalScrollIndicator = false,
        Bounces = true,
        PagingEnabled = true,
        Frame = UIScreen.MainScreen.Bounds
    };
    scrollView.Scrolled += HandleScrollViewDecelerationEnded;
    pageControl.Frame = new RectangleF(0, scrollView.Bounds.Bottom - 10, scrollView.Bounds.Width, 10);
    View.AddSubviews(scrollView, pageControl);

    int i;
    for (i = 0; i < ViewModel.Items.Count; i++)
    {
        var pageViewController = CreatePage(ViewModel.Items[i]);
        pageViewController.View.Frame = new RectangleF(UIScreen.MainScreen.Bounds.Width * i, 0, UIScreen.MainScreen.Bounds.Width, UIScreen.MainScreen.Bounds.Height);
        scrollView.AddSubview(pageViewController.View);
    }
    scrollView.ContentSize = new SizeF(UIScreen.MainScreen.Bounds.Width * (i == 0 ? 1 : i), UIScreen.MainScreen.Bounds.Height);              
    pageControl.Pages = i; 
}

First we create our UIPageControl and UIScrollView and then iterate through the SimpleListViewModel's Items which holds the pages we want to create, create those pages, and lastly add the created pages to the UIScrollView.

The trickiest part of ViewWillAppear is where we set he location of our page within the UIScrollView, we have to set the starting x position based on the number of the page we just created:

pageViewController.View.Frame = new RectangleF(UIScreen.MainScreen.Bounds.Width * i, 0, UIScreen.MainScreen.Bounds.Width, UIScreen.MainScreen.Bounds.Height);

Lastly we tell our UIPageControl how many pages we created and set the ContentSize of the UIScrollView accordingly:

scrollView.ContentSize = new SizeF(UIScreen.MainScreen.Bounds.Width * (i == 0 ? 1 : i), UIScreen.MainScreen.Bounds.Height);

We create the pages by using a MvvmCross built in function CreateViewControllerFor that takes a ViewModel and will create the View that goes along with it:

private UIViewController CreatePage(IMvxViewModel viewModel)
{
    var controller = new UINavigationController();           
    var screen = this.CreateViewControllerFor(viewModel) as UIViewController;              
    controller.PushViewController(screen, false);           
    return controller;
}

The only items left to do are to hook up the scroll events for the UIScrollView and UIPageControl, see the source code on GitHub for the full implementation.

Summary

Using @Cheesebaron‘s custom Android bindings, and a custom presenter for iOS one can design a common MvvmCross core project that can feed both a Xamarin.Android and a Xamarin.iOS applications that use ViewPager and UIScrollView/UIPageControl respectively.

Posted in mvvmcross, xamarin, Xamarin.Android, Xamarin.iOS Tagged with: , , ,

Use Asset Catalogs for a View’s Background Image in Xamarin.iOS

In my latest app Goal Length, I added the ability for the user to enable Touch ID / Passcode protection to launching the application. A user cannot open the application unless they authenticate via Touch ID or using their device’s Passcode.

For a smooth user experience while authenticating I wanted the application launch image to be displayed behind the Touch ID / Passcode dialog. From the user’s perspective the application would launch, my launch image would be shown while the application is launching, the application would finish launching, I’d load the launch image as the background of my authentication View, and then the Touch ID / Passcode dialog box would pop up asking the user authenticate.

IMG_4360

In this article I’ll cover how to successfully use Asset Catalogs for launch images and loading the proper background image for a given device. This will allow you to transition from a launch image to your first View while retaining the launch image in the background. Be advised, this technique works well most of the time, it fails when devices have the same resolution, but different physical dimensions, like the iPhone 5s and iPhone 6.

Resources

Why Asset Catalogs

Simply, asset catalogs make our lives easier. For this project I’ll be loading a different image based on the resolution of the device. Without an Asset Catalog I have to maintain a semi-complex if/else structure checking the device height for each of our possible devices. Before discovering Asset Catalogs my code looked something like this:

UIImage backgroundImage = null;

if (UIScreen.MainScreen.Bounds.Height == 480)
    backgroundImage = UIImage.FromFile (@"Icons/Default@2x.png");
else if (UIScreen.MainScreen.Bounds.Height == 568)
    backgroundImage = UIImage.FromFile (@"Icons/Default-568h@2x.png");
else if (UIScreen.MainScreen.Bounds.Height == 667)
    backgroundImage = UIImage.FromFile (@"Icons/Default-667h@2x.png");
else if (UIScreen.MainScreen.Bounds.Height == 736)
    backgroundImage = UIImage.FromFile (@"Icons/Default-736h@3x.png");
else
//etc for each iPad resolution

View.BackgroundColor = UIColor.FromPatternImage(backgroundImage);

Note

In addition, and I’m not sure if this is a Xamarin bug or not, the files for each one of the devices needs to be named in the Default@2x.png format for each of the different resolutions. I had originally named the files their actual resolution names, i.e. Default@2x.png was named 640x970.png, but the scaling of the image in the background was completely wrong.

Even though we could solve our problem this way, we can do better.

Changing your Application to use Asset Catalogs

I’ll be working in Xamarin Studio for this article, as of this writing and Xamarin.iOS release this cannot be done in Visual Studio.

In Xamarin Studio first ensure your application is setup to use Asset Catalogs by right clicking on your project, select Options, from the Project Options select Build/iOS Application and scroll down to Universal Icons and Universal Launch Images and ensure Source is set properly for each.

Screen Shot 2014-11-18 at 10.26.19 AM

This will create a Resources folder in your application along with AppIcons.appiconset and LaunchImage.launchimage folders. Instead of adding your app icons and launch images in the project options we’ll now add them via the Resources/LaunchImage.launchimage/Contents.json file. Double click on Resources/LaunchImage.launchimage/Contents.jsonand let’s set our launch images.

Setting our Launch Images

First we are presented with a bunch of empty slots with resolution requirements for each device, and the iOS version the resolution corresponds to.

Screen Shot 2014-11-18 at 9.26.10 AM

In this example application I plan to support the following devices in portrait mode:

  • iPhone 4s
  • iPhone 5 / 5s
  • iPhone 6
  • iPhone 6 Plus
  • iPad 2 / iPad Mini
  • iPad Retina (iPad 3, iPad4, iPad Air, iPad Air 2)

So I’ll need to fill in each of the launch images for each of the devices.

Screen Shot 2014-11-18 at 9.27.01 AM

If we run our application we now have a launch image for each one of the devices.

Adding a Background Images Asset Catalog

Let’s now add our Background Images Asset Catalog. I can hear you saying, “Why not just use the Launch Image Asset Catalog as the source of our background images?” Believe me, I tried going down this path. Stay with me, you’ll see shortly why this does not work.

Right click on Resources/Images.xcassets and select Add/New Image Set.

Screen Shot 2014-11-18 at 10.35.35 AM

Rename the image set to BackgroundImages.imageset and double click on the Contents.json file. We’ll do the same procedure we did for the Launch Images asset catalog and fill in the following:

  • 2x iPhone
  • 3x iPhone
  • 1x iPad
  • 2x iPad

Screen Shot 2014-11-18 at 9.24.56 AM

We leave the R4 image empty, this would be the image that is set for the iPhone 5s / iPhone 6, it is shared by the iPhone 5s / iPhone 6.

As best as I can tell, since the overall resolutions for the iPhone 5s and iPhone 6 are identical, they are viewed as the same in the eyes of the Asset Catalog, if the user has an iPhone 5 or iPhone 6 the Asset Catalog will display the image in the R4 slot. This might be fine for most applications, but for us we are supplying an image that takes up the entirety of the screen, and we need two different images. Hence the Asset Catalog we just created cannot be leveraged by the iPhone 5s / iPhone 6 in this instance.

One would think the Launch Image Asset Catalog would take care of this problem for us, alas, no matter what I tried I could not make the Launch Image Asset Catalog select the proper image when running on the iPhone 5s or the iPhone 6.

Using Background Images Asset Catalog

Once we have everything configured in our project using the Background Images Asset Catalog using them in code is fairly straight forward. We’ll account for the fact we cannot load the proper images for the iPhone 5s / iPhone 6 from the Asset Catalog by checking the device’s Height. If the Height matches the iPhone 5s / iPhone 6 we’ll use a UIImage.FromFile call to load the proper image.

UIImage backgroundImage = null;
if (UIScreen.MainScreen.Bounds.Height == 568)
    backgroundImage = UIImage.FromFile(@"Icons/Default-568h@2x.png");
else if (UIScreen.MainScreen.Bounds.Height == 667)
    backgroundImage = UIImage.FromFile(@"Icons/Default-667h@2x.png");
else
    backgroundImage = UIImage.FromBundle("BackgroundImage");
View.BackgroundColor = UIColor.FromPatternImage(backgroundImage);

Remember, the iPhone 5s and iPhone 6 files need to be named Default-568h@2x.png and Default-667h@2x.png respectively, else they will not load properly.

Once implemented our user will launch the application, see the launch image from the Launch Image Asset Catalog, and then be shown the image from the Background Image Asset Catalog. Had I also implemented Authentication in Xamarin.iOS with Touch ID or Passcode, I could automatically present the user with the Touch ID / Passcode dialog box, thus creating a seamless transition into our application.

In the demo app you’ll know you are looking at the image from the Background Image Asset Catalog because I added an authentication button at the bottom of the screen.

iOS Simulator Screen Shot Nov 18, 2014, 7.20.00 PM

Summary

Asset Catalogs are nice additions to iOS development for managing and loading images in code. The only exception is when one needs a different image for devices that are physically different heights but have the same resolution, like the iPhone 5s / iPhone 6, we still need to manage loading those images by hand.

Working example source code can be found at https://github.com/benhysell/G.BackgroundImages, and my published application using these techniques can be found on the app store.

Posted in xamarin Tagged with: ,

Authentication in Xamarin.iOS with Touch ID or Passcode

Adding Touch ID to your application is easy to do in iOS 8, in most cases this can be done in less than 20 lines of code. In this scenario the developer is responsible to supply a backup mechanism for the user if they choose not to use Touch ID for authentication. This type of Touch ID authentication is done using Local Authentication without Keychain Services.

Apple provides users the ability to use a Passcode to unlock their phone if they do not configure Touch ID, or choose not to use it, if say, all of their fingers are covered in jelly and they know their fingers will not successfully work with Touch ID, users can enter a Passcode to access their device.

What if we could use that same Passcode to protect our applications? With a few lines of code we can leverage Touch ID and use the user’s Passcode as a backup authentication mechanism.

Source Code

https://github.com/benhysell/V.TouchIdExample

Resources

Introduction to Touch ID / Using the Local Authentication Framework in your App

Xamarin provides an excellent introduction to Touch ID, Keychain Services, and Local Authentication on iOS. If you are new to the topic start here to familiarize yourself your options.

Adding Touch ID Authentication in iOS 8

Alex Blount provides the crib notes for adding Touch ID to your app via Local Authentication. From his example he shows how simple it is to get up and going, but this example lacks a backup mechanism if the user decides not to use Touch ID.

How to Use Apple‘s Touch ID Fingerprint API in Your Mobile App

Excellent article in obj-c on using Keychain Services, Touch ID, and the device’s Passcode as backup to Touch ID, and is the basis of my approach in this article.

Xamarin.iOS Keychain Example

Xamarin provides the insight on how to work with Apple’s Keychain Services which we’ll leverage to launch Touch ID / Passcode authentication.

Motivation

In my latest application, Goal Length, I designed an application that provides users an easy method to record different body measurements on a daily basis. Measure your neck, chest, waist, ect, record them, and see from day to day, week to week, if you are physically shrinking or growing, the tape measure doesn’t lie. The scale can report you are putting weight on, but you could be loosing inches on your waist. Without body measurements you might think your not progressing towards your goals.

Information like this isn’t something someone typically wants easily accessible. Hand your unprotected phone to your friend and they can be navigating through your last several body measurements, which could prove embarrassing.

Most Touch ID implementations besides the Apple Home Screen Lock, are meant for the user to supply a user name and password to protect a service, say a log in to a website. Hence, typical implementations of Touch ID protection rely on the developer to provide the backup mechanism for the user to enter their password specific to that service.

Since I’m just looking to protect the application from prying eyes, and I don’t have a application specific login/password I wanted to use the user’s already configured Touch ID / Passcode. Why make someone memorize one more password?

Implementation

Note

The demo code for Touch ID / Passcode supplied in this article will not run properly on the simulators. Since you cannot set a Passcode on the simulators you’ll need to run and test your code on a real device to see it work.

The demo also assumes you have Touch ID / Passcode enabled on your device. If you do not have Touch ID enabled but you do have a Passcode configured the application with skip to the Passcode entry screen.

Architecture

The sample application allows you to play with turning Touch ID / Passcode protection for an application on and off, and authenticating with each of the methods.

One can :
* Turn Touch ID on and off. Turning Touch ID off requires the user to authenticate one last time before allowing them to turn it off.
IMG_4355
* Once Touch ID is enabled the user can authenticate via Touch ID
IMG_4357
* If the user decides to not use Touch ID they can use their device’s Passcode to authenticate
IMG_4358
* If successful ‘Authenticated!’ is shown to the user.
IMG_4359

Code

From the How to Use Apple‘s Touch ID Fingerprint API in Your Mobile App, we learn if we want to use Passcode as a fallback mechanism we need to interact with Apple’s Keychain. Basically, we are going to store some dummy information in Apple’s Keychain and then retrieve it via Touch ID / Passcode. It doesn’t matter what we store in the Keychain, we are not going to use it, but we are looking to trigger the workflow of presenting the user with the Touch ID / Passcode prompts, and be notified if our user was successful authenticating or not.

Turning on Touch ID

Let’s create our fake Keychain record.

//set our record
//note what you fill in here doesn't matter, just needs to be consistent across all uses of the record
var secRecord = new SecRecord(SecKind.GenericPassword)
{
      Label = "Keychain Item",
      Description = "fake item for keychain access",
      Account = "Account",
      Service = "com.goallineapps.touchIdExample",
      Comment = "Your comment here",
      ValueData = NSData.FromString("my-secret-password"),
      Generic = NSData.FromString("foo")
};

secRecord.AccessControl = new SecAccessControl(SecAccessible.WhenPasscodeSetThisDeviceOnly, SecAccessControlCreateFlags.UserPresence);
SecKeyChain.Add(secRecord);

authenticateButton.Enabled = true;

The AccessControl flags of SecAccessible.WhenPasscodeSetThisDeviceOnly and SecAccessControlCreateFlags.UserPresence only allow us to store this record if the device has a passcode, and then retrieve it if the user is present.

Accessing our Keychain Record

Now that we have saved away our Keychain record, let’s try to access it.

partial void AuthenticateUser(UIButton sender)
{
    var rec = new SecRecord(SecKind.GenericPassword)
    {
        Service = "com.goallineapps.touchIdExample",
        UseOperationPrompt = "Authenticate to access Test App"
    };
    SecStatusCode res;
    SecKeyChain.QueryAsRecord(rec, out res);
    if (SecStatusCode.Success == res || SecStatusCode.ItemNotFound == res)
    {
        //Success!!  
        //add your code here to continue into your application
        AuthenticatedLabel.Hidden = false;
    }
    else
    {
        //Failure
        AuthenticatedLabel.Hidden = true;
    }
 }

Pressing our ‘Authenticate’ button will kick off an attempt to access our dummy Keychain item. Since we already told the system the item is protected with the flags SecAccessible.WhenPasscodeSetThisDeviceOnly and SecAccessControlCreateFlags.UserPresence this kicks off the Touch ID / Passcode workflow.

Turning Off Touch ID

For completeness, I force the user to authenticate one last time before allowing them to turn off Touch ID authentication. Once authenticated it will delete the record from the device.

//disable Touch ID
var record = new SecRecord(SecKind.GenericPassword)
{
    Service = "com.goallineapps.touchIdExample",
    UseOperationPrompt = "Authenticate to Remove Touch ID / Passcode from Test App"
};

SecStatusCode result;

//query one last time to ensure they can remove it
SecKeyChain.QueryAsRecord(record, out result);
if (SecStatusCode.Success == result || SecStatusCode.ItemNotFound == result)
{
    //remove the record
    SecKeyChain.Remove(record);
    authenticateButton.Enabled = false;
}
else
{
    //could not authenticate, leave switch on
    sender.On = true;
}  

Summary

If you are looking to leverage the user’s existing Passcode to protect your application you can store a dummy value in the Keychain Service and then attempt to access it within your application to force an authentication attempt by the user via either Touch ID or Passcode.

Posted in coding, xamarin Tagged with: ,

MvvmCross – FlyoutNavigation, Hamburger Menu, Sliding Menu for Android Null Reference Exception on Fragment Shown Fix

Summary

The architecture I used in MvvmCross for a FlyoutNavigation/Hamburger Menu/Sliding Menu requires the developer to maintain ViewModel state for all of the fragments used within the menu. The original implementation did not provide such a service. On certain Android devices a null reference exception would be generated when the user navigated between fragments.

The fix described here is not a perfect solution, use with caution.

Original Articles

Source Code

https://github.com/benhysell/V.FlyoutTest

Issue

After using the slide out menu architecture in an iOS, Android, and Windows Phone app I started receiving bug reports from two different Android Samsung devices.

06-09 11:36:16.740 I/MonoDroid(21351): UNHANDLED EXCEPTION: System.NullReferenceException: Object reference not set to an instance of an object
06-09 11:36:16.740 I/MonoDroid(21351): at V.JobTrak.Apps.Droid.Views.EnterTimeView.OnCreateView (Android.Views.LayoutInflater,Android.Views.ViewGroup,Android.OS.Bundle) <IL 0x000cf, 0x00588>
06-09 11:36:16.740 I/MonoDroid(21351): at Android.Support.V4.App.Fragment.n_OnCreateView_Landroid_view_LayoutInflater_Landroid_view_ViewGroup_Landroid_os_Bundle_ (intptr,intptr,intptr,intptr,intptr) <IL 0x00026, 0x0019f>
06-09 11:36:16.740 I/MonoDroid(21351): at (wrapper dynamic-method) object.d4c53d5a-8a99-43f6-bd55-88a30287aec2 (intptr,intptr,intptr,intptr,intptr) <IL 0x00023, 0x0005f>
An unhandled exception occured.

Unhandled Exception:

Java.Lang.Throwable: Loading...

06-09 11:36:20.620 D/HockeyApp(21351): Writing unhandled exception to: /data/data/com.droid.apps.jobtrak.v/files/f6e8f1e8-532a-49e9-99e2-00970568f77a.stacktrace
06-09 11:36:20.620 E/mono-rt (21351): [ERROR] FATAL UNHANDLED EXCEPTION: Java.Lang.Throwable: Exception of type 'Java.Lang.Throwable' was thrown.
06-09 11:36:20.620 E/mono-rt (21351): at Android.Runtime.JNIEnv.NewString (string) <IL 0x0004c, 0x00260>
06-09 11:36:20.620 E/mono-rt (21351): java.lang.Throwable: System.NullReferenceException: Object referenc06-09 11:36:20.620 E/mono-rt (21351): at Android.Util.Log.Info (string,string) <IL 0x0002e, 0x00127>
06-09 11:36:20.620 E/mono-rt (21351): at Android.Runtime.AndroidEnvironment.UnhandledException (System.Exception) <IL 0x00010, 0x00093>
06-09 11:36:20.620 E/mono-rt (21351): at (wrapper dynamic-method) object.da665a14-2c2b-48b2-9db0-7303fcb7fde2 (intptr,intptr) <IL 0x00029, 0x0008f>
06-09 11:36:20.620 E/mono-rt (21351): at (wrapper native-to-managed) object.da665a14-2c2b-48b2-9db0-7303fcb7fde2 (intptr,intptr) <IL 0x0001a, 0x0006b>
06-09 11:36:20.620 E/mono-rt (21351): 
06-09 11:36:20.620 E/mono-rt (21351):   --- End of managed exception stack trace ---
06-09 11:36:20.620 E/mono-rt (21351): java.lang.Throwable: System.NullReferenceException: Object reference not set to an instance of an object
06-09 11:36:20.620 E/mono-rt (21351): at V.JobTrak.Apps.Droid.Views.EnterTimeView.OnCreateView (Android.Views.LayoutInflater,Android.Views.ViewGroup,Android.OS.Bundle) <IL 0x000cf, 0x00588>
06-09 11:36:20.620 E/mono-rt (21351): at Android.Support.V4.App.Fragment.n_OnCreateView_Landroid_view_LayoutInflater_Landroid_view_ViewGroup_Landroid_os_Bundle_ (intptr,intptr,intptr,intptr,intptr)
The program 'Mono' has exited with code 0 (0x0).

Same crash, two different devices. I couldn’t for the life of me reproduce it on my Nexus 5 and was at a loss as to what was going on.

Getting Help

I turned to StackOverflow, http://stackoverflow.com/questions/24145410/mvvmcross-android-null-reference-for-viewmodel-when-reloading-fragments and almost immediately I received a response from Stuart Lodge the creator of MvvmCross.

From his answer on StackOverflow:

From your code, it looks like you are manually setting the ViewModel’s of your fragments when you first create them:

        frag.ViewModel = viewModelLocal;

https://github.com/benhysell/V.FlyoutTest/blob/master/V.FlyoutTest.Droid/Views/HomeView.cs#L153

Which, sure enough, that was exactly what was going on, from the HomeView.cs\Show() in the Android project:

var loaderService = Mvx.Resolve<IMvxViewModelLoader>();
var viewModelLocal = loaderService.LoadViewModel(request, null /* saved state */);
frag.ViewModel = viewModelLocal;

Bottom line, since I’ve created the ViewModel in my HomeView for a View I need to make sure it gets recreated if it is ever unloaded from memory.

Reproducing the Error with My Device

The real kicker with this issue is I could not reproduce it on my own device, or almost any other device I tested with. The error only showed up on a Samsung S3 and S5. I turned to the jabbr.net chat room for MvvmCross, https://jabbr.net/#/rooms/mvvmcross, for advice. It was Stuart Lodge who turned me onto a devious little setting in the Android Developer Options Menu called ‘Don’t Keep Activities’.

Screenshot_2014-06-28-23-09-51

Check that box and Android will ensure that the second you navigate away from an activity it is completely dumped from memory. When I checked it on the N5 I could re-create the crashes I was seeing in the field!

Fixing the Crashes

I do not know the proper, “Ivory Tower” method to fix this particular issue. I stumbled around for a few days trying different methods to address the null reference exception, but discovered this is a non-trivial issue. From MvvmCross’ GitHub issue tracker: Setting ViewModel property on a Fragment is not Correct

var viewModelLoader = Mvx.Resolve();
fragment.ViewModel = viewModelLoader.LoadViewModel(request, null);

This is incorrect because Fragments are managed by a FragmentManager and each Fragments lifecycle is at the mercy of that manager and Android. At any time your Fragment may be destroyed and the FragmentManager’s job is to recreate it. When it gets recreated it will no longer have the ViewModel you gave it and this will cause problems in the program. This pattern works in ideal situations but will break in many others….

The issue, as of publishing this post, is still open.

Warning – This Fix is a Hack

Realizing the proper fix was currently beyond my capability I went for a fix that appears to work. Since the fix has been implemented the app no longer crashes due to this bug. Be warned, this is a hack, understand it before you use it.

Steps Taken to Fix the Issue

  1. In HomeViewModel we’ll save the ViewModels for the fragments we are creating when OnSaveInstanceState(Bundle outState) is called in hopes MvvmCross will properly save the state of our ViewModels if they are properties of the HomeViewModel
  2. In the OnCreate() method we’ll attempt to restore the ViewModel of the fragment we are about to show, and if we can’t we’ll create a new one.

Modifying HomeViewModel

In the HomeViewModel we’ll add public properties for each of the ViewModels of our fragments.

public class HomeViewModel : BaseViewModel
{
    //allows us to save state for Android
    public EnterTimeViewModel EnterTimeViewModelFragment;
    public CreateNewJobViewModel CreateNewJobViewModelFragment;

Creating Our Fragments

When we create our fragments we are going to add a tag to the fragment when we add it to the SupportFragmentManager. We’ll use the fragment’s title property as the tag, thus later on we can check the SupportFragmentManager using the title of the fragment to see if the fragment exists and if it has a valid ViewModel attached to it.

this.SupportFragmentManager.BeginTransaction().Replace(Resource.Id.content_frame, frag, title).Commit();

Saving Fragment State

We’ll iterate through all of the fragments in our SupportFragmentManager, grab their ViewModels and place them in our HomeViewModel with the hopes that if the HomeViewModel is removed from memory the standard MvvmCross state saving mechanisms will also save the state of our fragment’s ViewModels.

        protected override void OnSaveInstanceState(Bundle outState)
        {
            SaveViewModelStates();
            base.OnSaveInstanceState(outState);
        }

        private void SaveViewModelStates()
        {
            //save all of the ViewModels for fragments
            var view = this.SupportFragmentManager.FindFragmentByTag("Enter Time") as EnterTimeView;
            if (null != view)
            {
                ViewModel.EnterTimeViewModelFragment = view.ViewModel as EnterTimeViewModel;
            }
            var view2 = this.SupportFragmentManager.FindFragmentByTag("Create New Job") as CreateNewJobView;
            if (null != view2)
            {
                ViewModel.CreateNewJobViewModelFragment = view2.ViewModel as CreateNewJobViewModel;
            }            
        }

Showing a Fragment

In the OnCreate() method we used to punt if we had any savedInstanceState data.

    if (null == savedInstanceState)
    {
        this.ViewModel.SelectMenuItemCommand.Execute(this.ViewModel.MenuItems[0]);
    }
}// end function

Now we will attempt to restore that state.

    if (null == savedInstanceState)
    {
        this.ViewModel.SelectMenuItemCommand.Execute(this.ViewModel.MenuItems[0]);
    }
    else
    {
        //restore viewModels if we have them
        RestoreViewModels();
    }
} //end of function            

/// <summary>
/// Restore view models to fragments if we have them and the fragments were created
/// </summary>
private void RestoreViewModels()
{
    var loaderService = Mvx.Resolve<IMvxViewModelLoader>();

    var view = this.SupportFragmentManager.FindFragmentByTag("Enter Time") as EnterTimeView;
    if (null != view && null == view.ViewModel)
    {
        view.ViewModel = ViewModel.EnterTimeViewModelFragment ?? loaderService.LoadViewModel(new MvxViewModelRequest(typeof(EnterTimeViewModel), null, null, null), null) as EnterTimeViewModel;
    }
    var view2 = this.SupportFragmentManager.FindFragmentByTag("Create New Job") as CreateNewJobView;
    if (null != view2 && null == view2.ViewModel)
    {
        view2.ViewModel = ViewModel.CreateNewJobViewModelFragment ?? loaderService.LoadViewModel(new MvxViewModelRequest(typeof(CreateNewJobViewModel), null, null, null), null) as CreateNewJobViewModel;
    }            
}

We iterate through all of the fragments in the SupportFragmentManager checking to see if our fragment was created, and if so if the ViewModel is valid. If it is null attempt to grab it from the HomeViewModel instance we saved away, if that is also null create a new ViewModel for the fragment.

Conclusion

This solution is a hack, it is ugly, and in no means the proper way to account for this behavior on the Android platform. Case and point, in my testing I found often times the HomeViewModel would have a valid reference to my fragment’s ViewModels when I attempted to restore them in the RestoreViewModels(). I may just be fooling myself in my limited testing, and there is a good chance that step doesn’t do what I’m expecting it to do. However, the worst case in this situation is a new ViewModel is created for the fragment, ensuring we do not get a null reference exception when we attempt to load our fragment.

Posted in mvvmcross, xamarin

MvvmCross – FlyoutNavigation, Hamburger Menu, Sliding Menu for Android, iOS, and Windows Phone

Summary

Provide a unified architecture for a FlyoutNavigation/Hamburger Menu/Sliding Menu for Android, iOS, and Windows Phone 8 Silverlight using MvvmCross.

flyout menus

Source Code

https://github.com/benhysell/V.FlyoutTest

Original Article

See – http://benjaminhysell.com/archive/2014/04/mvvmcross-flyoutnavigation-hamburger-menu-sliding-menu-for-android-and-ios/ for a walkthrough of the unified architecture for iOS and Android. This article expands upon that architecture to include Windows Phone 8.0 Silverlight.

Inspiration for Windows Phone 8.0 Silverlight Implementation

I had found a few Windows Phone slide out implementations, http://slideview.codeplex.com/, but attempting to tie them in with MvvmCross appeared to be a bit of a tall order, and for a while I had given up on my dream of unifying a slide out menu architecture for all three platforms.

Then I read this great article from Scott Hanselman http://www.hanselman.com/blog/XamarinFormsWriteOnceRunEverywhereANDBeNative.aspx talking about the new Xamarin.Forms where one could write all of their presentation code once, and have it natively drawn on each platform. The app used for the demo was a iOS, Android, and Windows Phone app with a slide out menu!

Source – https://github.com/jamesmontemagno/Hanselman.Forms

Astute readers will notice, James Montemagno was the inspiration for the Android slide out menu in my original article:

I took the evening and dissected the demo Xamarin.Forms app. iOS and Android implemented the familiar ‘hamburger’ in the upper left hand corner, that when pressed, would reveal other screens the user could navigate to. The Windows Phone however placed the button on the ApplicationBar, and when pushed showed a whole new page.

Inspired, I went back to Visual Studio and proceeded to provide a Windows Phone implementation of my slide out menu architecture.

Windows Phone Silverlight Implementation

As I said, this is not the traditional slide out menu for Windows Phone. If I must confess, I’m not a daily Windows Phone user, yet, but the days where I do use Windows Phone I find the slide out menu is not as prevalent on the platform as it is ingrained on iOS and Android. My slide out implementation is a “For Now” implementation, if the platform idioms change where a true slide out becomes the norm I’ll revisit the topic.

Solution

wp8 entertime

There are two major elements we need to implement for our Windows Phone solution:
1. The HomeView.xaml to hold our application menu
2. An ApplicationBar to hold our slide out menu icon on each of our root views

HomeView.xaml

The HomeView.xaml holds the list of other Views we could navigate to. For this example I kept things simple, placing the items in a ListBox. Since our HomeViewModel already has

private List<MenuViewModel> menuItems;
public List<MenuViewModel> MenuItems
{
    get { return this.menuItems; }
    set { this.menuItems = value; this.RaisePropertyChanged(() => this.MenuItems); }
}

We can bind to the MenuItems in our HomeView.xaml.

<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
    <StackPanel>
        <ListBox ItemsSource="{Binding MenuItems}" >
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <StackPanel Margin="24">
                        <TextBlock Text="{Binding Title}" Tap="UIElement_OnTap" FontSize="50" ></TextBlock>
                    </StackPanel>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
    </StackPanel>
</Grid>

HACK WARNING

Notice the Tap=UIElement_OnTap? In HomeView.xaml.cs It leads us to:

private void UIElement_OnTap(object sender, GestureEventArgs e)
{
    var selectedItem = ((HomeViewModel)ViewModel).MenuItems.FirstOrDefault(x => x.Title == ((TextBlock)sender).Text);
    if (null != selectedItem)
        ((HomeViewModel)ViewModel).SelectMenuItemCommand.Execute(selectedItem);
}

This allows us to figure out which cell the user pressed so we can navigate to the requested page. There are better ways to get this done, however I’m not a Windows Phone 8/MvvmCross master so I settled on this implementation for now.

Show the First View on Application Start

The only item left in our HomeView.xaml.cs is to navigate to the first view we would like our user to see when they launch the app. We accomplish that in the OnNavigatedTo

protected override void OnNavigatedTo(NavigationEventArgs e)
{
    if (null == ViewModel)
    {
        base.OnNavigatedTo(e);

        var selectedItem = ((HomeViewModel)ViewModel).MenuItems.FirstOrDefault(x => x.Title == "Enter Time");
        if (null != selectedItem)
        ((HomeViewModel)ViewModel).SelectMenuItemCommand.Execute(selectedItem);
    }
}

In OnNavigatedTo I am guaranteed I’ll have a valid HomeViewModel from which I can navigate to our first view, Enter Time.

Navigating Back to the Menu

From EnterTimeView we can get back to our menu two different ways:
1. Press the phone Back button
2. Press the Menu icon in the ApplicationBar

Setting Up the Phone Back Button

We actually don’t have to provide any code for this functionality, this is built into Windows Phone 8.

ApplicationBar Setup

First we’ll add the code for our ApplicationBar to EnterTimeView.xaml

<phone:PhoneApplicationPage.ApplicationBar>
    <shell:ApplicationBar IsVisible="True" IsMenuEnabled="True">
        <shell:ApplicationBarIconButton Click="ShowMenu" IconUri="/Toolkit.Content/ApplicationBar.Select.png" Text="Menu"/>           
        <shell:ApplicationBarIconButton Click="AddNewHoursEntry"  IconUri="/Assets/AppBar/add.png" Text="Add Hours"/>
        <shell:ApplicationBar.MenuItems>
            <shell:ApplicationBarMenuItem Click="ShowMenu" Text="Menu"/>                
            <shell:ApplicationBarMenuItem Click="AddNewHoursEntry" Text="Add Hours Entry"/>
        </shell:ApplicationBar.MenuItems>
    </shell:ApplicationBar>
</phone:PhoneApplicationPage.ApplicationBar>

Here I’ve added a second ApplicationBarIconButton and ApplicationBarMenuItem that would allow us to show another View when pressed to mimic the functionality in the iOS and Android implementations.

wp8 entertime app bar

Getting ApplicaitonBar.Select.png

ApplicationBar.Select.png is part of the Windows Phone Toolkit, add it to your project via NuGet if you’d like to use the same icon I’m using. Mark the icon as Content to ensure it is include with your application.

ShowMenu()

The ShowMenu() function uses the NavigationService allowing us to navigate back to our menu in the HomeView.

private void ShowMenu(object sender, EventArgs e)
{
    NavigationService.GoBack();
}

Conclusion

The lack of slide out menus in Windows Phone 8 shouldn’t stop us from creating a Windows Phone 8 app that uses ViewModels that were designed with the slide out menu architecture in mind. With the example code one can now create one .Core MvvmCross project based on slide out menus and use it in Windows Phone 8, iOS, and Android apps.

Grab the code and try it out: https://github.com/benhysell/V.FlyoutTest

Posted in mvvmcross, xamarin Tagged with: , , , ,

MvvmCross – Keychain Plugin with Windows Phone 8 Silverlight Support

Summary

Unified MvvmCross Keychain Plugin for iOS, Android, and Windows Phone 8 Silverlight

Source Code

Original – https://github.com/wedkarz/IHS.MvvmCross.Plugins.Keychain
Updated with Windows Phone Support – https://github.com/benhysell/IHS.MvvmCross.Plugins.Keychain

Introduction

The major app platforms, iOS, Android, and Windows Phone have mechanisms available to the app developer to store usernames and passwords. The rub is, each implementation is a bit different, and none of the major frameworks have plugins for MvvmCross.

Artur Rybak went out and created a unifying plugin for MvvmCross, IHS.MvvmCross.Plugins.Keychain. I have added onto the plugin to include support for Windows 8 Silverlight applications.

Why?

Most of the time developers would never store raw usernames and passwords in their app, one strives to store a token or hash of these sensitive items. If the device is compromised the attacker woudn’t gain a user’s raw username or password.

However, when connecting to web services often times an app developer will need the raw username and password, hence each of the major platforms provide a secure system to store these credentials.

Implementation

Artur Rybak‘s plugin, IHS.MvvmCross.Plugins.Keychain covers iOS and Android with a simple interface that met all of my needs:

namespace IHS.MvvmCross.Plugins.Keychain
{
    public interface IKeychain
    {
        bool SetPassword(string password, string serviceName, string account);
        string GetPassword(string serviceName, string account);
        bool DeletePassword(string serviceName, string account);
        LoginDetails GetLoginDetails(string serviceName);
        bool DeleteAccount(string serviceName, string account);
    }
}

The implementation uses the idea of a serviceName so one can keep their usernames/passwords separate from other services on the device.

Usage

This is a great plugin because “it just works.” Need a user name and password from your MvvmCross application?

var keyChain = Mvx.Resolve<IKeychain>();
var user = keyChain.GetLoginDetails(SERVICE);

if(null != user)
    var loginResult = service.Login(user.Username, user.Password);

keyChain.GetLoginDetails(SERVICE) will return null if a username and password cannot be found for your service.

Issues

The only issue I had was on iOS, and this may have only been a simulator issue, but I found when I attempted to update a username and/or password for a service the old entries were never deleted. To work around this issue I cleared out all user names and passwords for my particular service before adding one back in.

var keyChain = Mvx.Resolve<IKeychain>();
var user = keyChain.GetLoginDetails(SERVICE);
while (user != null) //clear out old user, if we don't it will hold onto all of the old users
{
    keyChain.DeletePassword(SERVICE, user.Password);
    keyChain.DeleteAccount(SERVICE, user.Username);
    user = keyChain.GetLoginDetails(SERVICE);
}                    
keyChain.SetPassword(Password, SERVICE, Username);

Windows Phone 8 Silverlight Implementation

In iOS and Android it appears there is a single system wide service that handles storage of raw passwords and usernames, and hence the required serviceName when accessing the keychain.

In Windows Phone 8 Silverlight, this isn’t the case. The best suggestion I found for storing usernames and passwords was to place them in isolated storage. I based my implementation on an answer found on StackOverflow, Username and Password data Windows phone 8 app.

public bool SetPassword(string password, string serviceName, string account)
{
    // Convert the password to a byte[].
    var passwordByte = Encoding.UTF8.GetBytes(password);

    // Encrypt the password by using the Protect() method.
    var protectedPasswordByte = ProtectedData.Protect(passwordByte, null);

    // Store the encrypted password in isolated storage.
    WriteToFile(protectedPasswordByte, serviceName + account);

    // same steps for the username
    var usernameByte = Encoding.UTF8.GetBytes(account);
    var protectedUsernameByte = ProtectedData.Protect(usernameByte, null);
    WriteToFile(protectedUsernameByte, serviceName);

    return true;
}

/// <summary>
/// Write a string to isolated storage
/// </summary>
/// <param name="data"></param>
/// <param name="filePath"></param>
private void WriteToFile(byte[] data, string filePath)
{
    // Create a file in the application's isolated storage.
    IsolatedStorageFile file = IsolatedStorageFile.GetUserStoreForApplication();
    IsolatedStorageFileStream writestream = new IsolatedStorageFileStream(filePath, System.IO.FileMode.Create, System.IO.FileAccess.Write, file);

    // Write pinData to the file.
    Stream writer = new StreamWriter(writestream).BaseStream;
    writer.Write(data, 0, data.Length);
    writer.Close();
    writestream.Close();
}

Since we are storing files to isolated storage I named the password file serviceName + account and the username file just serviceName.

Obtaining a username and password requires one to read the files from isolated storage:

public string GetPassword(string serviceName, string account)
{            
    return ReadIsolatedStorage(serviceName+account);
}

public string GetUsername(string serviceName)
{
    return ReadIsolatedStorage(serviceName);
}

/// <summary>
/// Given a filename read the item from isolated storage
/// </summary>
/// <param name="filename">defines item developer is looking for in isolated storage</param>
/// <returns>value found in isolated storage</returns>
private string ReadIsolatedStorage(string filename)
{
    using (var folder = IsolatedStorageFile.GetUserStoreForApplication())
    {
        string returnValue = null; //null if not found
        if (folder.FileExists(filename))
        {
            // Retrieve the item from isolated storage.
            byte[] protectedItemByte = this.ReadFromFile(filename);

            // Decrypt the item by using the Unprotect method.
            byte[] itemByte = ProtectedData.Unprotect(protectedItemByte, null);

            // Convert the password from byte to string and display it in the text box.
            returnValue = Encoding.UTF8.GetString(itemByte, 0, itemByte.Length);
        }
        return returnValue;
    }
}


/// <summary>
/// Read value from isolated storage
/// </summary>
/// <param name="filePath"></param>
/// <returns></returns>
private byte[] ReadFromFile(string filePath)
{
    // Access the file in the application's isolated storage.
    IsolatedStorageFile file = IsolatedStorageFile.GetUserStoreForApplication();
    IsolatedStorageFileStream readstream = new IsolatedStorageFileStream(filePath, System.IO.FileMode.Open, FileAccess.Read, file);

    // Read the PIN from the file.
    Stream reader = new StreamReader(readstream).BaseStream;
    byte[] pinArray = new byte[reader.Length];

    reader.Read(pinArray, 0, pinArray.Length);
    reader.Close();
    readstream.Close();

    return pinArray;
}

Windows Phone 8.1

Windows Phone 8.1 supports the Credential Locker. Once Windows Phone 8.1 is released I’ll update the library accordingly.

MvvmCross Plugin

Creating a plugin for MvvmCross was straightforward, I followed the path set by Artur Rybak, I added a KeychainPluginBootstrap.cs.pp and modified the IHS.MvvmCross.Plugins.Keychain.nuspec so the Windows Phone implementation was included in the NuGet package.

Using My Branch in a MvvmCross Application

IHS.MvvmCross.Plugins.Keychain is published on NuGet. As of publishing this blog post I have submitted a pull request to Artur Rybak to include my latest changes. If you are impatient for an ‘official release’ here are the steps you’ll need to take to get the plugin installed in your MvvmCross application.

  1. Pull down a copy of https://github.com/benhysell/IHS.MvvmCross.Plugins.Keychain
  2. Build
  3. Drop to the command line and navigate to the common folder of IHS.MvvmCross.Plugins.Keychain.
  4. Package the plugin – nuget pack IHS.MvvmCross.Plugins.Keychain.nuspec, one should now have a new NuGet package with the Keychain plugin for iOS, Android, and Windows Phone.
  5. Open your MvvmCross project and setup Visual Studio to have a new, local NuGet repository. Tools\Options\NuGetPackage Manager\Package Sources
  6. Add the directory where you just built the NuGet Packages. I called my new local location Keychain.
    Screen Shot 2014-06-22 at 1.57.12 PM
  7. Open your MvvmCross application, Manage NuGet Packages and select the keychain package from your new local NuGet repository.
    Screen Shot 2014-06-22 at 1.58.45 PM

Conclusion

If one must store username’s and passwords locally on the device ensure you are using a built in mechanism to do so, attempting to create your own password store is a difficult path I do not recommend. https://github.com/benhysell/IHS.MvvmCross.Plugins.Keychain provides a plugin for MvvmCross to work with iOS, Android, and Windows Phone.

Posted in mvvmcross, xamarin Tagged with:

MvvmCross – Xamarin.Android Popup DatePicker on EditText Click

Goal

When the user clicks on an EditText box in a MvvmCross Xamarin.Android app show a date picker popup.

Screenshot_2014-04-24-15-09-55

Research

Solution

All of the solutions I kept finding to this problem always had the user clicking on a button to show the DatePicker popup, and I really wanted the user to be able to show the date picker when they pressed on the EditText box, much like Android does in the default Calendar app.

From the above articles I was able to piece together a solution, the highlights:

  • Instead of setting the click event on a button set it to the EditText box.
datePickerText = view.FindViewById<EditText>(Resource.Id.DatePickerEditText);            
datePickerText.Click += delegate
{
    var dialog = new DatePickerDialogFragment(Activity, Convert.ToDateTime(datePickerText.Text), this);
    dialog.Show(FragmentManager, "date");
};
  • Ensure when the user presses on the EditText box that the system does not display the default keyboard by setting datePickerText.Focusable = false;.

  • For MvvmCross binding we only need to bind to the datePickerText.

The full solution:

public class EnterTimeView : MvxFragment, DatePickerDialog.IOnDateSetListener
{
    private EditText datePickerText;

    public EnterTimeView()
    {
        this.RetainInstance = true;
    }

    public override Android.Views.View OnCreateView(Android.Views.LayoutInflater inflater, Android.Views.ViewGroup container, Android.OS.Bundle savedInstanceState)
    {
        this.HasOptionsMenu = true;

        var ignored = base.OnCreateView(inflater, container, savedInstanceState);
        var view = inflater.Inflate(Resource.Layout.EnterTimeView, container, false);

        datePickerText = view.FindViewById<EditText>(Resource.Id.DatePickerEditText);
        datePickerText.Focusable = false;
        datePickerText.Click += delegate
        {
            var dialog = new DatePickerDialogFragment(Activity, Convert.ToDateTime(datePickerText.Text), this);
            dialog.Show(FragmentManager, "date");
        };

        var set = this.CreateBindingSet<EnterTimeView, EnterTimeViewModel>();
        set.Bind(datePickerText).To(vm => vm.Date);
        set.Apply();

        return view;
    }

    public void OnDateSet(Android.Widget.DatePicker view, int year, int monthOfYear, int dayOfMonth)
    {
        datePickerText.Text = new DateTime(year, monthOfYear + 1, dayOfMonth).ToString();
    }

    private class DatePickerDialogFragment : Android.Support.V4.App.DialogFragment 
    {
        private readonly Context _context;
        private DateTime _date;
        private readonly DatePickerDialog.IOnDateSetListener _listener;

        public DatePickerDialogFragment(Context context, DateTime date, DatePickerDialog.IOnDateSetListener listener)
        {
            _context = context;
            _date = date;
            _listener = listener;
        }

        public override Dialog OnCreateDialog(Bundle savedState)
        {
            var dialog = new DatePickerDialog(_context, _listener, _date.Year, _date.Month - 1, _date.Day);
            return dialog;
        }
    }
}
Posted in mvvmcross, xamarin Tagged with: ,

MvvmCross – Custom MvxTableViewCell Without a NIB File

Summary

Programmatically create a custom MvxTableViewCell without using a NIB file.

Source Code

https://github.com/benhysell/V.MvvmCross.CustomCell

Research

Introduction

As I venture deeper into MvvmCross based development I had a need to create a custom MvxTableViewCell without using a NIB file. There are several examples of how one can use a NIB file to complete this task, but as a general rule I try to stay away from NIB files and choose to create all of my iOS interfaces in code.

Problem Domain

I’m working on a application that allows an engineer to track their time spent against all the projects they are working on. My example user interface for this demo has a calendar with a UIDatePicker allowing the engineer to select the date and then show all of the entries they have made for that date.

iOS Simulator Screen shot Apr 16, 2014, 3.43.25 PM

My data source for this example is a simple class containing the job name, a job id, and the number of hours spent on that job:

public class EnterTime
{
    public string JobName { get; set; }
    public string JobId { get; set; }
    public decimal Hours { get; set; }        
}

Attempting to display all three elements in a standard MvxTableViewCell wasn’t working, thus the need for a custom cell.

View and ViewModel

FirstView and FirstViewModel are standard MvvmCross Views and ViewModels…no real magic needs to take place in these for us to use a custom cell. In this example code FirstViewModel provides functionality that simulates an async server call to retrieve data for a given date provided by the user. FirstViewModel is also setup for pull to refresh, delete, and select from the UITableView. Thanks again goes to James Montemagno for two great Gists to make pull to refresh and swipe to delete happen:

MvxDeleteTableViewSource

I used James Montemagno‘s MvvmCross TableView Swipe to Delete as a starting point for my TableViewSource and proceeded with a few key modifications.

Register Our Custom Cell Class

In the constructor we need to tell the UITableView which class we will be using for our cells, this is done via RegisterClassForCellReuse.

private IRemove viewModel;        

public MvxDeleteTableViewSource(IRemove viewModel, UITableView tableView) : base(tableView)
{
    this.viewModel = viewModel;
    tableView.RegisterClassForCellReuse(typeof(HoursEntryCell), new NSString("HoursEntryCell"));
}

Derive From MvxTableViewSource

Originally MvxDeleteTableViewSource derived from MvxStandardTableViewSource, this however caused issues with data binding failing to work for my custom cell. Deriving from MvxTableViewSource and implementing GetOrCreateCellFor solved the data binding issues.

protected override UITableViewCell GetOrCreateCellFor(UITableView tableView, NSIndexPath indexPath, object item)
{
    return (HoursEntryCell)tableView.DequeueReusableCell("HoursEntryCell");
}

Custom MvxTableViewCell

The HoursEntryCell turned out to be straight forward…in each constructor call CreateLayout and ensure data binding is initialized.

[Register("HoursEntryCell")]
public class HoursEntryCell : MvxTableViewCell
{        
    public HoursEntryCell()            
    {
        CreateLayout();
        InitializeBindings();
    }

    public HoursEntryCell(IntPtr handle) : base(handle)
    {
        CreateLayout();
        InitializeBindings();
    }

    private UILabel jobId;
    private UILabel hours;
    private UILabel jobName;

    private void CreateLayout()
    {
        const int offsetStart = 10;
        Accessory = UITableViewCellAccessory.DisclosureIndicator;
        jobId = new UILabel(new RectangleF(offsetStart, 0, 75, 40));
        hours = new UILabel(new RectangleF(UIScreen.MainScreen.Bounds.Right - 85, 0, 55, 40));
        hours.TextAlignment = UITextAlignment.Right;
        jobName = new UILabel(new RectangleF(jobId.Frame.Right, 0, UIScreen.MainScreen.Bounds.Width - jobId.Frame.Width - hours.Frame.Width - (3 * offsetStart), 40));
        jobName.AdjustsFontSizeToFitWidth = true;
        jobName.Lines = 0;
        jobName.Font = jobName.Font.WithSize(10);
        ContentView.AddSubviews(jobId, jobName, hours);
    }

    private void InitializeBindings()
    {
        this.DelayBind(() =>
        {
            var set = this.CreateBindingSet<HoursEntryCell, EnterTime>();
            set.Bind(jobId).To(vm => vm.JobId);
            set.Bind(hours).To(vm => vm.Hours);
            set.Bind(jobName).To(vm => vm.JobName);
            set.Apply();
        });
    }
}

Conclusion

The big hurtle I had with creating a custom UITableViewCell in MvvmCross without using a NIB file was deriving from the wrong MvxTableViewSource. Once I had that sorted out everything worked as expected, and I can now create my custom cells all in code without a NIB file.

Posted in mvvmcross, xamarin Tagged with:

MvvmCross – FlyoutNavigation, Hamburger Menu, Sliding Menu for Android and iOS

Summary

Provide a unified architecture for a FlyoutNavigation/Hamburger Menu/Sliding Menu across Android and iOS using MvvmCross.

Updates

Source Code

https://github.com/benhysell/V.FlyoutTest

Introduction

FlyoutNavigation, Hamburger Menus, and Sliding Menus (from here on out I’ll refer to this type of control as a slide out menu) all describe a popular method on mobile devices of navigating application windows via a menu that slides/flies out onto the screen providing a user multiple choices on which screen they want to navigate to next.

In my own app Goal Weight I used the popular Xamarin component FlyoutNavigation to show/hide access to settings, set goals, and view weight history.

Menu

For my latest project I’ve started development of a time tracking application that will run on Android and iOS. Thus, I found myself needing a cross platform slide out menu.

Existing Work

After Googling and researching StackOverflow I had a couple of contenders for an out of the box solution:

The only rub was neither one of these methodologies shared a common MvvmCross .Core project, and I didn’t want to support multiple/different .Core projects for different platforms. I also had experience with the FlyoutNavigation component and wanted to use it for the iOS solution. Thus I set out to create a unified architecture.

Source Code Walkthrough

The source code up on GitHub is used to demo this unified architecture…think of it as a road map on how to implement a cross platform MvvmCross slide out menu.

The demo application has four ViewModels:

  • HomeViewModel – Holds all of the ViewModels that will appear in the slide out menu
  • EnterTimeViewModel – ViewModel accessible from the slide out menu. In the demo this view is blank.
  • CreateNewJobViewModel – A second ViewModel accessible from the slide out menu. Also blank for this demo.
  • AddNewHoursEntryViewModel – A ViewModel that is created from the EnterTimeViewModel when the user presses a button on the upper right hand corner of the navigation bar.

Android Architecture

Background – http://motzcod.es/post/60427389481/effective-navigation-in-xamarin-android-part-1
Source – https://github.com/jamesmontemagno/Xam.NavDrawer

Android has a slide out menu built into later versions of Android called a Navigation Drawer. James Montemagno does an amazing job implementing the Navigation Drawer on Android in his blog post, so I decided to use his code as a starting point/inspiration for my .Core and .Android projects.

For a full detailed breakdown of the Android architecture read James’ blog. The short version is the HomeViewModel holds the slide out menu items and EnterTimeView and CreateNewJobView are fragments that are swapped in and out of the View as commanded by the slide out menu.

I tore through James’ GitHub code and re-implemented it in my example application to ensure I understood it well enough to keep moving forward. I liked the idea of having one ViewModel to hold all of the menu data and carried this idea over to the iOS application.

iOS Architecture

Warning – Although the iOS architecture works, it doesn’t “feel correct”, i.e. it has a little code smell than I would not normally feel comfortable living with. However I’m happy enough with the solution for now.

With the iOS architecture the idea is simple, construct a FlyoutNavigationController and build up the Views that will populate it in the HomeView.

First build the FlyoutNavigationController and add it to the View.

public override void ViewDidLoad()
{
    base.ViewDidLoad();
    NavigationController.NavigationBarHidden = true;
    Title = "Home";
    this.View = new UIView { BackgroundColor = UIColor.White };

    navigation = new FlyoutNavigationController();

    View.AddSubview(navigation.View);
    this.AddChildViewController(navigation);

One key point here is NavigationController.NavigationBarHidden = true;, for Views that will be managed from the FlyoutNavigationController we want to use the FlyoutNavigationController‘s navigation bar and not the one supplied by MvvmCross.

Next, we’ll build up the ViewModels and menu elements for the FlyoutNavigationController. Data for the ViewModels and their names are held in the HomeViewModel

//names of the views shown in the flyout
var flyoutMenuElements = new Section();
//views that will be shown when a menu item is selected
var flyoutViewControllers = new List<UIViewController>();
var homeViewModel = ViewModel as HomeViewModel;
if (homeViewModel != null)
{
    //create the ViewModels
    foreach (var viewModel in homeViewModel.MenuItems)
    {
        var viewModelRequest = new MvxViewModelRequest
        {
            ViewModelType = viewModel.ViewModelType
        };

        flyoutViewControllers.Add(CreateMenuItemController(viewModelRequest));
        flyoutMenuElements.Add(new StringElement(viewModel.Title));
    }
    navigation.ViewControllers = flyoutViewControllers.ToArray();

    //add the menu elements
    var rootElement = new RootElement("")
    {
        flyoutMenuElements
    };
    navigation.NavigationRoot = rootElement;
}

Creating the UIViewControllers in CreateMenuItemController

private UIViewController CreateMenuItemController(MvxViewModelRequest viewModelRequest)
{
    var controller = new UINavigationController();
    var screen = this.CreateViewControllerFor(viewModelRequest) as UIViewController;
    controller.PushViewController(screen, false);
    return controller;
}

Lastly, we need to listen to two messages that will be emitted by all Views that the FlyoutNavigationController will show. One message to toggle the FlyoutNavigationController‘s menu, and another to show and hide the MvvmCross navigation bar.

var messenger = Mvx.Resolve<IMvxMessenger>();
navigationMenuToggleToken = messenger.SubscribeOnMainThread<ToggleFlyoutMenuMessage>(message => navigation.ToggleMenu());
navigationBarHiddenToken = messenger.SubscribeOnMainThread<NavigationBarHiddenMessage>(message => NavigationController.NavigationBarHidden = message.NavigationBarHidden);

One last item needs to be addressed in our HomeView, setting the size of the Views that will be shown in the FlyoutNavigationController.

public override void ViewWillAppear(bool animated)
{
    base.ViewWillAppear(animated);
    navigation.View.Frame = UIScreen.MainScreen.Bounds;
    navigation.View.Bounds = UIScreen.MainScreen.Bounds;
}

If we fail to set the FlyoutNavigationController View.Frame and View.Bounds the FlyoutNavigationController will draw all of our views at 2x the Frame size than they should be.

Our HomeView is now complete, let’s take a look at EnterTimeView.

EnterTimeView

The EnterTimeView is going to show the slide out menu button that when toggled will show the slide out menu, plus it will show another view, the AddHoursEntryView with a button on the upper right of the navigation bar. We’ll add two buttons to the navigation bar with delegates to send message back to the HomeView to perform these actions.

public override void ViewDidLoad()
{
    View = new UIView { BackgroundColor = UIColor.Blue };

    base.ViewDidLoad();
    Title = "Enter Time";
    NavigationItem.LeftBarButtonItem = new UIBarButtonItem(UIBarButtonSystemItem.Pause,
                                      (delegate
                                      {
                                       //message to show the menu
                                       var messenger = Mvx.Resolve<IMvxMessenger>();
                                       messenger.Publish(new ToggleFlyoutMenuMessage(this));
                                       }));
    NavigationItem.RightBarButtonItem = new UIBarButtonItem(UIBarButtonSystemItem.Add,
                                       (delegate
                                       {
                                       //hide MvvmCross navigation bar and show next view
                                       var messenger = Mvx.Resolve<IMvxMessenger>();
                                       messenger.Publish(new NavigationBarHiddenMessage(this, false));
                                       var viewmodel = ViewModel as EnterTimeViewModel;
                                       if (viewmodel != null) viewmodel.ShowFirstView();
                                       }));
}

AddHoursEntryView

Since we are showing the navigation bar for MvvmCross when we show AddHoursEntryView we need to send a message back to the HomeView to hide it again once AddHoursEntryView is dismissed by the user via the back button.

public override void ViewWillDisappear(bool animated)
{
    if (!NavigationController.ViewControllers.Contains(this))
    {
        // Back button was pressed.  We know this is true because self is no longer
        // in the navigation stack, hide MvvmCross's navigation menu
        var messenger = Mvx.Resolve<IMvxMessenger>();
        messenger.Publish(new NavigationBarHiddenMessage(this, true)); 
    }
    base.ViewWillDisappear(animated);
}

With this last step we have a working slide out menu in iOS that shares a common .Core solution with the Android project.

Conclusion

I have the warning about the iOS code smell because of the extra steps we need to take in the Views to toggle the menu and show/hide the MvvmCross navigation bar. I can’t place my finger on the exact code smell, the solution works…but to me just doesn’t feel “correct” in an Ivory Tower sort of way.

That being said, I believe the extra messaging is a small price to pay to use the FlyoutNavigation component without modification, and to have iOS and Android share a common .Core project.

Go grab the code and try it out, https://github.com/benhysell/V.FlyoutTest

Posted in mvvmcross, xamarin Tagged with: , , ,

Goal Weight

Just a quick post to announce I’m on the app store!

https://itunes.apple.com/us/app/goal-weight/id693001074

http://goallineapps.com/app/goal-weight/

 

Posted in Uncategorized