Skip to content

Commit faf31e8

Browse files
Add SetDefaultEventParameters and ClearDefaultEventParameters to Analytics
This change introduces two new C++ Analytics SDK functions: - `SetDefaultEventParameters(const std::map<std::string, Variant>& params)`: Allows setting default parameters that will be included in all subsequent `LogEvent` calls. If a `Variant::Null()` is provided as a value for a key, that specific default parameter will be cleared/removed. - `ClearDefaultEventParameters()`: Clears all currently set default event parameters. Platform implementations: - iOS: Uses `[FIRAnalytics setDefaultEventParameters:]`. `Variant::Null()` maps to `[NSNull null]` for clearing individual parameters. Calling with `nil` clears all. - Android: Uses `FirebaseAnalytics.setDefaultEventParameters(Bundle)`. `Variant::Null()` results in `Bundle.putString(key, null)`. Calling with a `null` Bundle clears all. - Stub: Implemented as no-ops. Unit tests and integration tests have been updated to cover these new functionalities, including the null-value handling for clearing specific parameters and the overall clearing of all parameters.
1 parent a06d206 commit faf31e8

File tree

9 files changed

+314
-0
lines changed

9 files changed

+314
-0
lines changed

analytics/integration_test/src/integration_test.cc

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -341,4 +341,61 @@ TEST_F(FirebaseAnalyticsTest, TestSetConsent) {
341341
did_test_setconsent_ = true;
342342
}
343343

344+
TEST_F(FirebaseAnalyticsTest, TestDefaultEventParametersUsage) {
345+
LogInfo("Testing SetDefaultEventParameters with initial values, then updating with Null.");
346+
347+
std::map<std::string, firebase::analytics::Variant> initial_defaults;
348+
initial_defaults["initial_key"] = "initial_value";
349+
initial_defaults["key_to_be_nulled"] = "text_before_null";
350+
initial_defaults["numeric_default"] = 12345LL;
351+
352+
LogInfo("Setting initial default event parameters.");
353+
firebase::analytics::SetDefaultEventParameters(initial_defaults);
354+
// Log an event that would pick up these defaults.
355+
firebase::analytics::LogEvent("event_with_initial_defaults");
356+
LogInfo("Logged event_with_initial_defaults.");
357+
ProcessEvents(500); // Short pause for event logging, if it matters for backend.
358+
359+
std::map<std::string, firebase::analytics::Variant> updated_defaults;
360+
updated_defaults["key_to_be_nulled"] = firebase::analytics::Variant::Null();
361+
updated_defaults["another_key"] = "another_value";
362+
// "initial_key" should persist if not overwritten.
363+
// "numeric_default" should persist.
364+
365+
LogInfo("Updating default event parameters, setting key_to_be_nulled to Null.");
366+
firebase::analytics::SetDefaultEventParameters(updated_defaults);
367+
// Log an event that would pick up the updated defaults.
368+
firebase::analytics::LogEvent("event_after_nulling_and_adding");
369+
LogInfo("Logged event_after_nulling_and_adding.");
370+
ProcessEvents(500);
371+
372+
// For this C++ SDK integration test, we primarily ensure API calls complete.
373+
// Actual parameter presence on logged events would be verified by backend/native tests.
374+
LogInfo("TestDefaultEventParametersUsage completed. Calls were made successfully.");
375+
}
376+
377+
TEST_F(FirebaseAnalyticsTest, TestClearDefaultEventParametersFunctionality) {
378+
LogInfo("Testing ClearDefaultEventParameters.");
379+
380+
std::map<std::string, firebase::analytics::Variant> defaults_to_clear;
381+
defaults_to_clear["default_one"] = "will_be_cleared";
382+
defaults_to_clear["default_two"] = 9876LL;
383+
384+
LogInfo("Setting default parameters before clearing.");
385+
firebase::analytics::SetDefaultEventParameters(defaults_to_clear);
386+
// Log an event that would pick up these defaults.
387+
firebase::analytics::LogEvent("event_before_global_clear");
388+
LogInfo("Logged event_before_global_clear.");
389+
ProcessEvents(500);
390+
391+
LogInfo("Calling ClearDefaultEventParameters.");
392+
firebase::analytics::ClearDefaultEventParameters();
393+
// Log an event that should not have the previous defaults.
394+
firebase::analytics::LogEvent("event_after_global_clear");
395+
LogInfo("Logged event_after_global_clear.");
396+
ProcessEvents(500);
397+
398+
LogInfo("TestClearDefaultEventParametersFunctionality completed. Call was made successfully.");
399+
}
400+
344401
} // namespace firebase_testapp_automated

