“Good artists copy; great artists steal” –Steve Jobs
Every Windows Phone developer who has non-tech friends, relatives or a wife with a Windows Phone knows the problem: a lot of users don’t check their Store regularly and simply don’t update their apps – or at least not often enough. The highly intelligent Pedro Lamas – Senior Nokia engineer, author of the Cimbalino Windows Phone toolkit, and my personal savior when I ran into some very exotic problems with Nokia Music – recently described a way to automatically check for updates inside the app.With his permission, I decided to pack the core logic into a behavior, much like I did with the ratings logic in my previous post – so you can essentially drag this on your main app page and be done with it.Pedro has used something like my SafeBehavior in Cimbalino, and now I use some of his code in the wp7nl library on codeplex. This is how community works. In that light the quote on top of this article is rather badly chosen ;-)
If you drop the UpgradeCheckBehavior, as I have christened this thing, onto the main page of your application, Blend will present you with only two options: a message box caption and a message box text:
- Caption is the text that will appear on the message box when the behavior detects a new version. Default value is “New version!”
- Message is the text that will appear inside the message box when the behavior detects a new version. Default value is “There's a new version of this app available, do you want to update?”
If you are fine with that, you are finished. The app will check for updates automatically on app startup.
The behavior itself is a rather simple SafeBehavior and goes like this:
using System; using System.Globalization; using System.Net; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Xml; using Microsoft.Phone.Tasks; using Wp7nl.Utilities; namespace Wp7nl.Behaviors { /// <summary> /// A behavior checking from in the app if there's an update available. /// </summary> public class UpgradeCheckBehavior : SafeBehavior<Page> { private ManifestAppInfo appInfo; protected override void OnSetup() { appInfo = new ManifestAppInfo(); CheckUpgrade(); } private void CheckUpgrade() { #if !DEBUG GetLatestVersion().ContinueWith(ProcessResult); #endif } } }
Since in debug mode the app will run on you phone or emulator using a generated temporary app id, it won’t be possible to check for new versions anyway so I’ve added an #if statement around the actual check.
Using my ManifestAppInfo helper class I retrieve all the app’s metadata which is used by the GetLatestVersion – in essence a slightly modified version of Pedro’s work:
/// <summary> /// This method is almost 100% stolen from /// http://www.pedrolamas.com/2013/07/24/checking-for-updates-from-inside-a-windows-phone-app/ /// </summary> private Task<Version> GetLatestVersion() { var cultureInfoName = CultureInfo.CurrentUICulture.Name; var url = string.Format( "http://marketplaceedgeservice.windowsphone.com/v8/catalog/apps/{0}?os={1}&cc={2}&oc=&lang={3}", appInfo.ProductId, Environment.OSVersion.Version, cultureInfoName.Substring(cultureInfoName.Length - 2).ToUpperInvariant(), cultureInfoName); var request = WebRequest.Create(url); return Task.Factory.FromAsync(request.BeginGetResponse, result => { try { var response = (HttpWebResponse)request.EndGetResponse(result); if (response.StatusCode != HttpStatusCode.OK) { throw new WebException("Http Error: " + response.StatusCode); } using (var outputStream = response.GetResponseStream()) { using (var reader = XmlReader.Create(outputStream)) { reader.MoveToContent(); var aNamespace = reader.LookupNamespace("a"); reader.ReadToFollowing("entry", aNamespace); reader.ReadToDescendant("version"); return new Version(reader.ReadElementContentAsString()); } } } catch (Exception) { return null; } }, null); }
I think my only addition to this is the try-catch around the method as I tended to have some odd errors sometimes when running it inside Visual Studio. This method craftily downloads the app metadata from the store and extracts a version from it.
And then the only things left of course are comparing the retrieved version to the current version, and if the current version is greater, displaying a message box asking the user to upgrade:
private void ProcessResult(Task<Version> t) { if(t.IsCompleted && t.Result != null ) { var currentVersion = new Version(appInfo.Version); if (currentVersion < t.Result ) { DoShowUpgrade(); } } } private void DoShowUpgrade() { Deployment.Current.Dispatcher.BeginInvoke(() => { var result = MessageBox.Show(Message, Caption, MessageBoxButton.OKCancel); if (result == MessageBoxResult.OK) { var marketplaceReviewTask = new MarketplaceDetailTask(); try { marketplaceReviewTask.Show(); } catch (InvalidOperationException ex) { } } }); }And that's all there's to it. The two dependency properties holding caption and message have been omitted for the sake of brevity. The sad thing of Pedro’s brilliant idea is that it’s quite hard to check if it actually works. Well let me assure you it does, and if you run the demo solution in release mode configuration, it will show you by actually asking for an upgrade:
How is that possible? The app created by Visual Studio runs a temporary ID that should not even be in the Store! That’s correct, unless you make it have a real id. So I opened the WPAppManifest.xml file that’s over in the Properties folder and messed a little with the settings:
I changed the app Id to that of my latest game 2 Phone Pong in the store, and changed the version number to 0.9.0.0 (while the version number in the Store of course is at least 1.0.0.0). Now the app thinks ask the store for the version number of 2 Phone Pong, gets (at the time of this writing) 1.0.0.0 back, ascertains that’s lower than it’s current version and pops up the message. I you hit the OK button it will actually take you to 2 Phone Pong in the Store . This proves Pedro’s code actually works, and you have now a zero code solution to take your users to the best possible version of your app – and no longer an excuses not to do it;-)
Warning – don’t try to deploy a debug version of your app with this id trick on a phone that has the actual app downloaded from the Store on it. That won’t work – you won’t have writing permissions. I haven’t tried the opposite – downloading an app from the store over an app deployed from Visual Studio – but I can imagine that way only lie mud pies.
I will include this soon in the upcoming version of my wp7nl library on codeplex
4 comments:
Awesome work, Joost!! I'm all in for Behaviors and lazy coding!! :)
Thank you Pedro. Coming from you, I really appreciate that.
How about localizing those two behaviour properties, if I have an app that has been localized to more than one language for instance..?
@Jarno - you could bind these properties to some view model that returns localized versions. That's why they are dependency properties, so they support data binding :)
Post a Comment