Created
February 13, 2018 21:26
-
-
Save profexorgeek/7e89058630dffc120fa19001b11d6f4d to your computer and use it in GitHub Desktop.
Basic cross-platform Google Analytics implementation.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Text; | |
using System.Net; | |
using System.Collections.Specialized; | |
using System.Collections.Generic; | |
#if DESKTOP_GL | |
using System.Web; | |
#endif | |
namespace Masteroid.Analytics | |
{ | |
public static class DictionaryExtensions | |
{ | |
/// <summary> | |
/// Helper extension to convert dictionary to name value collection. | |
/// Dictionaries are easier to use, especially when merging and | |
/// checking for overriding keys. This allows easy conversion | |
/// of a dictionary to the NameValueCollection object | |
/// expected by WebClient.UploadValues() | |
/// </summary> | |
/// <param name="dict">The dictionary</param> | |
/// <returns>NameValueCollection created from dictionary</returns> | |
public static NameValueCollection ToNameValueCollection(this Dictionary<string, string> dict) | |
{ | |
var nvc = new NameValueCollection(); | |
foreach (var kvp in dict) | |
{ | |
nvc.Add(kvp.Key, kvp.Value); | |
} | |
return nvc; | |
} | |
} | |
/// <summary> | |
/// Provides cross-platform Google Analytics tracking using the | |
/// Google Measurement Protocol as defined here: | |
/// https://developers.google.com/analytics/devguides/collection/protocol/v1/ | |
/// </summary> | |
public class GoogleAnalytics : IDisposable | |
{ | |
/// <summary> | |
/// Singleton pattern accessor | |
/// </summary> | |
/// <value>The instance.</value> | |
public static GoogleAnalytics Instance | |
{ | |
get | |
{ | |
if (instance == null) | |
{ | |
instance = new GoogleAnalytics(); | |
initialized = false; | |
} | |
return instance; | |
} | |
} | |
/// <summary> | |
/// Optional application name. Can include OS or platform | |
/// to filter analytics by platform. For example: | |
/// 'GameName PC' or 'GameName Mac' | |
/// </summary> | |
public string AppName { get; set; } | |
/// <summary> | |
/// Optional build version of teh application | |
/// </summary> | |
public string AppVersion { get; set; } | |
/// <summary> | |
/// Optional screen resolution the app is | |
/// running at. Providing this can provide | |
/// data on most popular resolutions for testing | |
/// </summary> | |
public string Resolution { get; set; } | |
/// <summary> | |
/// The version of Google Analytics API to use. | |
/// Currently only 1 is supported by Google. | |
/// </summary> | |
public int GAVersion { get; set; } = 1; | |
/// <summary> | |
/// The unique user identifier, used like a web cookie. | |
/// If none exists, a guid is automatically created on first get. | |
/// </summary> | |
public string ClientId | |
{ | |
get | |
{ | |
if(string.IsNullOrWhiteSpace(clientId)) { | |
clientId = Guid.NewGuid().ToString("N"); | |
} | |
return clientId; | |
} | |
set | |
{ | |
clientId = value; | |
} | |
} | |
/// <summary> | |
/// Google analytics tracking code, usually starts with "UA-" | |
/// </summary> | |
public string TrackingId | |
{ | |
get | |
{ | |
return trackingId; | |
} | |
} | |
private const string gaUrl = "http://www.google-analytics.com/collect"; | |
private static GoogleAnalytics instance; | |
private static bool initialized; | |
#if DESKTOP_GL | |
private WebClient client; | |
#endif | |
private string trackingId; | |
private string clientId; | |
/// <summary> | |
/// Private constructor to enforce singleton pattern | |
/// </summary> | |
private GoogleAnalytics() | |
{ | |
} | |
/// <summary> | |
/// Initialize this class with a tracking ID and optional version | |
/// </summary> | |
/// <param name="trackingId">Google Analytics Tracking Code</param> | |
public void Initialize(string trackingId, string appName = null, string appVersion = null, string resolution = null) | |
{ | |
#if DESKTOP_GL | |
client = new WebClient(); | |
client.Headers[HttpRequestHeader.ContentType] = "application/x-www-form-urlencoded"; | |
#endif | |
this.trackingId = trackingId; | |
this.AppName = appName; | |
this.AppVersion = appVersion; | |
this.Resolution = resolution; | |
initialized = true; | |
} | |
/// <summary> | |
/// Disposes of the web client, uninitializes and nullifies the instance | |
/// </summary> | |
/// <remarks>Call <see cref="Dispose"/> when you are finished using the <see cref="T:Masteroid.GoogleAnalytics"/>. The | |
/// <see cref="Dispose"/> method leaves the <see cref="T:Masteroid.GoogleAnalytics"/> in an unusable state. After | |
/// calling <see cref="Dispose"/>, you must release all references to the <see cref="T:Masteroid.GoogleAnalytics"/> so | |
/// the garbage collector can reclaim the memory that the <see cref="T:Masteroid.GoogleAnalytics"/> was occupying.</remarks> | |
public void Dispose() | |
{ | |
#if DESKTOP_GL | |
client.Dispose(); | |
client = null; | |
#endif | |
initialized = false; | |
instance = null; | |
} | |
/// <summary> | |
/// Helper encodes string for URLs | |
/// </summary> | |
/// <returns>Encoded string</returns> | |
private string Encode(string str) | |
{ | |
return System.Net.WebUtility.UrlEncode(str); | |
} | |
/// <summary> | |
/// Fire an event tracking request to google | |
/// </summary> | |
/// <returns>The response from Google</returns> | |
/// <param name="category">Event category</param> | |
/// <param name="action">Event action</param> | |
/// <param name="label">Optional label</param> | |
/// <param name="label">Optional value</param> | |
public byte[] TrackEvent(string category, string action, string label = null, int? eventValue = null, string screenName = null) | |
{ | |
var reqParams = new Dictionary<string, string>(); | |
// build custom event parameters | |
reqParams.Add("t", "event"); | |
reqParams.Add("ec", category); | |
reqParams.Add("ea", action); | |
if (label != null) | |
{ | |
reqParams.Add("el", label); | |
} | |
if (eventValue != null) | |
{ | |
reqParams.Add("ev", eventValue.ToString()); | |
} | |
if(screenName != null) | |
{ | |
reqParams.Add("cd", screenName); | |
} | |
return SendTrackingRequest(reqParams); | |
} | |
/// <summary> | |
/// Fires screen tracking only request to google | |
/// To fire screen and event tracking request, use the TrackEvent method and pass a screen name | |
/// Some useful info is here: https://stackoverflow.com/questions/30414559/google-analytics-screenname-for-events | |
/// </summary> | |
/// <param name="screenName">The name of the screen</param> | |
/// <returns>The response from Google</returns> | |
public byte[] TrackScreenView(string screenName) | |
{ | |
var reqParams = new Dictionary<string, string>(); | |
// build custom params | |
reqParams.Add("t", "screenview"); | |
reqParams.Add("cd", screenName); | |
return SendTrackingRequest(reqParams); | |
} | |
/// <summary> | |
/// The base method that actually broadcasts a tracking | |
/// request. Automatically sends common information | |
/// </summary> | |
/// <param name="customParameters">A dictionary of key/value pairs to be used as request payload</param> | |
/// <returns>Byte array response from Google</returns> | |
public byte[] SendTrackingRequest(Dictionary<string, string> customParameters) | |
{ | |
// EARLY OUT: don't track in debug mode. This pollutes game stats! | |
#if DEBUG | |
return new byte[0]; | |
#endif | |
if (!initialized || string.IsNullOrWhiteSpace(TrackingId)) | |
{ | |
throw new Exception("Google Analytics has not been initialized or tracking code is invalid."); | |
} | |
// build common request parameters | |
var rp = new Dictionary<string, string>(); | |
// build custom event parameters | |
rp.Add("v", GAVersion.ToString()); | |
rp.Add("tid", TrackingId); | |
rp.Add("cid", clientId); | |
if (!string.IsNullOrWhiteSpace(AppName)) | |
{ | |
rp.Add("an", AppName); | |
} | |
if (!string.IsNullOrWhiteSpace(AppVersion)) | |
{ | |
rp.Add("av", AppVersion); | |
} | |
if (!string.IsNullOrWhiteSpace(Resolution)) | |
{ | |
rp.Add("sr", Resolution); | |
} | |
// merge custom params, overriding existing keys | |
foreach (var kvp in customParameters) | |
{ | |
if (rp.ContainsKey(kvp.Key)) | |
{ | |
rp[kvp.Key] = kvp.Value; | |
} | |
else | |
{ | |
rp.Add(kvp.Key, kvp.Value); | |
} | |
} | |
byte[] result = new byte[0]; | |
try | |
{ | |
var nvc = rp.ToNameValueCollection(); | |
#if DESKTOP_GL | |
result = client.UploadValues(gaUrl, "post", nvc); | |
#endif | |
} | |
catch | |
{ | |
// TODO: catch failures and either batch or log or somehow handle failed calls | |
// this assignment is just something to put a breakpoint on :P | |
int m = 4; | |
} | |
return result; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment