Thursday, May 24, 2012

100 things I learned while writing Windows Phone 7 apps


This is a work in progress:

  1. Ship early, ship often.  If it's good enough to show your spouse or kids, that's version 1.0.
  2. Don't add features that take a long time to implement. Wait for someone to ask for them.
  3. Sleep deprivation seems to be cumulative.
  4. Contests are good motivation. They have deadlines.
  5. A version of your app is probably already translated into Chinese.
  6. Put money you make from your apps back into your apps.
  7. Try AppDeals.
  8. The community is a good and friendly one.
  9. Use analytics.
  10. Localize your successful apps.
  11. Have a mobile FAQ.  
  12. Have a link to your FAQ right where a user might be getting frustrated.
  13. Test your app in another Locale with different text and time formatting rules.
  14. Look for Microsoft and phone players' events in your area.  It's worth driving to your local big city.
  15. Use nice icons--especially XAML ones.
  16. Use Twitter.  
  17. Post about your apps.
  18. Read what others have found.
  19. Allow the user to create secondary tiles that go directly to useful features of your app.
  20. Kids are awesome testers.
  21. Leverage Facebook.
  22. Take an occasional break--don't work all night, every night.
  23. Use the beta feature of the App Hub.
  24. Use advertising.
  25. Think about adveristing alternatives.
  26. Combine multiple advertising approaches.
  27. Don't set Canvas.Top or Canvas.Left to large or rapidly changing values off of the surface.
  28. Use third party tools.
  29. You're going to have to put on a marketing hat at some point.  Check this out.
  30. Issuing an update is another chance to catch people's attention. Make the new feature noticeable.
  31. Leverage others' APIs.
  32. Put the language of the UI thread into your analytics.  Later choose languages to localize to based on this.
  33. Remember, the US may not be your biggest market.
  34. Search for your app (in quotes) on Google.
  35. Search for your app (in quotes) on Twitter.  Save this search.
  36. Include a feedback page with links to support, FAQ, Twitter, and Facebook.
  37. Include a link to search the marketplace for your other apps.
  38. Reply to support requests in no more than a day.
  39. The quicklier you respond, the more likely you'll get a response from them.
  40. Put your feature ideas in a list so you don't forget them.
  41. Sort your features and bug lists by priority. Do them in that order.
  42. Indie developer with a day job? You wear 4 or 5 hats in limited time--don't just think like a dev.
  43. Read your reviews. Use a tool or app that shows you reviews from all markets and translates them.
  44. Do not link to Bing Maps if you plan to publish to the Chinese Marketplace.
  45. Test in both Dark and Light themes.
  46. To make a background image work in both themes, use Opacity < 1.

Sunday, January 8, 2012

Windows Phone 7: Getting Started with Analytics

Initially, I went with PreEmptive's RTI which was great at first, although I didn't like running the tool to generate the XAP nor the restrictions of using attributes.  It was worth it though for a while.  When RTI got too latent and the freeness waned, I looked to MSAF.  I was directed to a great article to get started "Statistics for your Windows Phone application..." by Mark Monster.  This may be enough for you to get up and running.  Again, it's a great start but turned out to not be enough for me.

So what happened?
Initially, with a couple statistics being logged, everything seemed promising so I started sprinkling my version of Mark's AnalyticsTracker throughout my code mostly replacing the spots where I had used RTI.  I launched the debugger on the Emulator and....it exited.  I thought that was weird.  Since I was using the debugger, I expected any exception to break into Visual Studio.  You may have experienced this before--an exception occurs early in your program and it seems as if the debugger's not all hooked up by the time it happens.  What was a little more perplexing was that, occasionally, it would launch without issue.  This made me suspect a race condition...but with what?

The Race is on
Considering what code I had just added, I figured the AnalyticsTracker was being used before the AnalyticsService had started.  This was easy to verify by making the tracker check for the existence of the service and verify it was initialized.

So what to do about the events that occur too early?  I'm not too fond of just rearranging things until it works as it seems like a lurking issue that may show up on just the right device.  Instead, I made my AnalyticsTracker buffer the events that occur early on.

