Wednesday, 13 July 2011

"M"osetta Stone II: ViewController → Activity

After my recent posts I received a question asking for tips on porting a MonoTouch app to MonoDroid. I'm going to take a crack at that over a couple of days - with the warning that you probably shouldn't rush out and buy either tool but wait for Xamarin's future Mono-on-iOS and Mono-on-Android products.

The purpose of the original "M"osetta Stone post was to give cross-platform developers a 'head start' with some high-level concepts. This post will look a little closer at a screen from the Monospace11 app: the Speakers list.


This may be a massive oversimplification, but...
if you have
___ in iOS
then you want
___ in Android
to
UIViewControllerActivitysubclass to present a screen/form/page to the user
ViewDidLoad()OnCreate()setup the UI
Add() or AddSubview()SetContentView()use in ViewDidLoad/OnCreate to 'attach' UI to display
ViewWillAppear()OnResume()load/re-load data that might change and bind to UI
UITableViewListViewvisual control that presents a scrolling list
UITableViewSourceAdaptersubclass to broker the display of a data collection to the UI
GetCell()GetView()to build the layout(UI) for each cell/row, including re-using cells/views for performance
RowSelected()ItemClick EventHandlerperform an action when a row is touched
RowsInSectionCounttell the table/list how many rows to cater for

Here's how it looks in a Visual Studio Class Diagram*
* I've greyed out some of the methods not relevant to the discussion - the iOS implementation of the alphabetic index down the right edge doesn't have an Android equivalent, for example

Notes:
  • UITableViewSource has been implemented as an nested class to resemble the iOS pattern of implementing the equivalent Obj-C protocols on the ViewController - this pattern makes no sense in Android so the classes are separate.
  • The MonoTouch example code builds the UI in C# (eg. tableView=new UITableView();AddSubview (tableView);) whereas MonoDroid loads layout XML files SetContentView(Resource.Layout.Speakers);. This can happen in both ViewDidLoad/OnCreate and GetCell/GetView where UI controls must be created and presented to the user.
  • The 'touch event handler' is in a different class: in MonoTouch the TableViewSource handles a touch for a specific row (NSIndexPath); MonoDroid implements the ItemClick EventHandler<ItemEventArgs> on the ListView for a given GetRow(e.Position).
  • Both examples use the constructor (TableViewSource/SpeakersAdapter) of the 'broker' class to pass the actual business object collection for display. This pattern lets the data be filtered/sorted/whatever before being passed in for display.
  • Calling refreshSpeakers() from ViewDidLoad/OnResume is kinda redundant in this example since the list never changes - but in other spots (eg the Favorites screen) you would want to refresh the list each time because it may have changed.

The Code
You can have a look at the MonoTouch code SpeakersViewController.cs and the MonoDroid SpeakersActivity.cs, SpeakersAdapter.cs to see the actual implementation. For MonoDroid you'll also need to check out the layout XML files Speakers.axml and SpeakersItem.axml.

One more thing...
One final piece which takes some getting used to... in MonoTouch this screen is part of a UINavigationController stack and your RowSelected creates a new ViewController object (passing whatever parameters you need) and calls NavigationController.PushViewController(). The ViewController instances in the navigation stack are 'stateful' and it is really easy to pass business objects around as parameters.
In MonoDroid, the ever-present 'back-stack' does not require a specific 'navigation controller' BUT at it's simplest the mechanism is much more primative and involves passing simple type parameters (eg. strings) like this
var intent = new Intent();
intent.SetClass(this, typeof(SpeakerActivity));
intent.PutExtra("Name", speaker.Name);
StartActivity(intent);

There are more sophisticated approaches but it is probably best to keep your parameter-passing to a minimum - this way the Android hardware 'Back' button "just works".

HTH Slava ;-)

2 comments:

  1. Nice Explanation, but to support tablet and future ice cream sandwich you should also show the example using the fragments API which is back compatible by jar to Android 1.6. So you would want the Activity with a fragment with the actual UI logic. That way the fragment could be composed into a more elaborate tablet friendly layout. Hopefully Mono-On-Android will have fragments from the beginning. Thanks for the good blog posts.

    ReplyDelete
  2. There's a few reasons I don't touch on Fragments here:
    * "Novell's Mono-for-Android" never supported Android 3.0 (and therefore the _built-in_ implementation of Fragments)
    * The vast majority of Android devices out there are still phones, not tablets, so Fragments could be considered overkill in many cases
    * This is really an example for beginners, and as such is simpler _without_ Fragments
    * It is a little more work to use external JAR files (gregshackles.com...using-admob-in-a-monodroid-application) -- the only way you could use the Fragment implementation that is floating around for pre-SDK-11 -- which again doesn't really suit a 'beginners example'

    Thanks for the feedback though. I'm sure Fragments will be a part of future Mono-on-Android implementations that target Honeycomb and newer Android releases.

    ReplyDelete

Note: only a member of this blog may post a comment.