analytics/src/analytics_android.cc

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@ static const ::firebase::App* g_app = nullptr;
5858
"()Lcom/google/android/gms/tasks/Task;"), \
5959
X(GetSessionId, "getSessionId", \
6060
"()Lcom/google/android/gms/tasks/Task;"), \
61+
X(SetDefaultEventParameters, "setDefaultEventParameters", \
62+
"(Landroid/os/Bundle;)V", util::kMethodTypeInstance), \
6163
X(GetInstance, "getInstance", "(Landroid/content/Context;)" \
6264
"Lcom/google/firebase/analytics/FirebaseAnalytics;", \
6365
firebase::util::kMethodTypeStatic)
@@ -609,6 +611,72 @@ void ResetAnalyticsData() {
609611
util::CheckAndClearJniExceptions(env);
610612
}
611613

614+
void SetDefaultEventParameters(
615+
const std::map<std::string, Variant>& default_parameters) {
616+
FIREBASE_ASSERT_RETURN_VOID(internal::IsInitialized());
617+
JNIEnv* env = g_app->GetJNIEnv();
618+
if (!env) return;
619+
620+
jobject bundle =
621+
env->NewObject(util::bundle::GetClass(),
622+
util::bundle::GetMethodId(util::bundle::kConstructor));
623+
if (util::CheckAndClearJniExceptions(env) || !bundle) {
624+
LogError("Failed to create Bundle for SetDefaultEventParameters.");
625+
if (bundle) env->DeleteLocalRef(bundle);
626+
return;
627+
}
628+
629+
for (const auto& pair : default_parameters) {
630+
jstring key_jstring = env->NewStringUTF(pair.first.c_str());
631+
if (util::CheckAndClearJniExceptions(env) || !key_jstring) {
632+
LogError("Failed to create jstring for key: %s", pair.first.c_str());
633+
if (key_jstring) env->DeleteLocalRef(key_jstring);
634+
continue;
635+
}
636+
637+
if (pair.second.is_null()) {
638+
// Equivalent to Bundle.putString(String key, String value) with value as null.
639+
env->CallVoidMethod(bundle, util::bundle::GetMethodId(util::bundle::kPutString),
640+
key_jstring, nullptr);
641+
if (util::CheckAndClearJniExceptions(env)) {
642+
LogError("Failed to put null string for key: %s", pair.first.c_str());
643+
}
644+
} else {
645+
if (!AddVariantToBundle(env, bundle, pair.first.c_str(), pair.second)) {
646+
// AddVariantToBundle already logs errors for unsupported types.
647+
}
648+
}
649+
env->DeleteLocalRef(key_jstring);
650+
}
651+
652+
env->CallVoidMethod(g_analytics_class_instance,
653+
analytics::GetMethodId(analytics::kSetDefaultEventParameters),
654+
bundle);
655+
if (util::CheckAndClearJniExceptions(env)) {
656+
LogError("Failed to call setDefaultEventParameters on Java instance.");
657+
}
658+
659+
env->DeleteLocalRef(bundle);
660+
}
661+
662+
void ClearDefaultEventParameters() {
663+
FIREBASE_ASSERT_RETURN_VOID(internal::IsInitialized());
664+
JNIEnv* env = g_app->GetJNIEnv();
665+
if (!env) return;
666+
667+
// Calling with nullptr bundle should clear the parameters.
668+
env->CallVoidMethod(g_analytics_class_instance,
669+
analytics::GetMethodId(analytics::kSetDefaultEventParameters),
670+
nullptr);
671+
if (util::CheckAndClearJniExceptions(env)) {
672+
// This might happen if the method isn't available on older SDKs,
673+
// or if some other JNI error occurs.
674+
LogError("Failed to call setDefaultEventParameters(null) on Java instance. "
675+
"This may indicate the method is not available on this Android SDK version "
676+
"or another JNI error occurred.");
677+
}
678+
}
679+
612680
Future<std::string> GetAnalyticsInstanceId() {
613681
FIREBASE_ASSERT_RETURN(GetAnalyticsInstanceIdLastResult(),
614682
internal::IsInitialized());

analytics/src/analytics_ios.mm

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -373,6 +373,42 @@ void SetSessionTimeoutDuration(int64_t milliseconds) {
373373
setSessionTimeoutInterval:static_cast<NSTimeInterval>(milliseconds) / kMillisecondsPerSecond];
374374
}
375375

376+
void SetDefaultEventParameters(
377+
const std::map<std::string, Variant>& default_parameters) {
378+
FIREBASE_ASSERT_RETURN_VOID(internal::IsInitialized());
379+
NSMutableDictionary* ns_default_parameters =
380+
[[NSMutableDictionary alloc] initWithCapacity:default_parameters.size()];
381+
for (const auto& pair : default_parameters) {
382+
NSString* key = SafeString(pair.first.c_str());
383+
const Variant& value = pair.second;
384+
385+
if (value.is_null()) {
386+
[ns_default_parameters setObject:[NSNull null] forKey:key];
387+
} else if (value.is_int64()) {
388+
[ns_default_parameters setObject:[NSNumber numberWithLongLong:value.int64_value()] forKey:key];
389+
} else if (value.is_double()) {
390+
[ns_default_parameters setObject:[NSNumber numberWithDouble:value.double_value()] forKey:key];
391+
} else if (value.is_string()) {
392+
[ns_default_parameters setObject:SafeString(value.string_value()) forKey:key];
393+
} else if (value.is_bool()) {
394+
[ns_default_parameters setObject:[NSNumber numberWithBool:value.bool_value()] forKey:key];
395+
} else {
396+
// Log an error for unsupported types.
397+
// Note: FIRAnalytics.setDefaultEventParameters only supports NSNumber, NSString, NSNull.
398+
// It does not support nested collections (NSArray, NSDictionary) unlike LogEvent.
399+
LogError("SetDefaultEventParameters: Unsupported Variant type (%s) for key %s. "
400+
"Only Int64, Double, String, Bool, and Null are supported for default event parameters.",
401+
Variant::TypeName(value.type()), pair.first.c_str());
402+
}
403+
}
404+
[FIRAnalytics setDefaultEventParameters:ns_default_parameters];
405+
}
406+
407+
void ClearDefaultEventParameters() {
408+
FIREBASE_ASSERT_RETURN_VOID(internal::IsInitialized());
409+
[FIRAnalytics setDefaultEventParameters:nil];
410+
}
411+
376412
void ResetAnalyticsData() {
377413
MutexLock lock(g_mutex);
378414
FIREBASE_ASSERT_RETURN_VOID(internal::IsInitialized());

analytics/src/analytics_stub.cc

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,17 @@ void SetSessionTimeoutDuration(int64_t /*milliseconds*/) {
142142
FIREBASE_ASSERT_RETURN_VOID(internal::IsInitialized());
143143
}
144144

145+
void SetDefaultEventParameters(
146+
const std::map<std::string, Variant>& /*default_parameters*/) {
147+
FIREBASE_ASSERT_RETURN_VOID(internal::IsInitialized());
148+
// This is a stub implementation. No operation needed.
149+
}
150+
151+
void ClearDefaultEventParameters() {
152+
FIREBASE_ASSERT_RETURN_VOID(internal::IsInitialized());
153+
// This is a stub implementation. No operation needed.
154+
}
155+
145156
void ResetAnalyticsData() {
146157
FIREBASE_ASSERT_RETURN_VOID(internal::IsInitialized());
147158
g_fake_instance_id++;

analytics/src/include/firebase/analytics.h

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -558,6 +558,23 @@ void SetSessionTimeoutDuration(int64_t milliseconds);
558558
/// instance id.
559559
void ResetAnalyticsData();
560560

561+
/// @brief Sets the default event parameters.
562+
///
563+
/// These parameters will be automatically logged with all calls to `LogEvent`.
564+
/// Default parameters are overridden by parameters supplied to the `LogEvent`
565+
/// method.
566+
///
567+
/// When a value in the `default_parameters` map is
568+
/// `firebase::Variant::Null()`, it signifies that the default parameter for
569+
/// that specific key should be cleared.
570+
///
571+
/// @param[in] default_parameters A map of parameter names to Variant values.
572+
void SetDefaultEventParameters(
573+
const std::map<std::string, Variant>& default_parameters);
574+
575+
/// @brief Clears all default event parameters.
576+
void ClearDefaultEventParameters();
577+
561578
/// Get the instance ID from the analytics service.
562579
///
563580
/// @note This is *not* the same ID as the ID returned by

analytics/src_ios/fake/FIRAnalytics.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,4 +37,6 @@
3737

3838
+ (void)resetAnalyticsData;
3939

40+
+ (void)setDefaultEventParameters:(nullable NSDictionary<NSString *, id> *)parameters;
41+
4042
@end

analytics/src_ios/fake/FIRAnalytics.mm

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@
2121
@implementation FIRAnalytics
2222

2323
+ (NSString *)stringForValue:(id)value {
24+
if (value == [NSNull null]) {
25+
return @"<NSNull>";
26+
}
2427
return [NSString stringWithFormat:@"%@", value];
2528
}
2629

@@ -94,4 +97,14 @@ + (void)resetAnalyticsData {
9497
FakeReporter->AddReport("+[FIRAnalytics resetAnalyticsData]", {});
9598
}
9699

100+
+ (void)setDefaultEventParameters:(nullable NSDictionary<NSString *, id> *)parameters {
101+
if (parameters == nil) {
102+
FakeReporter->AddReport("+[FIRAnalytics setDefaultEventParameters:]", {"nil"});
103+
} else {
104+
NSString *parameterString = [self stringForParameters:parameters];
105+
FakeReporter->AddReport("+[FIRAnalytics setDefaultEventParameters:]",
106+
{ [parameterString UTF8String] });
107+
}
108+
}
109+
97110
@end

analytics/src_java/fake/com/google/firebase/analytics/FirebaseAnalytics.java

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,4 +79,30 @@ public void setSessionTimeoutDuration(long milliseconds) {
7979
FakeReporter.addReport(
8080
"FirebaseAnalytics.setSessionTimeoutDuration", Long.toString(milliseconds));
8181
}
82+
83+
public void setDefaultEventParameters(Bundle bundle) {
84+
if (bundle == null) {
85+
FakeReporter.addReport("FirebaseAnalytics.setDefaultEventParameters", "null");
86+
} else {
87+
StringBuilder paramsString = new StringBuilder();
88+
// Sort keys for predictable ordering.
89+
for (String key : new TreeSet<>(bundle.keySet())) {
90+
paramsString.append(key);
91+
paramsString.append("=");
92+
Object value = bundle.get(key); // Get as Object first
93+
if (value == null) {
94+
// This case handles when bundle.putString(key, null) was called.
95+
paramsString.append("<null_string_value>");
96+
} else {
97+
paramsString.append(value.toString());
98+
}
99+
paramsString.append(",");
100+
}
101+
// Remove trailing comma if paramsString is not empty
102+
if (paramsString.length() > 0) {
103+
paramsString.setLength(paramsString.length() - 1);
104+
}
105+
FakeReporter.addReport("FirebaseAnalytics.setDefaultEventParameters", paramsString.toString());
106+
}
107+
}
82108
}

analytics/tests/analytics_test.cc

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -296,5 +296,89 @@ TEST_F(AnalyticsTest, TestGetAnalyticsInstanceId) {
296296
EXPECT_EQ(std::string("FakeAnalyticsInstanceId0"), *result.result());
297297
}
298298

299+
TEST_F(AnalyticsTest, TestSetDefaultEventParameters) {
300+
// Android part is not tested here as it's about iOS fakes.
301+
// This test focuses on the iOS fake reporting.
302+
// Android: AddExpectationAndroid("FirebaseAnalytics.setDefaultEventParameters", {"my_param_bool=true,my_param_double=3.14,my_param_int=123,my_param_string=hello"});
303+
AddExpectationApple("+[FIRAnalytics setDefaultEventParameters:]",
304+
{"my_param_bool=1,my_param_double=3.14,my_param_int=123,my_param_string=hello"});
305+
306+
std::map<std::string, Variant> default_params;
307+
default_params["my_param_string"] = "hello";
308+
default_params["my_param_double"] = 3.14;
309+
default_params["my_param_int"] = 123;
310+
default_params["my_param_bool"] = true; // Note: [NSNumber numberWithBool:YES] stringifies to 1
311+
312+
SetDefaultEventParameters(default_params);
313+
}
314+
315+
TEST_F(AnalyticsTest, TestSetDefaultEventParametersWithNull) {
316+
// Android: AddExpectationAndroid("FirebaseAnalytics.setDefaultEventParameters", {"key_to_clear=null,other_key=value"});
317+
AddExpectationApple("+[FIRAnalytics setDefaultEventParameters:]",
318+
{"key_to_clear=<NSNull>,other_key=value"});
319+
320+
std::map<std::string, Variant> default_params;
321+
default_params["key_to_clear"] = Variant::Null();
322+
default_params["other_key"] = "value";
323+
324+
SetDefaultEventParameters(default_params);
325+
}
326+
327+
TEST_F(AnalyticsTest, TestSetDefaultEventParametersEmpty) {
328+
// Android: AddExpectationAndroid("FirebaseAnalytics.setDefaultEventParameters", {""}); // Or however an empty bundle is represented
329+
AddExpectationApple("+[FIRAnalytics setDefaultEventParameters:]", {""});
330+
331+
std::map<std::string, Variant> default_params;
332+
SetDefaultEventParameters(default_params);
333+
}
334+
335+
TEST_F(AnalyticsTest, TestClearDefaultEventParameters) {
336+
// Android: AddExpectationAndroid("FirebaseAnalytics.setDefaultEventParameters", {"null"}); // Passing null bundle
337+
AddExpectationApple("+[FIRAnalytics setDefaultEventParameters:]", {"nil"});
338+
339+
ClearDefaultEventParameters();
340+
}
341+
342+
TEST_F(AnalyticsTest, TestSetDefaultEventParametersAndroid) {
343+
AddExpectationAndroid("FirebaseAnalytics.setDefaultEventParameters",
344+
{"my_bool=true,my_double=3.14,my_int=123,my_string=hello"});
345+
346+
std::map<std::string, Variant> default_params;
347+
default_params["my_string"] = "hello";
348+
default_params["my_double"] = 3.14;
349+
default_params["my_int"] = 123;
350+
default_params["my_bool"] = true;
351+
352+
SetDefaultEventParameters(default_params);
353+
WaitForMainThreadTask();
354+
}
355+
356+
TEST_F(AnalyticsTest, TestSetDefaultEventParametersWithNullValueAndroid) {
357+
AddExpectationAndroid("FirebaseAnalytics.setDefaultEventParameters",
358+
{"key_to_clear=<null_string_value>,other_key=value"});
359+
360+
std::map<std::string, Variant> default_params;
361+
default_params["key_to_clear"] = Variant::Null();
362+
default_params["other_key"] = "value";
363+
364+
SetDefaultEventParameters(default_params);
365+
WaitForMainThreadTask();
366+
}
367+
368+
TEST_F(AnalyticsTest, TestSetDefaultEventParametersEmptyAndroid) {
369+
AddExpectationAndroid("FirebaseAnalytics.setDefaultEventParameters", {""});
370+
371+
std::map<std::string, Variant> default_params;
372+
SetDefaultEventParameters(default_params);
373+
WaitForMainThreadTask();
374+
}
375+
376+
TEST_F(AnalyticsTest, TestClearDefaultEventParametersAndroid) {
377+
AddExpectationAndroid("FirebaseAnalytics.setDefaultEventParameters", {"null"});
378+
379+
ClearDefaultEventParameters();
380+
WaitForMainThreadTask();
381+
}
382+
299383
} // namespace analytics
300384
} // namespace firebase

0 commit comments

Comments
 (0)