Nick's .NET Travels

Continually looking for the yellow brick road so I can catch me a wizard....

Windows Phone 7, Android and iOS with Mono I: Getting Started

Over the past couple of months I’ve been thinking more about how to share resources between applications written for WP7, Android and iOS. Increasingly companies that I consult with are asking how they can build an application once and have it run across multiple platforms. My belief is that this isn’t easily achievable and even if it is would lead to a poor user experience on one or  more platforms.

The reality is that whilst functionality the platforms all offer similar capabilities and services, they don’t all share the same user experience. This means that we’d want to be able to customise how our applications look and behave on the different platforms. Enter the world of Mono.

MonoTouch and MonoDroid not only allow you to write c# code to address iOS and Android respectively, they also provide wrappers for a large proportion of the core apis and controls. Essentially this means that I could write my business logic once in C# and then have the UI component done in a way that is native to the platform I’m targeting. With iOS this would be via Interface Builder, WP7 using Blend and then for Android my understanding is that I’m left writing UI markup in XML (how surprising….).

In this series I’m going to try to share some of the pain/success/difficulties I experience in building the same application across three platforms.

Let’s get started, so what do we need:

Development and Design Tools

Windows Phone 7

- Development tools (Free): Visual Studio 2010 + Expression Blend (http://create.msdn.com)
- Register for developer program/marketplace ($100AUD): Register

iOS

- Note: Make sure you following the instructions on the MonoTouch website re installation
- Development tools (Free): XCode + Interface Builder (http://developer.apple.com/devcenter/ios)
- MonoTouch ($399USD):  http://monotouch.net/
- Register for developer program/app store ($99USD): Register

Android

- Note: Make sure you following the instructions on the MonoDroid website re installation
- Development tools (Free): Java JDK, Android SDK
- MonoDroid (public beta): monodroid-download
- Register for developer program/market ($25USD): Register

Don’t forget for the iOS component you’re going to need to go buy a Mac to use.

First Applications

 

Windows Phone 7

- Launch Visual Studio 2010

- File > New > Project
  > Select Windows Phone Application template under Silverlight for Windows Phone node
  > Enter name of project: NicksNetTravels
  > Click Ok

image

- Make sure Windows Phone Emulator is selected as deployment device (in Standard toolbar)

- F5 to build and run. This will launch the Windows Phone 7 Emulator, deploy the app to it and run it.

image

 

iOS

- Launch MonoDevelop

- File > New > Solution
  > Select  iPhone Window-Based Project under the iPhone and iPad node
  > Enter name of solution: NicksNetTravelsiOS (my applications are going to reside in the same folder, so it’s important that they are named slightly differently).

Screen shot 2011-04-05 at 3.16.50 PM

- (Apple key, or Windows key if using an MS keyboard) + Alt + Enter – build and run application. This will launch the simulator, deploy and run the application

Screen shot 2011-04-05 at 3.23.06 PM

Android

- Launch Visual Studio 2010

- File > New > Project
> Select Mono for Android Application template under Mono for Android node
> Enter name of project: NicksNetTravelsAndroid
> Click Ok

image

- F5 to build and run. This will launch the “Select Device” window.

image

- Click “Start emulator image”

image

- Click OK to launch the MonoDroid emulator (this should have been created during installation – see MonoDroid installation instructions. If not, you’ll just need to follow those instructions now by clicking the Create new emulator image link).

Note: The first time I did this the process failed with some cryptic error. I pressed F5 again, selected the existing emulator instance and it seemed to work fine.

image

Pingbacks and trackbacks (3)+

Comments are closed
Limitations of the WebView in Windows 8 Metro Apps

Nick's .NET Travels

Continually looking for the yellow brick road so I can catch me a wizard....

Limitations of the WebView in Windows 8 Metro Apps

Having worked quite intimately with the WebBrowser control in Windows Phone I was a little flawed by how immature and annoying the WebView for Windows 8 metro app is. As noted by Brent Schooley in his post on Metro WebView Source and HTML workarounds you can work around some of the limitations such as the missing SaveToString method. One of the features of the WebBrowser control that I quite often use is the series of events that are raised around navigating to pages within the control. Unlike the WebBrowser, the WebView is missing events such as Navigating, Navigated and NavigationFailed, and has only the LoadCompleted event. I figured that I should be able to detect when the current page is changing, and thus raise a Navigating event. Turns out that it is possible with a combination of InvokeScript (to invoke some javascript within the page) and window.external.notify (to raise an event when the page in the browser is changing). The following WebViewWrapper class exposes a Navigating event which you can wire an event handler to in order to detect when the browser is being navigated away from the current page (unfortunately there doesn’t seem to be a way to extract the target url, nor to be able to cancel the navigation, unlike the real Navigating event that the WebBrowser control has). I’ve also included a SaveAsString method which wraps the code Brent had in his post.

public class WebViewWrapper
{
    // Maintain a reference to the WebView control so that
    // we can invoke javascript
    public WebView WebView { get; private set; }

    public WebViewWrapper(WebView webView)
    {
        WebView = webView;
    }

    // The Navigating event is a custom event, allowing us to hook/unhook
    // from the ScriptNotify and LoadCompleted events. To invoke this
    // event, we actually invoke the internalNavigating event.
    private event EventHandler<NavigatingEventArgs> internalNavigating;
    public event EventHandler<NavigatingEventArgs> Navigating
    {
        add
        {
            WebView.ScriptNotify+=NavigatingScriptNotify;
            WebView.LoadCompleted+=WireUpNavigating;
            internalNavigating += value;
        }
        remove
        {
            WebView.ScriptNotify -= NavigatingScriptNotify;
            WebView.LoadCompleted-=WireUpNavigating;
            internalNavigating -= value;
        }
    }

    // When each page loads, run a javascript function which wires up
    // an event handler to the onbeforeunload event on the window.  This
    // event is raised when the window is about to unload the current page
    // In the event handler we call window.external.notify in order to raise
    // the ScriptNotify event on the WebView. The javascript function also
    // returns the current document location. This is used to update the
    // AllowedScriptNotifyUris property on the WebView in order to permit
    // the current document to call window.external.notify (remembering
    // that even though we injected the javascript, it’s being invoked in the
    // context of the current document.
    private void WireUpNavigating(object sender, NavigationEventArgs e)
    {
        var unloadFunc = "(function(){" +
                            " function navigating(){" +
                            "  window.external.notify('%%' + location.href);" +
                            "} " +
                            "window.onbeforeunload=navigating;" +
                            "return location.href;" +
                            "})();";
        var host = WebView.InvokeScript("eval", new[] { unloadFunc });
        WebView.AllowedScriptNotifyUris = new[] { new Uri(host) };
    }

   // Check to see if the ScriptNotify was raised by the javascript we
   // injected (ie does it start with %%), and then raise the Navigating
   // event.
   private void NavigatingScriptNotify(object sender, NotifyEventArgs e)
    {
        if (internalNavigating == null) return;
        if(!string.IsNullOrWhiteSpace(e.Value))
        {
            if(e.Value.StartsWith("%%"))
            {
                internalNavigating(this, new NavigatingEventArgs() {LeavingUri = new Uri(e.Value.Trim('%'))});
            }
        }
    }

    public string SaveToString()
    {
        try
        {
            var retrieveHtml = "document.documentElement.outerHTML;";
            var html = WebView.InvokeScript("eval", new[] { retrieveHtml });
            return html;
        }
        catch
        {
            return null;
        }
    }
}

public class NavigatingEventArgs:EventArgs
{
    public Uri LeavingUri { get; set; }
}

Actually making use of the WebViewWrapper class is as simple as creating an instance and then wiring up an event handler.

public BlankPage()
{
    this.InitializeComponent(); 

    var wrapper = new WebViewWrapper(MyWebView);
    wrapper.Navigating += WrapperNavigating;
}

void WrapperNavigating(object sender, NavigatingEventArgs e)
{
    Debug.WriteLine(e.LeavingUri);
    Debug.WriteLine((sender as WebViewWrapper).SaveToString());
}

Comments are closed