
I didn't really want to change very much - being so close to done at this point. I also wanted to allow for enough of the game to be played without nagging people to purchase it. After thinking about it for a little while, I decided to disable the new game button in trial mode.
Disabling the buttons was consistent with the "Flowerz" experience from the above video and would be relatively easy to implement. To implement it's essentially:
using Microsoft.Phone.Marketplace;
public LicenseInformation licenseInfo = new LicenseInformation();
private void NewGame_Click(object sender, EventArgs e) {
if (!licenseInfo.IsTrial()) {
GameFactory.CreateNewGame();
}
else {
// do something that asks to buy the product.
}
}
Now note there are warnings all over MSDN that calling IsTrial() can take about 60ms - so you should consider caching it. However, since I was only going to call it on a button push, and not in a game loop, I didn't have to worry as much - people can tolerate about a 200ms - 500ms delay on a button push before noticing that work hasn't happened.
What to display when buying the app
I went to the howto MSDN page for the Trial API to get started. I am not normally this critical of an MSDN article - but I think these articles are based on pseudo code. They just don't deal in the real world problems that you run into using the API - which is unusual for MSDN, they're usually pretty good about this stuff.
Here is the dilemma I ran into: The suggested workflow of buying an app involved navigating to a second page, then offering to try or buy the application.

I started down the path of creating a "buy" page, which I would ask the frame to navigate to when someone pushed the new game button in trial mode. Because I'd already done a "share" page, I just copied that and started modifying. I went as far as wiring in the Marketplace task on a button push so I could send people to the right spot to buy the app.
I made two discoveries at this point - this little bit of code:
using Microsoft.Phone.Tasks;
public MarketplaceDetailTask detailTask = new MarketplaceDetailTask();
private void buy_now_Click(object sender, RoutedEventArgs e){
detailTask.Show();
}
..shows an error under the debugger (the marketplace does not like to be shown with the USB plugged in) - and even running standalone it wont work (obviously cause the app isn't in the store yet).
This was when I started to suspect the presence of untested pseudocode:
For the “Buy Now” button behavior described in our example, your code might look something like this:
...so I went in search of more information. At this point I stumbled across the second article, about testing and debugging your trial applications.
I had just been burned by tombstoning and the loaded event - especially in the "share" page which I had just copied to make my new "buy" page. If someone bumped the search button while I was navigated to the share page, when they pressed the back button to get back to nine lettuce, my app would be ressurected while it was navigated to a different page. I needed to make sure that backing out would load up my main panorama correctly.
Getting "share" right with tombstoning took effort, and I was keen to be able to test the same sorts of situations with the new "buy" page. All these questions started rattling through my head about the transition out of the MarketplaceDetailTask - questions I couldn't really test out as a developer...

I didn't like the solution in how to debug and test your trial apis because the magical box in step two was replaced by a MessageBox.Show. This would neither simulate tombstoning nor simulate starting the application over again with IsTrial() now set to false.
In the end I resolved that I needed a solution that would be tombstoning safe. So I deleted my "buy" page from the project. Instead of navigating to a second page which I would have difficulty testing, I decided to fade in a button on the panorama control. This eliminated my navigation issues and made something I could easily test.
I wound up writing a TrialManager that behaves differently in debug and release. In both it calls the MarketplaceDetailTask. This is important to do even if it throws an error in debug because you need to test having your app relaunch after purchase.
Essentially in DEBUG, I write a file when MarketpaceDetailTask.Show() is called - when the app resumes I implement IsTrial() to check for the file in DEBUG. The emulator doesn't remember files if you close it, so it's a great way to always be able to test the trial API but get past it. The device however will remember files between debugging sessions, so I also have a Reset method to clear the debug license off the phone.
Here is how I implemented a Debug version of the Trial APIs that test tombstoning after calling the MarketplaceDetailTask:

And the code for my TrialManager, which I think is more practical than the code in MSDN. Feel free to use and modify in your application.
using Microsoft.Phone.Tasks;
using System.IO.IsolatedStorage;
namespace NineLettuce {
internal static class TrialManager {
#if DEBUG
private const string LicenseFileName = "DebugFullLicense.txt";
#endif
public static bool IsTrial {
get {
#if DEBUG
System.Threading.Thread.Sleep(60); // simulate delay of IsTrial() call
// check if we have a license on the app.
IsolatedStorageFile myStore = IsolatedStorageFile.GetUserStoreForApplication();
return !(myStore.FileExists(LicenseFileName));
#else
// in release use the real APIs
LicenseInformation licenseInfo = new LicenseInformation();
return licenseInfo.IsTrial();
#endif
}
}
public static void ShowMarketplace() {
MarketplaceDetailTask detailTask = new MarketplaceDetailTask();
detailTask.Show();
#if DEBUG
// if tombstoning occurs, write out a file so
// we can test getting past trial mode
IsolatedStorageFile myStore
= IsolatedStorageFile.GetUserStoreForApplication();
myStore.CreateFile(LicenseFileName);
#endif
}
public static void ResetTrial() {
#if DEBUG
// function for clearing off the license
IsolatedStorageFile myStore
= IsolatedStorageFile.GetUserStoreForApplication();
if (myStore.FileExists(LicenseFileName)) {
myStore.DeleteFile(LicenseFileName);
}
#endif
}
}
}
Conclusions
Keep the buying experience simple, you don't need to navigate places to have a good experience in purchasing. If I had my druthers I would have put the new game button on the first page so it was more obvious.
If you want to do more elaborate things than I have done, you can use the IsolatedStorageFile solution to remember session state. If IsTrial() is false, and you've got something scribbled in a file, then you know you can show a particular screen or advance to a particular level.
I hope that as time goes on the developer experience in this area improves, as even with this solution it is still difficult to know what the experience will be when the app hits the store!
Related reads: Nine Lettuce, Beware the Loaded Event, Tech-Ed Video, How to Implement the Trial Experience in a Windows Phone Application, How to Test and Debug the Trial APIs
No comments:
Post a Comment