Cross-Platform Mobile Dev, pure badassery – pt 4

By | October 17, 2012

This is part 4 of a 5-part series in which I explore and demonstrate cross-platform app creation for mobile and desktop platforms using iFactr, an enterprise-grade framework, and Monocross, the open-source core of iFactr. The other articles are easily accessible at the bottom of each post.

Ok so now that we have a basis for our Monocross project, let’s see what it’ll take to turn this in to an application similar to the iFactr MonkeySpace one we did previously.

A good place to start would be to get our models in place, since these serve as the building blocks for the Navigation Map and Controllers that exist in the common area of a Monocross project. So let’s do that:

image

Since we rely on these models being populated by the Data class, add that one in too:

image

I won’t bother taking up space in this post to show it, but for ease of use as we move on, augment the namespaces in these classes to match our new Monocross project’s name. Once you start doing this, you’ll notice something in our Data.cs file:

image

image

Hmmm… how to solve this…

If you head back out to the Monocross website, you’ll see not just a link to download Monocross, but also a link to download Monocross + Utilities. The Utilities section has the projects we need to take care of the issues in these lines.
Add the project to your solution and reference it from the central app:

image

image

Sweet. Took care of the Serializing problems, but we’ve still got an issue w/ Network. This is because iFactr created Network in a platform-agnostic manner to perform platform-specific network connectivity so unfortunately we don’t get it in Monocross. No worry, though. It’s easy enough to redo using basic .Net constructs:

		Data ()
		{
			WebClient speakerClient = new WebClient ();
			speakerClient.DownloadStringCompleted += (s, e) =>
			{
				Speakers = SerializerFactory.Create<Speaker> (SerializationFormat.JSON).DeserializeList (e.Result);
			};
			speakerClient.DownloadStringAsync (new Uri ("http://monkeyspace.org/data/speakers.json"));
 
			WebClient sessionClient = new WebClient ();
			sessionClient.DownloadStringCompleted += (s, e) =>
			{
				Sessions = SerializerFactory.Create<Session> (SerializationFormat.JSON).DeserializeList (e.Result);
			};
			sessionClient.DownloadStringAsync (new Uri ("http://monkeyspace.org/data/sessions.json"));
 
			WebClient scheduleClient = new WebClient ();
			scheduleClient.DownloadStringCompleted += (s, e) =>
			{
				Schedule = SerializerFactory.Create<Schedule> (SerializationFormat.JSON).DeserializeObject (e.Result);
			};
			scheduleClient.DownloadStringAsync (new Uri ("http://monkeyspace.org/data/schedule.json"));
		}

Now obviously I don’t have the Async CTP installed or I’d definitely have chosen to use that here. We want these calls to be synchronous because we don’t want something accessing Data.Speakers before it’s had a chance to call the web service and process the results. But, I’m going to leave that alone for now in the interest of moving this little project along.

So now if we build the solution we’re golden. So let’s move on to having a look at the NavigationMap. Remember here’s what we had in our iFactr variant:

			// Add navigation mappings
			NavigationMap.Add ("", new SpeakerList ());
			NavigationMap.Add ("Speakers/{id}", new SpeakerDetail ());

And here’s what we currently have in our Monocross one:

			// add controllers to navigation map
			NavigationMap.Add ("", new Controllers.MessageController ());

When we view them like this we can see a pretty clear separation between the two; in iFactr you’re associating an entire UI with an endpoint, in Monocross it’s a controller. So, before we move on let’s create the controllers that will handle each of the endpoints.

	public class SpeakerListController : MXController<List<Speaker>>
	{
		public override string Load (Dictionary<string, string> parameters)
		{
			this.Model = Data.Instance.Speakers;
 
			return ViewPerspective.Default;
		}
	}

By specifying ‘List’ as the generic type for the MXController our .Model property gets that type immediately. I love Generics. Why do we have to use List? Because, while our Model type is Speaker, the SpeakerList VIEWS that we’re going to be implementing will be showing a list of them. So the underlying Model for the view needs to be the same.

The other controller is the one that will feed our singular Speaker Detail view, so let’s create it as a controller w/ a model for a single Speaker:

	public class SpeakerController : MXController<Speaker>
	{
		public override string Load (Dictionary<string, string> parameters)
		{
			this.Model = Data.Instance.Speakers.Find (s => s.Id == int.Parse (parameters["id"]));
 
			return ViewPerspective.Default;
		}
	}

And now let’s revisit the App.cs file and the definition of the URI mappings in our NavigationMap. They should now turn in to pretty much exactly like what we had in our iFactr example, with a couple of minor tweaks:

			// Add navigation mappings
			NavigationMap.Add ("", new SpeakerListController ());
			NavigationMap.Add ("Speakers/{id}", new SpeakerController ());

Again the important thing to remember here is that the navigation map points to a controller in Monocross, whereas in iFactr it directed to a view. Again this is that illustration that Monocross gives you the M and C, whereas iFactr provides you the M, C, and V.

So now, the Application in our Solution should look something like this:
image

Now comes the part that really outlines the difference between iFactr and Monocross. With iFactr, we’d be done right now. The Application would just shoot the information up to the Container and the Container takes over rendering the layers defined in the application with its platform-specific constructs. With Monocross, we still need to define the Views that will render the information in our Application which is just a Controller with a Model right now. So let’s do this on Console (because hey, I kinda know what I’m doing with that ;) )

Currently in Program.cs we have:

			// initialize views
			MXConsoleContainer.AddView<string> (new Views.MessageView (), ViewPerspective.Default);

But that no longer makes sense. Our Model is not simply a ‘string’ and our View isn’t going to be displaying a ‘string’ Model. So, much like in the Application, let’s start by defining the views for each of our model/controller combinations.

First, let’s get the easy one done – the list of speakers:

	public class SpeakerListView : MXView<List<Speaker>>
	{
		public override void Render ()
		{
			foreach (var s in this.Model.OrderBy (s => s.Id)) {
				System.Console.WriteLine (string.Concat (s.Id, ": ", s.Name));
			}
		}
	}

And wire that up in Program.cs:

			// initialize views
			MXConsoleContainer.AddView<List<MonkeySpaceMX.Speaker>> (new Views.SpeakerListView (), ViewPerspective.Default);

Which simply says “Hey, add a view to your bag of tricks that has a model of type List with an instance of Views.SpeakerListView(). Again let’s just pretend like the ViewPerspective isn’t there ;)

Now that we’ve got something that looks halfway functional, let’s give ‘er a run!

image

Full disclosure: I had to add a couple of spinlocks surrounding those Asynchronous network calls we’re making to fetch data to get this to work, but you get the idea ;)

Pretty cool! As you no doubt also notice, not only does iFactr take care of your Views, it also takes care of how the user Navigates through your application. Note that this console app just does what we define in the view, it just flat out prints the names. There’s no prompt for the user to enter the number to choose and navigate further simply because we haven’t defined that. But, you can definitely see that all of the data and business logic is entirely encapsulated in a common application; all we had to implement was a view.

Stay tuned until next time! We’ll add a Windows Phone container to this bad boy. Since there isn’t a pre-fabbed Monocross project template for this we’ll be home-growing a project for it. Woot!