Flurry API in Mono for Android

Hi Everyone,

We have been using Mono for Android at Taste Filter since may. I love coding in C#, it is much faster than coding in Java with all the shortcuts (events, delegates, actions, etc). Mono for Android is generally very good too.

One thing that is lacking, is the amount of Bindings available for it. We need fairly specific analytics at Taste Filter, since we are introducing new ways to interact with content, new touch enabled controls. We want to know what is going on; how people are interacting with it. Have using Google Analytics isn’t stellar; we want a better tool. More reporting, more information passed to the system gathering the stats.

I tried using the automated process for creating a binding from a JAR, but it didn’t yield good results; lots and lots of errors which I did not understand; didn’t seem to be documented.

I found a foundation

I then found this page. The guy had a solid foundation, but I had many questions. How do you determine those signatures? I mean, what is this?


_flurryOnEndSession = JNIEnv.GetStaticMethodID(_flurryClass, "onEndSession", "(Landroid/content/Context;)V");

Determining JNI function signatures

It turns out it was covered in the Xamarin Documentation, but I had overseen it. Sometimes I look for something too specific. You simply use javap.


javap -s -classpath android.jar fully.qualified.Java.Name

And it will give you the actual signature.

public final class java.lang.Thread$State extends java.lang.Enum{
public static final java.lang.Thread$State NEW;
Signature: Ljava/lang/Thread$State;
public static final java.lang.Thread$State RUNNABLE;
Signature: Ljava/lang/Thread$State;
public static final java.lang.Thread$State BLOCKED;
Signature: Ljava/lang/Thread$State;
public static final java.lang.Thread$State WAITING;
Signature: Ljava/lang/Thread$State;
public static final java.lang.Thread$State TIMED_WAITING;
Signature: Ljava/lang/Thread$State;
public static final java.lang.Thread$State TERMINATED;
Signature: Ljava/lang/Thread$State;
public static java.lang.Thread$State[] values();
Signature: ()[Ljava/lang/Thread$State;
public static java.lang.Thread$State valueOf(java.lang.String);
Signature: (Ljava/lang/String;)Ljava/lang/Thread$State;
static {};
Signature: ()V
}

Functions to bind

Since the session (activity) functions were covered already

  • onStartSession (Context context, String apiKey)
  • onEndSession (Context context)

First I wanted to have access to all logEvent functions, and ending timed events.

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

Then I wanted some demographic functions

  • setUserID(String)
  • setAge(int)
  • setGender(byte)

Finally, the error tracking function

  • static void onError (String errorId, String message, String errorClass)

End Result
I created a class that is called FlurryClient. This class registers the functions and give us an API. Thanks to Jonathan Pryor from Xamarin (I suggest reading that!) for the help.

Final source code can be found here.

Don’t forget to add the JAR file to your project, and select “AndroidJavaLibrary” as the action.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Android.App;
using Android.Content;
using Android.OS;
using Android.Runtime;
using Android.Views;
using Android.Widget;
using System.Threading;
using Android.Util;
using Tasty;

namespace WebserviceClients.Tasty.WSClients.Flurry
{
    public class FlurryClient 
    {
        public const string ApiKeyValue = WSConstants.FLURRY_API_KEY;

        private readonly IntPtr _flurryClass;

        // SESSIONS
        private readonly IntPtr _flurryOnStartSession;
        private readonly IntPtr _flurryOnEndSession;
        private readonly IntPtr _flurrySetContinueSessionMillis;

        // SIMPLE EVENTS
        private readonly IntPtr _flurryLogEvent;
        private readonly IntPtr _flurryLogEventMap;

        // TIMED EVENTS
        private readonly IntPtr _flurryLogTimedEvent;
        private readonly IntPtr _flurryLogTimedEventMap;
        private readonly IntPtr _flurryEndTimedEvent;
        
        // ADDITIONAL USER INFORMATION
        private readonly IntPtr _flurrySetUserId;
        private readonly IntPtr _flurrySetAge;
        private readonly IntPtr _flurrySetGender;

        // LOCATION
        private readonly IntPtr _flurrySetReportLocation;

        // ERROR / LOG
        private readonly IntPtr _flurrySetLogEnabled;
        private readonly IntPtr _flurryOnError;

