diff --git a/analytics/src/FirebaseAnalytics.cs b/analytics/src/FirebaseAnalytics.cs index d3c600db9..e61bf24a6 100644 --- a/analytics/src/FirebaseAnalytics.cs +++ b/analytics/src/FirebaseAnalytics.cs @@ -15,11 +15,14 @@ */ using System.Threading.Tasks; +using Firebase.Platform; namespace Firebase.Analytics { public static partial class FirebaseAnalytics { + private static readonly Firebase.Platform.ModuleLogger logger = new Firebase.Platform.ModuleLogger("FirebaseAnalytics"); + /// Get the instance ID from the analytics service. /// /// @returns A Task with the Analytics instance ID. @@ -248,6 +251,61 @@ public static void SetConsent(System.Collections.Generic.IDictionary< ConsentTyp FirebaseAnalyticsInternal.SetConsentWithInts(consentSettingsMap); } + /// @brief Sets default event parameters. + /// + /// These parameters are logged with all events, in addition to the parameters passed to the + /// LogEvent() call. They are useful for logging common parameters with all events. + /// Default event parameters are overridden by event-specific parameters if the names are the + /// same. Default parameters are removed if the dictionary is null or empty. + /// + /// @param[in] parameters The dictionary of parameters to set. + /// If null, clears all default parameters. + public static void SetDefaultEventParameters( + System.Collections.Generic.IDictionary parameters) { + if (parameters == null) { + logger.LogMessage(LogLevel.Debug, "Input parameters dictionary is null. Clearing all default event parameters."); + FirebaseAnalyticsInternal.ClearDefaultEventParameters(); + } else { + StringList parameterNames = new StringList(); + VariantList parameterValues = new VariantList(); + + if (parameters.Count == 0) { + logger.LogMessage(LogLevel.Debug, "Input parameters dictionary is empty. Clearing all default event parameters via empty map."); + // Proceed with empty lists, C++ SDK will clear defaults when given an empty map. + } else { + logger.LogMessage(LogLevel.Debug, string.Format("Processing {0} default event parameter(s).", parameters.Count)); + foreach (var kvp in parameters) { + try { + Firebase.Variant variantValue = Firebase.Variant.FromObject(kvp.Value); + // Assuming Variant.FromObject(null) correctly yields a Variant.Null() + // and doesn't throw for it, or that Variant.Null() is a valid state. + // If Variant.FromObject might return an "invalid" Variant object for certain inputs + // instead of throwing (e.g. for unsupported types that don't cause exceptions), + // an additional check like `if (!variantValue.IsValid())` might be needed here. + // For now, relying on try-catch for conversion failures. + + parameterNames.Add(kvp.Key); + parameterValues.Add(variantValue); + } catch (System.Exception e) { + logger.LogMessage(LogLevel.Warning, string.Format( + "Failed to convert default parameter '{0}' (value type: {1}). Skipping. Error: {2}", + kvp.Key, kvp.Value?.GetType().FullName ?? "null", e.Message)); + } + } + } + + // If parameters.Count was > 0 but all failed conversion, parameterNames will be empty. + // Sending empty lists to C++ SetDefaultEventParameters (with map) results in clearing. + if (parameters.Count > 0 && parameterNames.Count == 0) { + logger.LogMessage(LogLevel.Warning, "All supplied default parameters failed conversion. This will result in clearing all default parameters."); + } else if (parameterNames.Count > 0) { + logger.LogMessage(LogLevel.Debug, string.Format("Setting {0} default event parameter(s) via helper.", parameterNames.Count)); + } + // Always call SetDefaultEventParametersHelper. If lists are empty, it clears params. + FirebaseAnalyticsInternal.SetDefaultEventParametersHelper(parameterNames, parameterValues); + } + } + /// @brief Sets the duration of inactivity that terminates the current session. /// /// @note The default value is 30 minutes. diff --git a/analytics/src/swig/analytics.i b/analytics/src/swig/analytics.i index 866f6d4fb..98579d4a5 100644 --- a/analytics/src/swig/analytics.i +++ b/analytics/src/swig/analytics.i @@ -87,6 +87,24 @@ void SetConsentWithInts(const std::map& settings) { SetConsent(converted); } +// Helper function to set default event parameters from vectors of strings and variants. +void SetDefaultEventParametersHelper( + const std::vector& parameter_names, + const std::vector& parameter_values) { + if (parameter_names.size() != parameter_values.size()) { + firebase::LogError( + "SetDefaultEventParametersHelper given different list sizes (%d, %d)", + parameter_names.size(), parameter_values.size()); + return; + } + + std::map default_parameters; + for (size_t i = 0; i < parameter_names.size(); ++i) { + default_parameters[parameter_names[i]] = parameter_values[i]; + } + SetDefaultEventParameters(default_parameters); +} + } // namespace analytics } // namespace firebase @@ -100,6 +118,8 @@ void SetConsentWithInts(const std::map& settings) { %ignore firebase::analytics::LogEvent(const char*, const Parameter*, size_t); // Ignore SetConsent, in order to convert the types with our own function. %ignore firebase::analytics::SetConsent; +// Ignore SetDefaultEventParameters, as we handle it with a custom helper. +%ignore firebase::analytics::SetDefaultEventParameters; // Ignore the Parameter class, as we don't want to expose that to C# at all. %ignore firebase::analytics::Parameter; @@ -121,5 +141,9 @@ namespace analytics { void LogEvent(const char* name, std::vector parameter_names, std::vector parameter_values); void SetConsentWithInts(const std::map& settings); +void SetDefaultEventParametersHelper( + const std::vector& parameter_names, + const std::vector& parameter_values); +void ClearDefaultEventParameters(); } // namespace analytics } // namespace firebase diff --git a/analytics/testapp/Assets/Firebase/Sample/Analytics/UIHandler.cs b/analytics/testapp/Assets/Firebase/Sample/Analytics/UIHandler.cs index 08564b79d..8e9198356 100644 --- a/analytics/testapp/Assets/Firebase/Sample/Analytics/UIHandler.cs +++ b/analytics/testapp/Assets/Firebase/Sample/Analytics/UIHandler.cs @@ -162,6 +162,32 @@ public void AnalyticsSetConsent() { }); } + public void TestSetDefaultEventParameters() { + DebugLog("Starting TestSetDefaultEventParameters..."); + + DebugLog("Setting single default string parameter: {'default_param_string', 'default_value_1'}"); + FirebaseAnalytics.SetDefaultEventParameters(new Dictionary + { + { "default_param_string", "default_value_1" } + }); + + DebugLog("Setting multiple default parameters: {'default_param_int', 123, 'default_param_double', 45.67, 'default_param_bool', true}"); + FirebaseAnalytics.SetDefaultEventParameters(new Dictionary + { + { "default_param_int", 123 }, + { "default_param_double", 45.67 }, + { "default_param_bool", true } + }); + + DebugLog("Clearing default parameters with null."); + FirebaseAnalytics.SetDefaultEventParameters(null); + + DebugLog("Clearing default parameters with empty dictionary."); + FirebaseAnalytics.SetDefaultEventParameters(new Dictionary()); + + DebugLog("TestSetDefaultEventParameters completed."); + } + // Get the current app instance ID. public Task DisplayAnalyticsInstanceId() { return FirebaseAnalytics.GetAnalyticsInstanceIdAsync().ContinueWithOnMainThread(task => { @@ -257,6 +283,9 @@ void GUIDisplayControls() { if (GUILayout.Button("Test SetConsent")) { AnalyticsSetConsent(); } + if (GUILayout.Button("Test SetDefaultEventParameters")) { + TestSetDefaultEventParameters(); + } GUILayout.EndVertical(); GUILayout.EndScrollView(); } diff --git a/analytics/testapp/Assets/Firebase/Sample/Analytics/UIHandlerAutomated.cs b/analytics/testapp/Assets/Firebase/Sample/Analytics/UIHandlerAutomated.cs index e101771ab..a32975844 100644 --- a/analytics/testapp/Assets/Firebase/Sample/Analytics/UIHandlerAutomated.cs +++ b/analytics/testapp/Assets/Firebase/Sample/Analytics/UIHandlerAutomated.cs @@ -40,6 +40,7 @@ public override void Start() { TestAnalyticsSetConsentDoesNotThrow, TestInstanceIdChangeAfterReset, TestResetAnalyticsData, + TestSetDefaultEventParametersAutomated, // Renamed test here // Temporarily disabled until this test is deflaked. b/143603151 //TestCheckAndFixDependenciesInvalidOperation, TestCheckAndFixDependenciesDoubleCall, @@ -108,6 +109,40 @@ Task TestAnalyticsSetConsentDoesNotThrow() { return true; }); } + + Task TestSetDefaultEventParametersAutomated() { + DebugLog("Automated Test: Starting TestSetDefaultEventParametersAutomated..."); + + DebugLog("Automated Test: Setting single default string parameter: {'auto_default_param_string', 'auto_value_1'}"); + FirebaseAnalytics.SetDefaultEventParameters(new Dictionary + { + { "auto_default_param_string", "auto_value_1" } + }); + + DebugLog("Automated Test: Setting multiple default parameters: {'auto_default_param_int', 789, 'auto_default_param_double', 12.34, 'auto_default_param_bool', false}"); + FirebaseAnalytics.SetDefaultEventParameters(new Dictionary + { + { "auto_default_param_int", 789 }, + { "auto_default_param_double", 12.34 }, + { "auto_default_param_bool", false } + }); + + DebugLog("Automated Test: Setting a default parameter with a null value: {'param_with_null_value', null}"); + FirebaseAnalytics.SetDefaultEventParameters(new Dictionary + { + { "param_with_null_value", null } + }); + + DebugLog("Automated Test: Clearing default parameters with null."); + FirebaseAnalytics.SetDefaultEventParameters(null); + + DebugLog("Automated Test: Clearing default parameters with empty dictionary."); + FirebaseAnalytics.SetDefaultEventParameters(new Dictionary()); + + DebugLog("Automated Test: TestSetDefaultEventParametersAutomated completed."); + return Task.FromResult(true); // Indicate successful completion + } + Task TestCheckAndFixDependenciesInvalidOperation() { // Only run the test on Android, as CheckAndFixDependenciesAsync is short // lived on other platforms, and thus could finish before the extra call.