Sunday, April 29, 2012

Using Flurry Analytics from MonoTouch, MonoDroid and WP7 (MvvmCross)

UPDATE - want to do more - someone took this code on to the next level - http://blog.martinlegris.com/2012/11/14/flurry-api-in-mono-for-android/

----

Within a recent MvvmCross project, we used www.flurry.com for our analytics.


Basically what we did was log all important events from the ViewModel using an IAnalytics interface, and then each platform supplied an implementation of IAnalytics like:
WP7:
public class FlurryAnalytics : IAnalytics
{
    public const string ApiKeyValue = "--- your key ---";

    public void StartSession()
    {
        FlurryWP7SDK.Api.StartSession(ApiKeyValue);
    }

    public void LogEvent(string eventName)
    {
        FlurryWP7SDK.Api.LogEvent(eventName);
    }
}
public class FlurryAnalytics : IAnalytics
{
    public const string ApiKeyValue = "37SHD8L8VATPBS88AMHU";

    public void StartSession()
    {
        FlurryAPI.StartSession (ApiKeyValue);
    }

    public void LogEvent(string eventName)
    {
        FlurryAPI.LogEvent(eventName);
    }
}
Android (a bit more complicated - it needs a hook from every start/stop of an activity):
public class FlurryAnalytics : IAnalytics, IAndroidActivityTracker
{
    public const string ApiKeyValue = "--- your key ---";

    private readonly IntPtr _flurryClass;
    private readonly IntPtr _flurryOnStartSession;
    private readonly IntPtr _flurryOnEndSession;
    private readonly IntPtr _flurryLogEvent;

    public FlurryAnalytics()
    {
        _flurryClass = JNIEnv.FindClass("com/flurry/android/FlurryAgent");
        _flurryOnStartSession = JNIEnv.GetStaticMethodID(_flurryClass, "onStartSession",
                                                         "(Landroid/content/Context;Ljava/lang/String;)V");
        _flurryOnEndSession = JNIEnv.GetStaticMethodID(_flurryClass, "onEndSession", "(Landroid/content/Context;)V");
        _flurryLogEvent = JNIEnv.GetStaticMethodID(_flurryClass, "logEvent", "(Ljava/lang/String;)V");
    }

    public void StartSession()
    {
        // not used in Android - Android relies on Activity tracking instead
    }

    public void LogEvent(string eventName)
    {
        ExceptionSafe(() => JNIEnv.CallStaticVoidMethod(_flurryClass, _flurryLogEvent, new JValue(new Java.Lang.String(eventName))));
    }

    private static void ExceptionSafe(Action action)
    {
        try
        {
            action();
        }
        catch (ThreadAbortException)
        {
            throw;
        }
        catch (Exception exception)
        {
            UITrace.Trace("Exception seen in calling Flurry through JNI " + exception.ToLongString());
        }
    }

    public void OnStartActivity(Activity activity)
    {
        ExceptionSafe(() => JNIEnv.CallStaticVoidMethod(_flurryClass, _flurryOnStartSession, new JValue(activity), new JValue(new Java.Lang.String(ApiKeyValue))));
    }

    public void OnStopActivity(Activity activity)
    {
        ExceptionSafe(() => JNIEnv.CallStaticVoidMethod(_flurryClass, _flurryOnEndSession, new JValue(activity)));
    }
}

Each of these platforms simply injected their implementation into IoC during setup - .i.e:

    this.RegisterServiceInstance (new FlurryAnalytics());
Among the messages recorded were the creation of every ViewModel - which gave us an easy to see picture of which pages were being used in our app. We did this through a BaseViewModel implementation which logged its type name on creation - simple, quick and clean:

public abstract class BaseViewModel 
    : MvxViewModel
    , IMvxServiceConsumer
{
    #region Protected API

    protected BaseViewModel()
    {
        Analytics.LogEvent("ViewModelCreate:" + GetType().Name);
    }

    protected IAnalytics Analytics
    {
        get { return this.GetService(); }
    }
}

9 comments:

  1. Hi. I was wondering if you could provide guidelines to add these functions?

    * logEvent (String eventId, Map parameters)
    * logEvent (String eventId, boolean timed)
    * endTimedEvent (String eventId)

    ReplyDelete
  2. Probably best to ask that question on forums.xamarin.com - just link back to the code here (or cut and paste) and someone who knows the native binding will help

    ReplyDelete
  3. Trust me I did. They helped you build this thing?

    ReplyDelete
  4. Could you provide your Android project source with JARs? thanks.

    ReplyDelete
  5. I think you have to get the jars from flurry - and the above code is all I have that's not in client specific code. Sorry

    ReplyDelete
  6. I updated it to cover the following functions (Android JNI):

    * setContinueSessionMillis
    * logEvent(string eventName, bool timed)
    * logEvent(string eventName, Map params)
    * logEvent(string eventName, Map params, bool timed)
    * endTimedEvent(string eventName)
    * setUserId(string userId)
    * setAge(int age)
    * setGender(byte gender)
    * setReportLocation(bool enabled)
    * setLogEnabled(bool enabled)
    * onError(string errorId, string message, string errorClass)

    Let me know if you want to update your source. Also, I am assuming the events are going out; how long do they take before they show up on Flurry's dashboard? And besides adding the "jar" as putting it to "JavaLibrary", is there anything else to do? Thanks.

    ReplyDelete
  7. yes please! Please post the code as a http://gist.github.com and I'll make sure it gets publicised as best I can :)

    Generally it used to take between 3 and 12 hours for the data to show up in the flurry website.

    ReplyDelete
  8. https://gist.github.com/4069294

    thanks!

    ReplyDelete
    Replies
    1. Thanks "Unknown" - publicised here http://slodge.blogspot.co.uk/2012/11/updated-flurry-analytics-in-monodroid.html

      Delete