        // CONTINUE SESSION MILLIS
        
        
        public FlurryClient()
        {
            _flurryClass = JNIEnv.FindClass("com/flurry/android/FlurryAgent");

            // SESSIONS
            _flurryOnStartSession = JNIEnv.GetStaticMethodID(_flurryClass, "onStartSession", "(Landroid/content/Context;Ljava/lang/String;)V");
            _flurryOnEndSession = JNIEnv.GetStaticMethodID(_flurryClass, "onEndSession", "(Landroid/content/Context;)V");
            _flurrySetContinueSessionMillis = JNIEnv.GetStaticMethodID(_flurryClass, "setContinueSessionMillis", "(J)V");

            // SIMPLE EVENTS
            _flurryLogEvent = JNIEnv.GetStaticMethodID(_flurryClass, "logEvent", "(Ljava/lang/String;)V");
            _flurryLogEventMap = JNIEnv.GetStaticMethodID(_flurryClass, "logEvent", "(Ljava/lang/String;Ljava/util/Map;)V");

            // TIMED EVENTS
            _flurryLogTimedEvent = JNIEnv.GetStaticMethodID(_flurryClass, "logEvent", "(Ljava/lang/String;Z)V");
            _flurryLogTimedEventMap = JNIEnv.GetStaticMethodID(_flurryClass, "logEvent", "(Ljava/lang/String;Ljava/util/Map;Z)V");
            _flurryEndTimedEvent = JNIEnv.GetStaticMethodID(_flurryClass, "endTimedEvent", "(Ljava/lang/String;)V");

            // ADDITIONAL USER INFORMATION
            _flurrySetUserId = JNIEnv.GetStaticMethodID(_flurryClass, "setUserId", "(Ljava/lang/String;)V");
            _flurrySetAge = JNIEnv.GetStaticMethodID(_flurryClass, "setAge", "(I)V");
            _flurrySetGender = JNIEnv.GetStaticMethodID(_flurryClass, "setGender", "(B)V");

            // LOCATION
            _flurrySetReportLocation = JNIEnv.GetStaticMethodID(_flurryClass, "setReportLocation", "(Z)V");

            // ERRROR / LOG
            _flurrySetLogEnabled = JNIEnv.GetStaticMethodID(_flurryClass, "setLogEnabled", "(Z)V");
            _flurryOnError = JNIEnv.GetStaticMethodID(_flurryClass, "onError", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V");
        }

        #region SESSIONS
        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)));
        }

        public void setContinueSessionMillis(long millis)
        {
            ExceptionSafe(() => JNIEnv.CallStaticVoidMethod(_flurryClass, _flurrySetContinueSessionMillis, new JValue(millis)));
        }
        #endregion

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

        public void LogEvent(string eventName, Dictionary parameters)
        {
            JavaDictionary actualParams = new JavaDictionary(parameters);
            ExceptionSafe(() => JNIEnv.CallStaticVoidMethod(_flurryClass, _flurryLogEventMap, new JValue(new Java.Lang.String(eventName)), new JValue(actualParams)));
        }
        #endregion

        #region TIMED EVENTS
        public void LogTimedEvent(string eventName, bool timed)
        {
            ExceptionSafe(() => JNIEnv.CallStaticVoidMethod(_flurryClass, _flurryLogTimedEvent, new JValue(new Java.Lang.String(eventName)), new JValue(timed)));
        }

        public void LogTimedEvent(string eventName, Dictionary parameters, bool timed)
        {
            JavaDictionary actualParams = new JavaDictionary(parameters);
            ExceptionSafe(() => JNIEnv.CallStaticVoidMethod(_flurryClass, _flurryLogTimedEventMap, new JValue(new Java.Lang.String(eventName)), new JValue(actualParams), new JValue(timed)));
        }

        public void EndTimedEvent(string eventName)
        {
            ExceptionSafe(() => JNIEnv.CallStaticVoidMethod(_flurryClass, _flurryEndTimedEvent, new JValue(new Java.Lang.String(eventName))));
        }
        #endregion

        #region ADDITIONAL USER INFORMATION
        public void setUserId(string userId)
        {
            ExceptionSafe(() => JNIEnv.CallStaticVoidMethod(_flurryClass, _flurrySetUserId, new JValue(new Java.Lang.String(userId))));
        }

        public void setAge(int age)
        {
            ExceptionSafe(() => JNIEnv.CallStaticVoidMethod(_flurryClass, _flurrySetAge, new JValue(age)));
        }

        public void setGender(byte gender)
        {
            ExceptionSafe(() => JNIEnv.CallStaticVoidMethod(_flurryClass, _flurrySetGender, new JValue(gender)));
        }
        #endregion

        #region LOCATION
        public void setReportLocation(bool enabled)
        {
            ExceptionSafe(() => JNIEnv.CallStaticVoidMethod(_flurryClass, _flurrySetReportLocation, new JValue(enabled)));
        }
        #endregion

        #region ERROR / LOG
        public void setLogEnabled(bool enabled)
        {
            ExceptionSafe(() => JNIEnv.CallStaticVoidMethod(_flurryClass, _flurrySetLogEnabled, new JValue(enabled)));
        }

        public void onError(string errorId, string message, string errorClass)
        {
            ExceptionSafe(() => JNIEnv.CallStaticVoidMethod(_flurryClass, _flurryOnError, new JValue(new Java.Lang.String(errorId)), new JValue(new Java.Lang.String(message)), new JValue(new Java.Lang.String(errorClass))));
        }
        #endregion

        #region HELPERS
        private static void ExceptionSafe(Action action)
        {
            try
            {
                action();
            }
            catch (ThreadAbortException)
            {
                throw;
            }
            catch (Exception exception)
            {
                Log.Info("FlurryClient", "Exception seen in calling Flurry through JNI {0}", exception.ToString());
            }
        }
        #endregion

        ~FlurryClient()
        {
            JNIEnv.DeleteGlobalRef (_flurryClass);
        }
    }
}

Good luck, ask for help if you need some

M

3 thoughts on “Flurry API in Mono for Android

  1. I have not been able to get this working. Everything integrates and builds fine, but in the end nothing is actually sent to my flurry dashboard… I have asked on stack-overflow and contacted flurry and neither were able to help me. Below is the stack-overflow to the unanswered question. I would greatly appreciate if you could give it a look, you might know the fix. Thank you.

    http://stackoverflow.com/questions/17444323/flurry-mono-android

  2. Just a note, it sometimes takes Flurry 24 hours to show results. But it does work.

Leave a Reply

Your email address will not be published. Required fields are marked *

* Copy This Password *

* Type Or Paste Password Here *

55,073 Spam Comments Blocked so far by Spam Free Wordpress

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>