quickstart-unity
quickstart-unity copied to clipboard
[FR] Able to drop analytics events before CheckAndFixDependenciesAsync() completes
The official guide recommends to set a flag when CheckAndFixDependenciesAsync
completes to indicate that Firebase is ready.
Firebase.FirebaseApp.CheckAndFixDependenciesAsync().ContinueWith(task => {
var dependencyStatus = task.Result;
if (dependencyStatus == Firebase.DependencyStatus.Available) {
// Create and hold a reference to your FirebaseApp,
// where app is a Firebase.FirebaseApp property of your application class.
app = Firebase.FirebaseApp.DefaultInstance;
// Set a flag here to indicate whether Firebase is ready to use by your app.
} else {
UnityEngine.Debug.LogError(System.String.Format(
"Could not resolve all Firebase dependencies: {0}", dependencyStatus));
// Firebase Unity SDK is not safe to use here.
}
});
Otherwise, an exception gets thrown:
InvalidOperationException: Don't call Firebase functions before CheckDependencies has finished
So how am I supposed to log early events? What are the best practices?
I found a few problems with this issue:
- I couldn't figure out how to label this issue, so I've labeled it for a human to triage. Hang tight.
- This issue does not seem to follow the issue template. Make sure you provide all the required information.
Hi @AntonPetrov83, any events happens before Firebase Initialization can't be logged properly by firebase. That should be one of the earliest init of the app. I'd like to get more context of what you might want to log earlier.
Hi @AntonPetrov83, any events happens before Firebase Initialization can't be logged properly by firebase. That should be one of the earliest init of the app. I'd like to get more context of what you might want to log earlier.
Hi @cynthiajoan , I would like to log very early events like "loading_start" to build funnels later. In my app there is an umbrella class used to log events to different systems through a single API.
The problem with Firebase SDK is that it requires async initialization so this only SDK blocks the entire logging system from early start. As a workaround I implemented a queue to collect calls into Firebase until it is initialized. And this is ugly.
I think such design shifts a lot of complexity from the Firebase SDK to the client side. Events logging must work through a simple static API and managing initialization and connection issues must be hidden on your side.
With all my respect to your hard work.
Hi @AntonPetrov83
Unfortunately this is how the API is designed now. CheckAndFixDependenciesAsync()
is primarily used for Android build to make sure all required deps, primarily Google Play Services, is installed and met minimum versions for all Firebase components used in your app.
I understand your concern. This seems to be a feature request for a design change (quite major one actually). Please allow me to change the label and title a bit.
Shawn
@AntonPetrov83 you inspired me to write this short article ;) https://kturek.medium.com/how-to-deal-with-dont-call-firebase-functions-before-checkdependencies-has-finished-in-unity-7a9c8adba754
@chkuang-g Well, it's really a bad design.
I create a script for handling this stuffs ( It use both Firebase and Facebook) maybe it helps someone in the future
//AnalyticsParam.cs
public class AnalyticsParam
{
private string _stringValue;
private double _doubleValue;
private long _longValue;
private int _intValue;
public AnalyticsParam(string name, string value)
{
Name = name;
StringValue = value;
}
public AnalyticsParam(string name, double value)
{
Name = name;
DoubleValue = value;
}
public AnalyticsParam(string name, long value)
{
Name = name;
LongValue = value;
}
public AnalyticsParam(string name, int value)
{
Name = name;
IntValue = value;
}
public string Name { get; set; }
public string StringValue
{
get => _stringValue;
set
{
_stringValue = value;
Type = 0;
}
}
public double DoubleValue
{
get => _doubleValue;
set
{
Type = 1;
_doubleValue = value;
}
}
public long LongValue
{
get => _longValue;
set
{
Type = 2;
_longValue = value;
}
}
public int IntValue
{
get => _intValue;
set
{
Type = 3;
_intValue = value;
}
}
public int Type { get; set; } = 0;
}
//AnalyticsUtils.cs
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Facebook.Unity;
using Firebase;
using Firebase.Analytics;
using Game_Assets.Scripts.Database;
using Game_Assets.Scripts.Database.Constants;
using UnityEngine;
using UnityEngine.Serialization;
using UnityEngine.UI;
namespace Game_Assets.Scripts.Analytics
{
public class AnalyticsUtils : MonoBehaviour
{
private static AnalyticsUtils _instance;
private bool _isFirebaseReadyToUse = false;
private bool _isFaceBookReadyToUse = false;
private readonly List<KeyValuePair<string, Parameter[]>> _firebaseQueue =
new List<KeyValuePair<string, Parameter[]>>();
private readonly List<KeyValuePair<string, Dictionary<string, object>>> _facebookQueue =
new List<KeyValuePair<string, Dictionary<string, object>>>();
private void DebugLog(string text)
{
Debug.Log(text);
}
private void Start()
{
_instance = this;
Initialize();
DontDestroyOnLoad(gameObject);
}
public static AnalyticsUtils GetInstance()
{
return _instance;
}
private void InitializeFirebase()
{
try
{
DebugLog("Start initializing firebase");
FirebaseAnalytics.SetAnalyticsCollectionEnabled(true);
SetUserId();
_isFirebaseReadyToUse = true;
DebugLog("Firebase Ready !");
}
catch (Exception e)
{
DebugLog("Exception happened intilizing : " + e.ToString());
throw;
}
}
private void Initialize()
{
if (Utility.IsAndroid())
FirebaseApp.CheckAndFixDependenciesAsync().ContinueWith(task =>
{
var dependencyStatus = task.Result;
if (dependencyStatus == DependencyStatus.Available)
InitializeFirebase();
else
DebugLog($"Could not resolve all Firebase dependencies: {dependencyStatus}");
});
else InitializeFirebase();
if (!FB.IsInitialized)
{
// Initialize the Facebook SDK
FB.Init(() =>
{
if (FB.IsInitialized)
{
FB.ActivateApp();
_isFaceBookReadyToUse = true;
DebugLog("Facebook ready !");
}
else
{
DebugLog("Failed to Initialize the Facebook SDK");
}
}, (bool _) => { });
}
else
{
DebugLog(" Already initialized, signal an app activation App Event !");
// Already initialized, signal an app activation App Event
FB.ActivateApp();
_isFaceBookReadyToUse = true;
}
}
private void AddToFirebaseQueue(string name, Parameter[] parameter)
{
_firebaseQueue.Add(new KeyValuePair<string, Parameter[]>(name, parameter));
}
private void AddToFirebaseQueue(string name, string parameterName, string parameterValue)
{
AddToFirebaseQueue(name, new Parameter[] {new Parameter(parameterName, parameterValue)});
}
private void AddToFirebaseQueue(string name, string parameterName, double parameterValue)
{
AddToFirebaseQueue(name, new Parameter[] {new Parameter(parameterName, parameterValue)});
}
private void AddToFirebaseQueue(string name, string parameterName, long parameterValue)
{
AddToFirebaseQueue(name, new Parameter[] {new Parameter(parameterName, parameterValue)});
}
private void AddToFirebaseQueue(string name, string parameterName, int parameterValue)
{
AddToFirebaseQueue(name, new Parameter[] {new Parameter(parameterName, parameterValue)});
}
private void AddToFacebookQueue(string name, string parameterName, object parameterValue)
{
var fbDict = new Dictionary<string, object> {[parameterName] = parameterValue};
AddToFacebookQueue(name, fbDict);
}
private void AddToFacebookQueue(string name, Dictionary<string, object> fbParams)
{
_facebookQueue.Add(new KeyValuePair<string, Dictionary<string, object>>(name, fbParams));
}
private void SendAllQueueEvents()
{
while (_facebookQueue.Count > 0)
{
var firstEvent = _facebookQueue.First();
FB.LogAppEvent(
firstEvent.Key,
parameters: firstEvent.Value
);
_facebookQueue.RemoveAt(0);
}
while (_firebaseQueue.Count > 0)
{
var firstEvent = _firebaseQueue.First();
FirebaseAnalytics
.LogEvent(firstEvent.Key, firstEvent.Value);
_firebaseQueue.RemoveAt(0);
}
}
private void SendFbEvent(string name, string parameterName, object parameterValue)
{
if (_isFaceBookReadyToUse)
{
SendAllQueueEvents();
var fbParams = new Dictionary<string, object> {[parameterName] = parameterValue};
FB.LogAppEvent(
name,
parameters: fbParams
);
}
else
{
AddToFacebookQueue(name, parameterName, parameterValue);
}
}
private void SendFirebaseEvent(string name, Parameter param)
{
if (_isFirebaseReadyToUse)
{
SendAllQueueEvents();
FirebaseAnalytics
.LogEvent(name, param);
}
else
{
AddToFirebaseQueue(name, new Parameter[] {param});
}
}
public void SendEvent(string name, string parameterName, double parameterValue)
{
SendFirebaseEvent(name, new Parameter(parameterName, parameterValue));
SendFbEvent(name, parameterName, parameterValue);
}
public void SendEvent(string name, string parameterName, long parameterValue)
{
SendFirebaseEvent(name, new Parameter(parameterName, parameterValue));
SendFbEvent(name, parameterName, parameterValue);
}
public void SendEvent(string name, string parameterName, int parameterValue)
{
SendFirebaseEvent(name, new Parameter(parameterName, parameterValue));
SendFbEvent(name, parameterName, parameterValue);
}
public void SendEvent(string name, string parameterName, string parameterValue)
{
SendFirebaseEvent(name, new Parameter(parameterName, parameterValue));
SendFbEvent(name, parameterName, parameterValue);
}
public void SendEvent(string name, List<AnalyticsParam> analyticsParams)
{
var fbParams = new Dictionary<string, object>();
var firebaseParams = new Parameter[analyticsParams.Count];
var index = 0;
foreach (var analyticsParam in analyticsParams)
{
switch (analyticsParam.Type)
{
case 0:
firebaseParams[index] = new Parameter(
analyticsParam.Name, analyticsParam.StringValue);
fbParams[analyticsParam.Name] = analyticsParam.StringValue;
break;
case 1:
firebaseParams[index] = new Parameter(
analyticsParam.Name, analyticsParam.DoubleValue);
fbParams[analyticsParam.Name] = analyticsParam.DoubleValue;
break;
case 2:
firebaseParams[index] = new Parameter(
analyticsParam.Name, analyticsParam.LongValue);
fbParams[analyticsParam.Name] = analyticsParam.LongValue;
break;
case 3:
firebaseParams[index] = new Parameter(
analyticsParam.Name, analyticsParam.IntValue);
fbParams[analyticsParam.Name] = analyticsParam.IntValue;
break;
}
++index;
}
if (_isFirebaseReadyToUse)
{
SendAllQueueEvents();
FirebaseAnalytics.LogEvent(name, firebaseParams);
}
else
{
AddToFirebaseQueue(name, firebaseParams);
}
if (_isFaceBookReadyToUse)
{
SendAllQueueEvents();
FB.LogAppEvent(
name,
parameters: fbParams
);
}
else
{
AddToFacebookQueue(name, fbParams);
}
}
public void SetUserProperty(string propertyName, int value)
{
SetUserProperty(propertyName,"" + value);
}
public void SetUserProperty(string propertyName, string value)
{
StartCoroutine(SetUserPropertyCoRoutine(propertyName, "" + value));
}
public void EarnCoin(int coin)
{
SendEvent(FirebaseAnalytics.EventEarnVirtualCurrency, "coin", coin);
}
public void SpendCoin(int coin)
{
SendEvent(FirebaseAnalytics.EventSpendVirtualCurrency, "coin", coin);
}
private IEnumerator SetUserPropertyCoRoutine(string propertyName, string value)
{
while (!_isFirebaseReadyToUse)
{
yield return new WaitForSeconds(1f);
}
FirebaseAnalytics.SetUserProperty(name, value);
}
private void SetUserId()
{
FirebaseAnalytics.SetUserId(
SystemInfo.deviceUniqueIdentifier);
}
public void SetCurrentScreen(string screenName, string className)
{
StartCoroutine(SetCurrentScreenCoRoutine(screenName, className));
}
private IEnumerator SetCurrentScreenCoRoutine(string screenName, string className)
{
while (!_isFirebaseReadyToUse)
{
yield return new WaitForSeconds(1f);
}
FirebaseAnalytics.SetCurrentScreen(screenName, className);
}
}
}
Then add it to an object in your first scene. And use it with :
AnalyticsUtils.GetInstance().SendEvent(...);