An awesome part of Windows Phone and Windows Store apps is the ability to offer a trial version to users before they decide to purchase the app. This gives uses an opportunity to try the app for free before paying. There are different ways to offer trials. You can limit functionality while in trial mode. You can offer unlimited trials, allowing the user to use the app forever, or you can allow the user to use the app for limited time period. For Windows Store apps, you can specify in the Store how long the user is allowed to try the app.
Windows Phone apps do not offer this capability. When implementing timed trials in apps, a common task for app developers is to store a value within the app for when the app was first opened.
Note: As this is relevant for both Silverlight and Runtime apps, this blog will contain code for both. The code samples will switch back and forth.
public static bool IsTrialExpired()
{
// Silverlight example
var appSettings = IsolatedStorageSettings.ApplicationSettings;
DateTime expirationDate;
if (appSettings.TryGetValue("TrialExpirationDate", out expirationDate) == false)
{
// first time app opened
expirationDate = DateTime.Now + TimeSpan.FromDays(7);
appSettings["TrialExpirationDate"] = expirationDate;
appSettings.Save();
}
return expirationDate > DateTime.Now;
}
You would then use this method along with the LicenseInformation class.
// Silverlight apps
var licenseInfo = new LicenseInformation();
var isTrial = licenseInfo.IsTrial();
// Runtime apps
var licenseInfo = CurrentApp.LicenseInformation
var isTrial = licenseInfo.IsTrial;
if(isTrial)
{
if(IsTrialExpired())
{
// tell user they can no longer use app
}
// possibly limit some functionality
}
This has been the solution for many apps. The problem with this solution is that the user can uninstall the app and reinstall and get the trial all over again! To overcome this, your app needs to use a service to store user information and check that service for if the trial has expired. There are many cloud based services available to you that are perfect for storing information like this. Two great services are Parse and Azure Mobile Services. This blog post will cover using Azure Mobile Services.
Using a service allows your app to check if the user opened the app at any time. If the user uninstalls the app and reinstalls it, they will now be able to get the trial again. The first step is to set up a mobile service in Azure.
Give the service a name. Name the service something that matches your apps name. If you do not already have a database in azure, create a new free db
When the service is created, click on it, then the Data tab and add new table
Give the table a name that maps to the app itself. This table will be in a database that you will use across multiple apps, so you don’t want something generic like UserInfo, or TrialUser.
Now for some code. First we will create a class that will represent our new table in Azure Mobile Services.
class MyAppUserInfo
{
public string Id { get; set; }
public DateTimeOffset TrialExpirationDate { get; set; }
}
Next let’s create a new interface and class that will allow us to check the service if the users trial has expired.
interface ITrialService
{
/// <summary>
/// Get or sets the amount of time the user is allowed to use the trial.
/// </summary>
TimeSpan TrialPeriod { get; set; }
/// <summary>
/// Gets a value indicating if the trial has expired for the given userId.
/// </summary>
/// <param name="userId">A unique idenitfier for a user</param>
/// <returns>A task that will complete when the data is retrieved.</returns>
Task<bool> IsExpired(string userId);
}
The interface is pretty simple. One property to specify how long the trial should last and one method to see if the trial has expired. For the class, the main this to implement it the IsExpiredAsync method.
Note: Ensure that you add the WindowsAzure.MobileServices nuget package.
class AzureTrialService : ITrialService
{
private readonly IMobileServiceClient _service;
public AzureTrialService(IMobileServiceClient service)
{
_service = service;
TrialPeriod = TimeSpan.FromDays(7);
}
public TimeSpan TrialPeriod { get; set; }
public async Task<bool> IsExpiredAsync(string userId)
{
bool isExpired = false;
IMobileServiceTable<MyAppUserInfo> table = _service.GetTable<MyAppUserInfo>();
var users = await table.Where(trialUser => trialUser.Id == userId).ToListAsync();
var user = users.FirstOrDefault();
if (user == null)
{
// new user, add it
var trialExpirationDate = DateTimeOffset.Now + TrialPeriod;
await table.InsertAsync(new MyAppUserInfo { Id = userId, TrialExpirationDate = trialExpirationDate });
}
else
{
// mobile services will deserialize as local DateTime
isExpired = user.TrialExpirationDate < DateTimeOffset.Now;
}
return isExpired;
}
}
So the IsExpiredAsync method will check Azure Mobile Services for the given userId. If none is returned, it will add a new user. Every time after that it will check the expiration date with the current time.
This current implementation is dependent on the MyAppUserInfo class, which means that we cannot reuse this class for multiple apps. I lie to reuse my code rather than copy and pasting code. Let’s make a modification to the interface and class that allows this to be used for any app.
First we’ll create an abstract class for the user information. We need an abstract class because I was not able to get data from the service using an interface. Might be user error, or an issue with the SDK. Our MyAppUserInfo will implement this abstract class.
/// <summary>
/// Represents information for a trial user.
/// </summary>
abstract class TrialUser
{
/// <summary>
/// Gets or sets the id for the user.
/// </summary>
public abstract string Id { get; set; }
/// <summary>
/// Gets or sets the date the trial will expire for the user.
/// </summary>
public abstract DateTimeOffset TrialExpirationDate { get; set; }
}
Next we will modify the IsExpiredAsync method to allow for a generic parameter, but must be of type ITrialUser
interface ITrialService
{
//other stuff
Task<bool> IsExpiredAsync<TUser>(string userId) where TUser : TrialUser, new();
}
class AzureTrialService : ITrialService
{
// other stuff
public async Task<bool> IsExpiredAsync<TUser>(string userId) where TUser : TrialUser, new()
{
bool isExpired = false;
IMobileServiceTable<TUser> table = _service.GetTable<TUser>();
var users = await table.Where(userInfo => userInfo.Id == userId).ToListAsync();
var user = users.FirstOrDefault();
if ((user == null)
{
// new user, add it
var trialExpirationDate = DateTimeOffset.Now + TrialPeriod;
user = new TUser { Id = userId, TrialExpirationDate = trialExpirationDate };
await table.InsertAsync(user);
}
else
{
// mobile services will deserialize as local DateTime
isExpired = user.TrialExpirationDate < DateTimeOffset.Now;
}
return isExpired;
}
}
Now we have a service that can be used across multiple apps to test if a trial has expired for a user. Let’s take a step back for a moment. Our initial goal here was to stop storing trial expiration locally and start using a service. Check! This new service does accomplish that, but now it checks the service every time the app is opened. There is no need to delay the opening of the app any more than is needed. We can get the expiration information the first time the app is opened and save it to use on later opens. We’ll again modify the IsExpiredAsync method
public async Task<bool> IsExpiredAsync<TUser>(string userId) where TUser : TrialUser, new()
{
bool isExpired = false;
object dateVal;
if (ApplicationData.Current.LocalSettings.Values.TryGetValue("trialExpiration", out dateVal) == false)
{
// get user from server
IMobileServiceTable<TUser> table = _service.GetTable<TUser>();
var users = await table.Where(userInfo => userInfo.Id == userId).ToListAsync();
var user = users.FirstOrDefault();
if (user == null)
{
// new user, add it
var trialExpirationDate = DateTimeOffset.Now + TrialPeriod;
dateVal = trialExpirationDate;
user = new TUser { Id = userId, TrialExpirationDate = trialExpirationDate.ToUniversalTime() };
await table.InsertAsync(user);
}
else
{
// mobile services will deserialize as local DateTime
dateVal = user.TrialExpirationDate;
}
ApplicationData.Current.LocalSettings.Values["trialExpiration"] = dateVal;
}
var expirationDate = (DateTimeOffset)dateVal;
isExpired = expirationDate < DateTimeOffset.Now;
return isExpired;
}
Now when the user opens the app the second time, they will not hit the service. If they uninstall the app and reinstall, it will check the service once.
The TrialService does still rely on the date the user has on their phone. So there are two possible problems. The first is if the user sets the date ahead when they first open the app. The second is if they set the date back before opening the app. While this is probably not likely, you can prevent this if you wish.
To ensure the expiration date cannot be tampered we can set the date in the service script when a new row is created. Go to your mobile service in the Azure portal and select the Data tab and select your table. Select the Script tab and edit the insert script.
function insert(item, user, request) {
var today = new Date();
var expiration = new Date(today.setDate(today.getDate() + 7));
item.TrialExpirationDate = expiration;
request.execute();
}
This script will set the expiration date to be one week after the row is created.
Next select the read script. We will check the expiration when getting the user information.
function read(query, user, request) {
request.execute({
success: function(results) {
results.forEach(function(r) {
// IMPORTANT: Note the case of trialexpirationdate
r.isExpired = r.trialexpirationdate < new Date();
});
request.respond();
}
});
}
If you take this approach, you will need a new IsExpired property on your user info class and you can then remove the TrialExpirationDate property. You can also remove the TrialPeriod property from ITrialService since the period is being set on the server.
So now we have a way to get if the trial has expired, how do we get the id for the user? For Silverlight apps (Windows Phone 8) you can get the the anonymous id for a user through the UserExtendedProperties class.
string userId = UserExtendedProperties.GetValue("ANID2") as string;
For Windows Phone 8.1 Runtime apps, there is no such property (that I have found!). I have seen some posts about using various device ids that are available, but this would mean the user could try the app on a number of devices. So far the best approach I have seen is from Dave Smits on the msdn forums. The solution is to use roaming settings to store a new unique ID.
object userId;
if (ApplicationData.Current.RoamingSettings.Values.TryGetValue("MyAppUserID", out userId) == false)
{
userId = Guid.NewGuid();
ApplicationData.Current.RoamingSettings.Values["MyAppUserID"] = userId;
}
Now with all of this information we can determine if the users trial has expired.
string userId = GetUserId();
var azureClient = new MobileServiceClient(
"https://myapptrials.azure-mobile.net/", "YourSecretKey"));
ITrialService trialService = new AzureTrialService(azureClient);
bool isExpired = await trialService.IsExpiredAsync<MyAppUserInfo>(userId);
if(isExpired)
{
// tell the user the trial is expired
// most likely prompt user to purchase app
}
This TrialService should still be used along with the LicenseInfomation class to first check if the user is using the trial version of the app. You can get the complete code for the service on github.
It's important to also note that all records in Azure Mobile Services have a __createdAt column that can be used as well. You can get the property in your class by adding a JsonPropertyAttribute to a property with the name "__createdAt".