Here's the updated AnaylticsTracker class:

   /// <summary>
   /// Creates an analytics tracker which can be used to send events through the analytics provider.
   /// NOTE: Instances of this class should not be kept around and reused over time b/c initialization in
   /// the constructor may fail if it's created too soon and, if it does, events will be deferred continually.
   /// You can use it in such a scenario but only if you call <see cref="Connect"/> when <see cref="IsConnected"/> is false.
   /// </summary>
   public class AnalyticsTracker
   {
      /// <summary>
      /// Indicates whether tracking is enabled.  For debugging mostly.
      /// </summary>
      public static bool Enabled { get; set; }

      static readonly Queue<AnalyticsEvent> deferred = new Queue<AnalyticsEvent>();

      static AnalyticsTracker()
      {
         Enabled = true;
      }

      /// <summary>
      /// Creates a default instance.
      /// </summary>
      public AnalyticsTracker()
      {
         Connect();
      }

      /// <summary>
      /// [Re]attempts to connect to the analytics service.
      /// </summary>
      public void Connect()
      {
         if (!Enabled)
            return;

         // If already connected, we're good.
         if (IsConnected)
            return;

         var service = Application.Current.ApplicationLifetimeObjects.OfType<AnalyticsService>().SingleOrDefault();
         if (service != null && !service.IsStarted)
            return;

         try
         {
            CompositionInitializerEx.SatisfyImports(this);
            // Flush any premature events.
            Flush();
         }
         catch (Exception ex)
         {
            Debug.WriteLine("Error creating analytics event. Deferring. {0}", ExceptionFormatter.FormatMessage(ex));
         }
      }

      /// <summary>
      /// Indicates whether this tracker is connected to the service.
      /// </summary>
      public bool IsConnected
      {
         get { return Log != null; }
      }

      /// <summary>
      /// Import from the analytics service provider.  Do not access directly.
      /// </summary>
      [Import("Log")]
      public Action<AnalyticsEvent> Log { get; set; }

      /// <summary>
      /// Logs or defers logging of an event.
      /// </summary>
      /// <remarks>If the analytics service is not yet up, the events are queued.</remarks>
      public void Track(string category, string name, string actionValue = null)
      {
         var analyticsEvent = new AnalyticsEvent { Category = category, Name = name, ObjectName = actionValue };
         if (Log == null)
         {
            lock (deferred)
               deferred.Enqueue(analyticsEvent);
         }
         else
            Log(analyticsEvent);
      }

      /// <summary>
      /// Track an exception.
      /// </summary>
      public void Track(Exception error)
      {
         Track("Errors", ExceptionFormatter.FormatName(error), error.ToString());
      }

      /// <summary>
      /// Flush deferred events.
      /// </summary>
      void Flush()
      {
         // Check before locking unnecessarily.
         if (deferred.Count <= 0) 
            return;
         
         lock (deferred)
         {
            while (deferred.Count > 0)
               Log(deferred.Dequeue());
         }
      }
   }

Issues with XAML Approach
There is a TrackAction trigger you can use in XAML.  I didn't like how this ended up impacting the organization of my data.  If you use this approach, you might want to take a look at the data coming into your provider before settling on it especially considering localization.

Other Things to Watch Out for
The Composition namespace.  This was frustrating.  Somehow I ended up adding a reference to the wrong Composition container.  If MEF can't find your imports, keep this in mind.  I noticed they included a CompositionInitializerEx in the WebAnalytics namespace.  I'd stick with this one to be sure you're using what they're using.
Don't use localized strings.  I touched on this above.  If any of the strings you use for your analytics are localized, you might find it hard to aggregate the data disregarding language.  For example, it'd be easy enough to track the current tool your users are choosing by its name (say CurrentTool.Name) but consider using the type instead if the tool's name is localized (e.g., CurrentTool.GetType().Name or CurrentTool.ToString()).

Wrapping Up
I hope this saves you time if you have any of these issues.  There are many more things to consider once you get analytics pumping.  For example, you might want to log the language your app is launched with.  By default, there's something logged automatically but there are issues.  I'll try to cover this in a more specific post.  In the meantime, try to think ahead about what you'll want to know.