Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FR] Able to drop analytics events before CheckAndFixDependenciesAsync() completes #853

Open
AntonPetrov83 opened this issue Nov 5, 2020 · 6 comments

Comments

@AntonPetrov83
Copy link

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?

@google-oss-bot
Copy link

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.

@AntonPetrov83 AntonPetrov83 changed the title How not to drop analytics events before CheckAndFixDependenciesAsync() completion? How not to drop analytics events before CheckAndFixDependenciesAsync() completes? Nov 5, 2020
@cynthiajoan cynthiajoan added api: analytics needs-info Need information for the developer and removed needs-triage labels Nov 10, 2020
@cynthiajoan
Copy link
Contributor

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.

@AntonPetrov83
Copy link
Author

AntonPetrov83 commented Nov 12, 2020

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.

@google-oss-bot google-oss-bot added needs-attention Need Googler's attention and removed needs-info Need information for the developer labels Nov 12, 2020
@chkuang-g
Copy link
Contributor

chkuang-g commented Nov 29, 2020

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

@chkuang-g chkuang-g changed the title How not to drop analytics events before CheckAndFixDependenciesAsync() completes? [FR] Able to drop analytics events before CheckAndFixDependenciesAsync() completes Nov 29, 2020
@chkuang-g chkuang-g added type: feature request and removed needs-attention Need Googler's attention labels Nov 29, 2020
@k-turek
Copy link

k-turek commented Jan 8, 2021

@uchar
Copy link

uchar commented Feb 16, 2021

@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(...);

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

6 participants