Porting an App Studio Windows Phone 8 Silverlight Project to Android Using Xamarin

We last looked at how you can use Xamarin to port an App Studio Universal App to Android by making a few changes to the PCL that it generates. This time we'll try to do the same thing with a Windows Phone 8 Silverlight version of the same app.

The first thing we need to do is generate the Windows Phone version from App Studio, which I've done from the project generation page:

Now we can open up the solution in Visual Studio and run it.

   

Create the PCL Project

The first thing you probably notice is that the shared Data project created by App Studio is no longer a PCL. So let's start by adding a new project, making sure to target Xamarin Android.

Notice that this time instead of targeting 8.1 (for a Universal App) we need to target Windows Phone 8. This will be important later as there are many differences between the way the shared code is implemented for this type of project.

For now, let's just move out all the code that's not specific to Windows Phone, which basically leaves behind the Storage and Settings classes for writing data to the phone. This can remain behind and we'll simply add a reference to the PCL to both the Data and UI projects.

Making the Shared Code Portable

Because the original Data project was specific to only Windows Phone, it made use of several libraries and classes which are simply not (or not yet) available in PCL to be shared to Xamarin. One example is the SharpGIS.GZipWebClient library which uses WebClient to allow downloading zipped content for the RSS feed through the RssDataProvider class.

Fortunately, we have another implementation of RssDataProvider that is compatible with the PCL project including Xamarin, which is the one from the Universal App project from last time.

So we'll simply replace the entire implementation of RssDataProvider as shown below. Just like last time, you want to make sure to exclude or comment out the code that references the AppLogs class since it's not defined in the PCL (or implement it!).

Also, once again in the interest of keeping things simple, I've commented out the support for GZip data since the GZipWebClient is also not available in this PCL. The updated RssDataProvider code is below.

using System; 
using System.IO; 
using System.IO.Compression; 
using System.Collections.ObjectModel; 
using System.Threading.Tasks; 
using System.Xml.Linq; 
using System.Net.Http; 
namespace AppStudio.Data { 
    /// <summary> 
    /// Rss data provider class 
    /// </summary> 
    public class RssDataProvider { 
        private Uri _uri; 
        private string _userAgent; 
        
        /// <summary> 
        /// Constructor. 
        /// </summary> 
        /// <param name="url"></param> 
        public RssDataProvider(string url, string userAgent = null) { 
            _uri = new Uri(url); _userAgent = userAgent; 
        } 
        
        /// <summary> 
        /// Starts loading the feed and initializing the reader for the feed type. 
        /// </summary> 
        /// <returns></returns> 
        public async Task<ObservableCollection<RssSchema>> Load() { 
            string xmlContent = await DownloadAsync(); 
            var doc = XDocument.Parse(xmlContent); 
            var type = BaseRssReader.GetFeedType(doc); 
            BaseRssReader rssReader; 
            if (type == RssType.Rss) rssReader = new RssReader(); 
            else rssReader = new AtomReader(); 
            return rssReader.LoadFeed(doc); 
        } 
        
        private async Task<string> DownloadAsync() { 
            var client = new HttpClient(); 
            HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, _uri); 
            if (!string.IsNullOrEmpty(_userAgent)) { 
                request.Headers.UserAgent.ParseAdd(_userAgent); 
            } 
            
            HttpResponseMessage responseMessage = await client.SendAsync(request); 
            using (var stream = await responseMessage.Content.ReadAsStreamAsync()) { 
                using (var memStream = new MemoryStream()) { 
                    // Note: Some RSS feeds return gzipped data even when they are not requested to. 
                    // This code check if data is gzipped and unzip data if needed. 
                    await stream.CopyToAsync(memStream); byte[] buffer = memStream.ToArray(); 
                    memStream.Position = 0; 
                    //if (buffer[0] == 31 && buffer[1] == 139 && buffer[2] == 8) 
                    //{ 
                    // using (var gzipStream = new GZipStream(memStream, CompressionMode.Decompress)) 
                    // { 
                    // return ReadStream(gzipStream); 
                    // } 
                    //} 
                    //else 
                    //{ return ReadStream(memStream); 
                    //} 
                } 
            } 
        } 
        
