Reaching the Nirvana: MvvmCross + Xamarin.iOS + FSharp
Introduction:
This time I want to show you how to combine three of my favourite technologies in a simple way: MvvmCross, Xamarin.iOS y Fsharp
Disclaimer: I'm not a FSharp expert. I just fell in love with the first line I wrote. There is so much power behind the scenes.
I want to keep the example as simple as possible so I based it on the easiest example of mvvmcross that you can find in CSharp (link).
Setting up the enviroment
The first thing that we have to do is to configure the environment, in order to do that, you need to have FSharp installed and working in iOS. You can see how do that here (http://fsharp.org/use/ios/). Perhaps you also need the latest version of the addin F# binding, more info here thanks to @7sharp9.
Once we have FSharp working on iOS, we have to manually add the references of MvvmCross that can be found here. Note that currently the libraries for osx and windows aren't the same.
In order to keep the convention of MvvmCross, we are going to make a project of type Library for iOS called ReachingNirvana.Core and the solution name will be ReachingNirvana. We'll also add the followed libraries: Cirrious.CrossCore.dll, Cirrous.MvvmCross.dll, Cirrious.MvvmCross.Localization.dll. The next thing that we have to do is to make a new project called ReachingTheNivana.Touch of type SingleViewApplication. And we also add the references Cirrious.CrossCore.dll, Cirrious.CrossCore.Touch.dll, Cirrious.MvvmCross.dll,Cirrious.MvvmCross.Touch, Cirrious.MvvmCross.Binding.dll, Cirrious.MvvmCross.Binding.Touch.dll and finally the reference for our ReachingNirvana.Core project.
Doing the magic
Once we have configured the environment we should add a file with the extension .fs to our application with the following content:
namespace ReachingNirvana.Core
open System
open Cirrious.CrossCore.IoC
open Cirrious.MvvmCross.ViewModels
type FirstViewModel() =
inherit MvxViewModel()
let mutable hello : string = "Hello MvvmCross"
member this.Hello
with get () = hello
and set (value) =
hello <- value
this.RaisePropertyChanged("Hello")
type App () =
inherit Cirrious.MvvmCross.ViewModels.MvxApplication()
override u.Initialize() =
u.RegisterAppStart<FirstViewModel>()
As you can see we defined some types in the same file, that isn't necessary, if you wish you can separate each type in an individual file, but remember that the order of the file matters. So if you want to change it, you also need to change the file project.
The first thing we defined is the FirstViewModel class which inherits from MvxViewModel and we also defined a property called Hello with its get and set. I prefer to keep the legibility, using a string instead of a func, because the sintax to do that in fsharp is a bit overload because of the explicit casting.
Moreover we defined the type App which inherits from MvxApplication and we also override the Initialize method. Note that we don't register anything in the IoC container because it isn't necessary for this example, but if you want to, this is the right place to do it.
Now we have done the Core project, so it's time to do the Touch project. The first thing that we have to do is to replace the code in the AppDelegate.fs file with this one:
namespace ReachingNirvana.Touch
open System
open MonoTouch.UIKit
open MonoTouch.Foundation
open Cirrious.MvvmCross.Touch.Platform
open Cirrious.MvvmCross.Touch.Views.Presenters
open Cirrious.MvvmCross.ViewModels
open Cirrious.CrossCore
open ReachingNirvana.Core
type Setup (applicationDelegate:MvxApplicationDelegate, presenter: IMvxTouchViewPresenter) =
inherit MvxTouchSetup(applicationDelegate,presenter )
override u.CreateApp() =
new App():> IMvxApplication
[<Register ("AppDelegate")>]
type AppDelegate () =
inherit MvxApplicationDelegate ()
let window = new UIWindow (UIScreen.MainScreen.Bounds)
// This method is invoked when the application is ready to run.
override this.FinishedLaunching (app, options) =
let presenter = new MvxTouchViewPresenter(this,window)
let setup = new Setup(this,presenter)
setup.Initialize()
let startup = Mvx.Resolve<IMvxAppStart>()
startup.Start()
window.MakeKeyAndVisible ()
true
module Main =
[<EntryPoint>]
let main args =
UIApplication.Main (args, null, "AppDelegate")
0
We defined three classes: Setup, AppDelegate and Main. The code is very similar to the CSharp version (but it has more beauty), the main difference is that we need to do an explicit casting from App to IMvxApplication instead of an implicit one.
Finally, we'll need to define our view, FirstView. You can change the name of the template that Xamarin Studio gives us or just add a new one, but remember that the order of the files matters.
In the FirstView we have the following code:
open System
open System.Drawing
open MonoTouch.UIKit
open MonoTouch.Foundation
open ReachingNirvana.Core
open Cirrious.MvvmCross.Binding
open Cirrious.MvvmCross.Binding.BindingContext
open Cirrious.MvvmCross.Touch.Views
[<Register ("FirstView")>]
type FirstView () =
inherit MvxViewController ()
let label = new UILabel()
let textBox = new UITextField()
// Release any cached data, images, etc that aren't in use.
override this.DidReceiveMemoryWarning () =
base.DidReceiveMemoryWarning ()
// Perform any additional setup after loading the view, typically from a nib.
override this.ViewDidLoad () =
base.ViewDidLoad ()
this.View.BackgroundColor <- UIColor.White
label.Frame <- new RectangleF(0.0f,0.0f,320.0f,50.f)
this.Add(label)
textBox.Frame <- new RectangleF(0.0f,70.0f,320.0f,50.f)
this.Add(textBox)
let set = MvxBindingContextOwnerExtensions.CreateBindingSet<FirstView,FirstViewModel>(this)
set.Bind(label).To("Hello") |> ignore
set.Bind(textBox).To("Hello") |> ignore
set.Apply()
// Return true for supported orientations
override this.ShouldAutorotateToInterfaceOrientation (orientation) =
orientation <> UIInterfaceOrientation.PortraitUpsideDown
As you can see, it's just a view with two controls: one UILabel and one UITextField. In the ViewDidLoad method we initialize some properties of the controls and we also make the bindings:
CreateBindingSet is an extension method. An extension method is just a static method that can be called from the class that implements it, we just need to pass the instance of the object that will use it as an explicit parameter. When we called the Bind method, we have an analogy with the RaisePropertyChanged. Again I choose the overload that recieves a string so we don't have to do explicit casting.
Conclusion
This was the first contact, I personally, still need to think if using FSharp as a main language everywhere compensates, because currently we don't have the same tooling support than CSharp has. Perhaps it makes more sense to keep the view part in the CSharp side (in the Windows targets case). But for sure FSharp has some advantages over CSharp and also some disadvantages (tooling and communication between libraries) but as you know, the key is to "choose the right tool for the right job".
PS Sorry for the bad formatting of the code. I'll fixe that soon.
You can also download it on git.