        private string ReadStream(Stream stream) { 
            using (var reader = new StreamReader(stream)) { return reader.ReadToEnd(); 
                                                          } 
        } 
    } 
} 

But we're not done yet; this new implementation uses HttpClient, which is not available out of the box in the PCL. Fortunately this is easily solved by adding the Microsoft HTTP Client Libraries via nuget. While we're at it, we can also add the fortunately-available PCL Html Agility Pack to resolve the remaining references required by the portable project.

Finally, the HtmlUtil class makes a reference to the ForEach method of the generic List which is available in the Windows Phone version of mscorlib but apparently not in the PCL that we are targeting.

We could write up an extension method or explore other options, but for simplicity and speed, I simply modified CleanHtml method in the HtmlUtil code to use a regular foreach on the collection in memory:

/// <summary> 
/// Clean html tags from string 
/// </summary> 
/// <param name="html">Html to be cleaned</param> 
/// <returns></returns> 
public static string CleanHtml(string html) { 
    html = html ?? ""; 
    string result = null; 
    try { 
        var doc = new HtmlDocument(); 
        doc.LoadHtml(html); 
        var desc = doc.DocumentNode.Descendants() 
            .Where(n => n.Name == "script" || n.Name == "style" || n.Name == "#comment") .ToList(); 
        foreach (var d in desc) d.Remove(); 
        
        result = doc.DocumentNode.InnerText; 
        result = result.TrimStart('n', 't', 'r') .TrimEnd('n', 't', 'r'); 
    } catch (Exception ex) { 
        Debug.WriteLine("There was an error cleaning html: {0}. Error: {1}", html, ex); 
    }
    return result ?? html; 
}

You might recall that last time that, because of the way the AppStudio.Shared library that is included was built, we had to create our own versions and add them to the project. Fortunately this time for WP8 Silverlight, the included library WPAppStudio.Shared2.dll does appear to be compatible so we simply add a reference to that in our PCL.

The updated project can now be rebuilt, and it runs just the same, but now it is referencing the PCL which we can now share to Android.

Adding the Android Project and UI

At this point the project is ready to add the Android project to the solution and create the UI files to run the app. Again I have set the Android manifest for API version 15 (Android 4.0.3) to match my test device.

I was pleasantly surprised to discover that because the naming of the resources in the generated Data project which we moved to PCL exactly match that of the ones we used in the previous Universal App example, I was literally able to copy and paste the class files and resources from that version's Android app over to this one.

The only changes I needed to make were to update the namespaces in the class files so that they could access the layout resources. Otherwise you will likely end up with the error:

The name 'Resource' does not exist in the current context

Depending on the name you used to generate the project as well as what you named the new PCL class library, you may need to shuffle around some namespaces yourself. For the most part, however, this was the only thing that needed to be modified.

Now the project is ready to build and run, and as you can see looks just like the Universal App version from before, but this time sharing the code from the Windows Phone 8 Silverlight project.

 

And just to show I’m not cheating, here it is live on my old g2x test device:

More To Come

Although the process was a bit more involved here, we've seen that even the Windows Phone Silverlight projects can share code easily to other platforms using Xamarin.

Download the source code for yourself and try it out: AppStudioWP8toXamarin.zip

So far we have looked at very basic examples here, using a single data source, which went great along with the simple Android Activities used to build these samples. Next, we should explore how we can leverage Xamarin.Forms to create more advanced, full-featured apps that the App Studio has to offer. Not to mention the yet unexplored iOS versions...

Stay tuned for more!

Enjoyed this post and/or found it useful?
SelArom Dot Net Profile Image
SelAromDotNet

Josh loves all things Microsoft and Windows, and develops solutions for Web, Desktop and Mobile using the .NET Framework, Azure, UWP and everything else in the Microsoft Stack.

His other passion is music, and in his spare time Josh spins and produces electronic music under the name DJ SelArom.



Scroll to top