From a2ab4dc75a23d21033b01615f5b6900c933c64f4 Mon Sep 17 00:00:00 2001 From: a-maurice Date: Thu, 10 Oct 2024 15:16:58 -0700 Subject: [PATCH 1/8] Add logic for Analytics parameters with maps --- analytics/src/analytics_android.cc | 109 ++++++++++++++++++++++++----- analytics/src/analytics_ios.mm | 82 +++++++++++++++++----- app/src/util_android.h | 4 +- 3 files changed, 157 insertions(+), 38 deletions(-) diff --git a/analytics/src/analytics_android.cc b/analytics/src/analytics_android.cc index 16f55b8995..0464bdb326 100644 --- a/analytics/src/analytics_android.cc +++ b/analytics/src/analytics_android.cc @@ -355,6 +355,93 @@ void AddToBundle(JNIEnv* env, jobject bundle, const char* key, int64_t value) { env->DeleteLocalRef(key_string); } +void AddArrayListToBundle(JNIEnv* env, jobject bundle, const char* key, jobject arraylist) { + jstring key_string = env->NewStringUTF(key); + env->CallVoidMethod(bundle, util::bundle::GetMethodId(util::bundle::kPutParcelableArrayList), + key_string, arraylist); + util::CheckAndClearJniExceptions(env); + env->DeleteLocalRef(key_string); +} + +void AddBundleToBundle(JNIEnv* env, jobject bundle, const char* key, jobject inner_bundle) { + jstring key_string = env->NewStringUTF(key); + env->CallVoidMethod(bundle, util::bundle::GetMethodId(util::bundle::kPutBundle), + key_string, inner_bundle); + util::CheckAndClearJniExceptions(env); + env->DeleteLocalRef(key_string); +} + +jobject MapToBundle(JNIEnv* env, const std::map& map); + +jobject VectorToArrayList(JNIEnv* env, const std::vector& vector) { + jobject arraylist = + env->NewObject(util::array_list::GetClass(), + util::array_list::GetMethodId(util::array_list::kConstructor)); + + for (const Variant& element : vector) { + if (element.is_map()) { + jobject bundle = MapToBundle(env, element.map()); + env->CallBooleanMethod(arraylist, util::array_list::GetMethodId(util::array_list::kAdd), + bundle); + env->DeleteLocalRef(bundle); + } else { + LogError("VectorToArrayList: Unsupported type (%s) within vector.", + Variant::TypeName(element.type())); + } + } + return arraylist; +} + +bool AddVariantToBundle(JNIEnv* env, jobject bundle, const char* key, const Variant& value) { + if (value.is_int64()) { + AddToBundle(env, bundle, key, value.int64_value()); + } else if (value.is_double()) { + AddToBundle(env, bundle, key, + value.double_value()); + } else if (value.is_string()) { + AddToBundle(env, bundle, key, + value.string_value()); + } else if (value.is_bool()) { + // Just use integer 0 or 1. + AddToBundle(env, bundle, key, + value.bool_value() ? static_cast(1L) + : static_cast(0L)); + } else if (value.is_null()) { + // Just use integer 0 for null. + AddToBundle(env, bundle, key, static_cast(0L)); + } else if (value.is_vector()) { + jobject arraylist = VectorToArrayList(env, value.vector()); + AddArrayListToBundle(env, bundle, key, arraylist); + env->DeleteLocalRef(arraylist); + } else if (value.is_map()) { + jobject inner_bundle = MapToBundle(env, value.map()); + AddBundleToBundle(env, bundle, key, inner_bundle); + env->DeleteLocalRef(inner_bundle); + } else { + // A Variant type that couldn't be handled was passed in. + return false; + } + return true; +} + +jobject MapToBundle(JNIEnv* env, const std::map& map) { + jobject bundle = + env->NewObject(util::bundle::GetClass(), + util::bundle::GetMethodId(util::bundle::kConstructor)); + for (const auto& pair : map) { + // Only add elements that use a string key + if (!pair.first.is_string()) { + continue; + } + if (!AddVariantToBundle(env, bundle, pair.first.string_value(), pair.second)) { + LogError("MapToBundle: Unsupported type (%s) within map with key %s.", + Variant::TypeName(pair.second.type()), pair.first.string_value()); + } + util::CheckAndClearJniExceptions(env); + } + return bundle; +} + // Log an event with one string parameter. void LogEvent(const char* name, const char* parameter_name, const char* parameter_value) { @@ -404,27 +491,11 @@ void LogEvent(const char* name, const Parameter* parameters, LogEvent(env, name, [env, parameters, number_of_parameters](jobject bundle) { for (size_t i = 0; i < number_of_parameters; ++i) { const Parameter& parameter = parameters[i]; - if (parameter.value.is_int64()) { - AddToBundle(env, bundle, parameter.name, parameter.value.int64_value()); - } else if (parameter.value.is_double()) { - AddToBundle(env, bundle, parameter.name, - parameter.value.double_value()); - } else if (parameter.value.is_string()) { - AddToBundle(env, bundle, parameter.name, - parameter.value.string_value()); - } else if (parameter.value.is_bool()) { - // Just use integer 0 or 1. - AddToBundle(env, bundle, parameter.name, - parameter.value.bool_value() ? static_cast(1L) - : static_cast(0L)); - } else if (parameter.value.is_null()) { - // Just use integer 0 for null. - AddToBundle(env, bundle, parameter.name, static_cast(0L)); - } else { - // Vector or Map were passed in. + if (!AddVariantToBundle(env, bundle, parameter.name, parameter.value)) { + // A Variant type that couldn't be handled was passed in. LogError( "LogEvent(%s): %s is not a valid parameter value type. " - "Container types are not allowed. No event was logged.", + "No event was logged.", parameter.name, Variant::TypeName(parameter.value.type())); } } diff --git a/analytics/src/analytics_ios.mm b/analytics/src/analytics_ios.mm index 4ceb8bdc7a..7ba4183e72 100644 --- a/analytics/src/analytics_ios.mm +++ b/analytics/src/analytics_ios.mm @@ -231,6 +231,67 @@ void LogEvent(const char* name) { [FIRAnalytics logEventWithName:@(name) parameters:@{}]; } +NSDictionary* MapToDictionary(const std::map& map); + +NSArray* VectorToArray(const std::vector& vector) { + NSMutableArray* array = [NSMutableArray arrayWithCapacity:vector.size()]; + for (const Variant& element : vector) { + if (element.is_map()) { + NSDictionary* dict = MapToDictionary(element.map()); + [array addObject:dict]; + } else { + LogError("VectorToArray: Unsupported type (%s) within vector.", + Variant::TypeName(element.type())); + } + } + return array; +} + +bool AddVariantToDictionary(NSMutableDictionary* dict, NSString* key, const Variant& value) { + if (value.is_int64()) { + [dict setObject:[NSNumber numberWithLongLong:value.int64_value()] + forKey:key]; + } else if (value.is_double()) { + [dict setObject:[NSNumber numberWithDouble:value.double_value()] + forKey:key]; + } else if (value.is_string()) { + [dict setObject:SafeString(value.string_value()) forKey:key]; + } else if (value.is_bool()) { + // Just use integer 0 or 1. + [dict setObject:[NSNumber numberWithLongLong:value.bool_value() ? 1 : 0] + forKey:key]; + } else if (value.is_null()) { + // Just use integer 0 for null. + [dict setObject:[NSNumber numberWithLongLong:0] forKey:key]; + } else if (value.is_vector()) { + NSArray* array = VectorToArray(value.vector()); + [dict setObject:array forKey:key]; + } else if (value.is_map()) { + NSDictionary* inner_dict = MapToDictionary(value.map()); + [dict setObject:inner_dict forKey:key]; + } else { + // A Variant type that couldn't be handled was passed in. + return false; + } + return true; +} + +NSDictionary* MapToDictionary(const std::map& map) { + NSMutableDictionary* dict = [NSMutableDictionary dictionaryWithCapacity:map.size()]; + for (const auto& pair : map) { + // Only add elements that use a string key + if (!pair.first.is_string()) { + continue; + } + NSString* key = SafeString(pair.first.string_value()); + const Variant& value = pair.second; + if (!AddVariantToDictionary(dict, key, value)) { + LogError("MapToDictionary: Unsupported type (%s) within map with key %s.", + Variant::TypeName(value.type()), key); + } + } +} + // Log an event with associated parameters. void LogEvent(const char* name, const Parameter* parameters, size_t number_of_parameters) { FIREBASE_ASSERT_RETURN_VOID(internal::IsInitialized()); @@ -239,25 +300,10 @@ void LogEvent(const char* name, const Parameter* parameters, size_t number_of_pa for (size_t i = 0; i < number_of_parameters; ++i) { const Parameter& parameter = parameters[i]; NSString* parameter_name = SafeString(parameter.name); - if (parameter.value.is_int64()) { - [parameters_dict setObject:[NSNumber numberWithLongLong:parameter.value.int64_value()] - forKey:parameter_name]; - } else if (parameter.value.is_double()) { - [parameters_dict setObject:[NSNumber numberWithDouble:parameter.value.double_value()] - forKey:parameter_name]; - } else if (parameter.value.is_string()) { - [parameters_dict setObject:SafeString(parameter.value.string_value()) forKey:parameter_name]; - } else if (parameter.value.is_bool()) { - // Just use integer 0 or 1. - [parameters_dict setObject:[NSNumber numberWithLongLong:parameter.value.bool_value() ? 1 : 0] - forKey:parameter_name]; - } else if (parameter.value.is_null()) { - // Just use integer 0 for null. - [parameters_dict setObject:[NSNumber numberWithLongLong:0] forKey:parameter_name]; - } else { - // Vector or Map were passed in. + if (!AddVariantToDictionary(parameters_dict, parameter_name, parameter.value)){ + // A Variant type that couldn't be handled was passed in. LogError("LogEvent(%s): %s is not a valid parameter value type. " - "Container types are not allowed. No event was logged.", + "No event was logged.", parameter.name, Variant::TypeName(parameter.value.type())); } } diff --git a/app/src/util_android.h b/app/src/util_android.h index 6cfeae5acf..2506bf12bb 100644 --- a/app/src/util_android.h +++ b/app/src/util_android.h @@ -498,7 +498,9 @@ METHOD_LOOKUP_DECLARATION(activity, ACTIVITY_METHODS) X(PutFloat, "putFloat", "(Ljava/lang/String;F)V"), \ X(PutLong, "putLong", "(Ljava/lang/String;J)V"), \ X(PutString, "putString", "(Ljava/lang/String;Ljava/lang/String;)V"), \ - X(PutBundle, "putBundle", "(Ljava/lang/String;Landroid/os/Bundle;)V") + X(PutBundle, "putBundle", "(Ljava/lang/String;Landroid/os/Bundle;)V"),\ + X(PutParcelableArrayList, "putParcelableArrayList", \ + "(Ljava/lang/String;Ljava/util/ArrayList;)V") // clang-format on METHOD_LOOKUP_DECLARATION(bundle, BUNDLE_METHODS) From fb6f866e0fb2cbe1b5a3e935dce7ee1f807b7e14 Mon Sep 17 00:00:00 2001 From: a-maurice Date: Tue, 15 Oct 2024 15:01:09 -0700 Subject: [PATCH 2/8] Update analytics_ios.mm --- analytics/src/analytics_ios.mm | 1 + 1 file changed, 1 insertion(+) diff --git a/analytics/src/analytics_ios.mm b/analytics/src/analytics_ios.mm index 7ba4183e72..5e5e7e3587 100644 --- a/analytics/src/analytics_ios.mm +++ b/analytics/src/analytics_ios.mm @@ -290,6 +290,7 @@ bool AddVariantToDictionary(NSMutableDictionary* dict, NSString* key, const Vari Variant::TypeName(value.type()), key); } } + return dict; } // Log an event with associated parameters. From 8d933891963f328a60f30e91705a6cd34423a3f6 Mon Sep 17 00:00:00 2001 From: a-maurice Date: Wed, 16 Oct 2024 18:13:06 -0700 Subject: [PATCH 3/8] Add a new test --- .../integration_test/src/integration_test.cc | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/analytics/integration_test/src/integration_test.cc b/analytics/integration_test/src/integration_test.cc index 575ff30b82..9ab3508d43 100644 --- a/analytics/integration_test/src/integration_test.cc +++ b/analytics/integration_test/src/integration_test.cc @@ -271,6 +271,27 @@ TEST_F(FirebaseAnalyticsTest, TestLogEventWithMultipleParameters) { sizeof(kLevelUpParameters) / sizeof(kLevelUpParameters[0])); } +TEST_F(FirebaseAnalyticsTest, TestLogEventWithComplexParameters) { + // Define the items that will go into the kParameterItems list. + firebase::Variant first_item = firebase::Variant::EmptyMap(); + first_item.map()[firebase::analytics::kParameterItemID] = "SKU_12345"; + first_item.map()[firebase::analytics::kParameterItemName] = "Horse Armor DLC"; + firebase::Variant second_item = firebase::Variant::EmptyMap(); + second_item.map()[firebase::analytics::kParameterItemID] = "SKU_67890"; + second_item.map()[firebase::analytics::kParameterItemName] = "Gold Horse Armor DLC"; + + // Define the parameters that are sent with the ViewCart event. + const firebase::analytics::Parameter kViewCartParameters[] = { + firebase::analytics::Parameter(firebase::analytics::kParameterCurrency, "USD"), + firebase::analytics::Parameter(firebase::analytics::kParameterValue, 30.03), + firebase::analytics::Parameter(firebase::analytics::kParameterItems, std::vector{ first_item, second_item }), + }; + + firebase::analytics::LogEvent( + firebase::analytics::kEventViewCart, kViewCartParameters, + sizeof(kViewCartParameters) / sizeof(kViewCartParameters[0])); +} + TEST_F(FirebaseAnalyticsTest, TestSetConsent) { // On Android, this test must be performed at the end, after all the tests for // session ID and instance ID. This is because once you call SetConsent to From eb775e1b7d2f70ced24c28f6aafa8cce0d298db4 Mon Sep 17 00:00:00 2001 From: a-maurice Date: Wed, 16 Oct 2024 18:13:44 -0700 Subject: [PATCH 4/8] Code formatting --- .../integration_test/src/integration_test.cc | 15 ++++-- analytics/src/analytics_android.cc | 40 ++++++++------- analytics/src/analytics_ios.mm | 49 +++++++++---------- 3 files changed, 56 insertions(+), 48 deletions(-) diff --git a/analytics/integration_test/src/integration_test.cc b/analytics/integration_test/src/integration_test.cc index 9ab3508d43..bedccee516 100644 --- a/analytics/integration_test/src/integration_test.cc +++ b/analytics/integration_test/src/integration_test.cc @@ -278,15 +278,20 @@ TEST_F(FirebaseAnalyticsTest, TestLogEventWithComplexParameters) { first_item.map()[firebase::analytics::kParameterItemName] = "Horse Armor DLC"; firebase::Variant second_item = firebase::Variant::EmptyMap(); second_item.map()[firebase::analytics::kParameterItemID] = "SKU_67890"; - second_item.map()[firebase::analytics::kParameterItemName] = "Gold Horse Armor DLC"; + second_item.map()[firebase::analytics::kParameterItemName] = + "Gold Horse Armor DLC"; // Define the parameters that are sent with the ViewCart event. const firebase::analytics::Parameter kViewCartParameters[] = { - firebase::analytics::Parameter(firebase::analytics::kParameterCurrency, "USD"), - firebase::analytics::Parameter(firebase::analytics::kParameterValue, 30.03), - firebase::analytics::Parameter(firebase::analytics::kParameterItems, std::vector{ first_item, second_item }), + firebase::analytics::Parameter(firebase::analytics::kParameterCurrency, + "USD"), + firebase::analytics::Parameter(firebase::analytics::kParameterValue, + 30.03), + firebase::analytics::Parameter( + firebase::analytics::kParameterItems, + std::vector{first_item, second_item}), }; - + firebase::analytics::LogEvent( firebase::analytics::kEventViewCart, kViewCartParameters, sizeof(kViewCartParameters) / sizeof(kViewCartParameters[0])); diff --git a/analytics/src/analytics_android.cc b/analytics/src/analytics_android.cc index 0464bdb326..cf35233b0e 100644 --- a/analytics/src/analytics_android.cc +++ b/analytics/src/analytics_android.cc @@ -355,17 +355,21 @@ void AddToBundle(JNIEnv* env, jobject bundle, const char* key, int64_t value) { env->DeleteLocalRef(key_string); } -void AddArrayListToBundle(JNIEnv* env, jobject bundle, const char* key, jobject arraylist) { +void AddArrayListToBundle(JNIEnv* env, jobject bundle, const char* key, + jobject arraylist) { jstring key_string = env->NewStringUTF(key); - env->CallVoidMethod(bundle, util::bundle::GetMethodId(util::bundle::kPutParcelableArrayList), - key_string, arraylist); + env->CallVoidMethod( + bundle, util::bundle::GetMethodId(util::bundle::kPutParcelableArrayList), + key_string, arraylist); util::CheckAndClearJniExceptions(env); env->DeleteLocalRef(key_string); } -void AddBundleToBundle(JNIEnv* env, jobject bundle, const char* key, jobject inner_bundle) { +void AddBundleToBundle(JNIEnv* env, jobject bundle, const char* key, + jobject inner_bundle) { jstring key_string = env->NewStringUTF(key); - env->CallVoidMethod(bundle, util::bundle::GetMethodId(util::bundle::kPutBundle), + env->CallVoidMethod(bundle, + util::bundle::GetMethodId(util::bundle::kPutBundle), key_string, inner_bundle); util::CheckAndClearJniExceptions(env); env->DeleteLocalRef(key_string); @@ -374,15 +378,16 @@ void AddBundleToBundle(JNIEnv* env, jobject bundle, const char* key, jobject inn jobject MapToBundle(JNIEnv* env, const std::map& map); jobject VectorToArrayList(JNIEnv* env, const std::vector& vector) { - jobject arraylist = - env->NewObject(util::array_list::GetClass(), - util::array_list::GetMethodId(util::array_list::kConstructor)); + jobject arraylist = env->NewObject( + util::array_list::GetClass(), + util::array_list::GetMethodId(util::array_list::kConstructor)); for (const Variant& element : vector) { if (element.is_map()) { jobject bundle = MapToBundle(env, element.map()); - env->CallBooleanMethod(arraylist, util::array_list::GetMethodId(util::array_list::kAdd), - bundle); + env->CallBooleanMethod( + arraylist, util::array_list::GetMethodId(util::array_list::kAdd), + bundle); env->DeleteLocalRef(bundle); } else { LogError("VectorToArrayList: Unsupported type (%s) within vector.", @@ -392,15 +397,14 @@ jobject VectorToArrayList(JNIEnv* env, const std::vector& vector) { return arraylist; } -bool AddVariantToBundle(JNIEnv* env, jobject bundle, const char* key, const Variant& value) { +bool AddVariantToBundle(JNIEnv* env, jobject bundle, const char* key, + const Variant& value) { if (value.is_int64()) { AddToBundle(env, bundle, key, value.int64_value()); } else if (value.is_double()) { - AddToBundle(env, bundle, key, - value.double_value()); + AddToBundle(env, bundle, key, value.double_value()); } else if (value.is_string()) { - AddToBundle(env, bundle, key, - value.string_value()); + AddToBundle(env, bundle, key, value.string_value()); } else if (value.is_bool()) { // Just use integer 0 or 1. AddToBundle(env, bundle, key, @@ -433,9 +437,11 @@ jobject MapToBundle(JNIEnv* env, const std::map& map) { if (!pair.first.is_string()) { continue; } - if (!AddVariantToBundle(env, bundle, pair.first.string_value(), pair.second)) { + if (!AddVariantToBundle(env, bundle, pair.first.string_value(), + pair.second)) { LogError("MapToBundle: Unsupported type (%s) within map with key %s.", - Variant::TypeName(pair.second.type()), pair.first.string_value()); + Variant::TypeName(pair.second.type()), + pair.first.string_value()); } util::CheckAndClearJniExceptions(env); } diff --git a/analytics/src/analytics_ios.mm b/analytics/src/analytics_ios.mm index 5e5e7e3587..15f5d1fc31 100644 --- a/analytics/src/analytics_ios.mm +++ b/analytics/src/analytics_ios.mm @@ -249,31 +249,28 @@ void LogEvent(const char* name) { bool AddVariantToDictionary(NSMutableDictionary* dict, NSString* key, const Variant& value) { if (value.is_int64()) { - [dict setObject:[NSNumber numberWithLongLong:value.int64_value()] - forKey:key]; - } else if (value.is_double()) { - [dict setObject:[NSNumber numberWithDouble:value.double_value()] - forKey:key]; - } else if (value.is_string()) { - [dict setObject:SafeString(value.string_value()) forKey:key]; - } else if (value.is_bool()) { - // Just use integer 0 or 1. - [dict setObject:[NSNumber numberWithLongLong:value.bool_value() ? 1 : 0] - forKey:key]; - } else if (value.is_null()) { - // Just use integer 0 for null. - [dict setObject:[NSNumber numberWithLongLong:0] forKey:key]; - } else if (value.is_vector()) { - NSArray* array = VectorToArray(value.vector()); - [dict setObject:array forKey:key]; - } else if (value.is_map()) { - NSDictionary* inner_dict = MapToDictionary(value.map()); - [dict setObject:inner_dict forKey:key]; - } else { - // A Variant type that couldn't be handled was passed in. - return false; - } - return true; + [dict setObject:[NSNumber numberWithLongLong:value.int64_value()] forKey:key]; + } else if (value.is_double()) { + [dict setObject:[NSNumber numberWithDouble:value.double_value()] forKey:key]; + } else if (value.is_string()) { + [dict setObject:SafeString(value.string_value()) forKey:key]; + } else if (value.is_bool()) { + // Just use integer 0 or 1. + [dict setObject:[NSNumber numberWithLongLong:value.bool_value() ? 1 : 0] forKey:key]; + } else if (value.is_null()) { + // Just use integer 0 for null. + [dict setObject:[NSNumber numberWithLongLong:0] forKey:key]; + } else if (value.is_vector()) { + NSArray* array = VectorToArray(value.vector()); + [dict setObject:array forKey:key]; + } else if (value.is_map()) { + NSDictionary* inner_dict = MapToDictionary(value.map()); + [dict setObject:inner_dict forKey:key]; + } else { + // A Variant type that couldn't be handled was passed in. + return false; + } + return true; } NSDictionary* MapToDictionary(const std::map& map) { @@ -301,7 +298,7 @@ void LogEvent(const char* name, const Parameter* parameters, size_t number_of_pa for (size_t i = 0; i < number_of_parameters; ++i) { const Parameter& parameter = parameters[i]; NSString* parameter_name = SafeString(parameter.name); - if (!AddVariantToDictionary(parameters_dict, parameter_name, parameter.value)){ + if (!AddVariantToDictionary(parameters_dict, parameter_name, parameter.value)) { // A Variant type that couldn't be handled was passed in. LogError("LogEvent(%s): %s is not a valid parameter value type. " "No event was logged.", From 25de44850748cbb125e59e96addb4dc2fb794702 Mon Sep 17 00:00:00 2001 From: a-maurice Date: Wed, 16 Oct 2024 18:21:08 -0700 Subject: [PATCH 5/8] Add comments --- analytics/src/analytics_android.cc | 9 +++++++++ analytics/src/analytics_ios.mm | 4 ++++ 2 files changed, 13 insertions(+) diff --git a/analytics/src/analytics_android.cc b/analytics/src/analytics_android.cc index cf35233b0e..fd0d9c4618 100644 --- a/analytics/src/analytics_android.cc +++ b/analytics/src/analytics_android.cc @@ -355,6 +355,7 @@ void AddToBundle(JNIEnv* env, jobject bundle, const char* key, int64_t value) { env->DeleteLocalRef(key_string); } +// Add an ArrayList to the given Bundle. void AddArrayListToBundle(JNIEnv* env, jobject bundle, const char* key, jobject arraylist) { jstring key_string = env->NewStringUTF(key); @@ -365,6 +366,7 @@ void AddArrayListToBundle(JNIEnv* env, jobject bundle, const char* key, env->DeleteLocalRef(key_string); } +// Add a Bundle to the given Bundle. void AddBundleToBundle(JNIEnv* env, jobject bundle, const char* key, jobject inner_bundle) { jstring key_string = env->NewStringUTF(key); @@ -375,8 +377,11 @@ void AddBundleToBundle(JNIEnv* env, jobject bundle, const char* key, env->DeleteLocalRef(key_string); } +// Declared here so that it can be used, defined below. jobject MapToBundle(JNIEnv* env, const std::map& map); +// Converts the given vector into a Java ArrayList. It is up to the +// caller to delete the local reference when done. jobject VectorToArrayList(JNIEnv* env, const std::vector& vector) { jobject arraylist = env->NewObject( util::array_list::GetClass(), @@ -388,6 +393,7 @@ jobject VectorToArrayList(JNIEnv* env, const std::vector& vector) { env->CallBooleanMethod( arraylist, util::array_list::GetMethodId(util::array_list::kAdd), bundle); + util::CheckAndClearJniExceptions(env); env->DeleteLocalRef(bundle); } else { LogError("VectorToArrayList: Unsupported type (%s) within vector.", @@ -397,6 +403,7 @@ jobject VectorToArrayList(JNIEnv* env, const std::vector& vector) { return arraylist; } +// Converts and adds the Variant to the given Bundle. bool AddVariantToBundle(JNIEnv* env, jobject bundle, const char* key, const Variant& value) { if (value.is_int64()) { @@ -428,6 +435,8 @@ bool AddVariantToBundle(JNIEnv* env, jobject bundle, const char* key, return true; } +// Converts the given map into a Java Bundle. It is up to the caller +// to delete the local reference when done. jobject MapToBundle(JNIEnv* env, const std::map& map) { jobject bundle = env->NewObject(util::bundle::GetClass(), diff --git a/analytics/src/analytics_ios.mm b/analytics/src/analytics_ios.mm index 15f5d1fc31..b74d388b89 100644 --- a/analytics/src/analytics_ios.mm +++ b/analytics/src/analytics_ios.mm @@ -231,8 +231,10 @@ void LogEvent(const char* name) { [FIRAnalytics logEventWithName:@(name) parameters:@{}]; } +// Declared here so that it can be used, defined below. NSDictionary* MapToDictionary(const std::map& map); +// Converts the given vector into an ObjC NSArray of ObjC objects. NSArray* VectorToArray(const std::vector& vector) { NSMutableArray* array = [NSMutableArray arrayWithCapacity:vector.size()]; for (const Variant& element : vector) { @@ -247,6 +249,7 @@ void LogEvent(const char* name) { return array; } +// Converts and adds the Variant to the given Dictionary. bool AddVariantToDictionary(NSMutableDictionary* dict, NSString* key, const Variant& value) { if (value.is_int64()) { [dict setObject:[NSNumber numberWithLongLong:value.int64_value()] forKey:key]; @@ -273,6 +276,7 @@ bool AddVariantToDictionary(NSMutableDictionary* dict, NSString* key, const Vari return true; } +// Converts the given map into an ObjC dictionary of ObjC objects. NSDictionary* MapToDictionary(const std::map& map) { NSMutableDictionary* dict = [NSMutableDictionary dictionaryWithCapacity:map.size()]; for (const auto& pair : map) { From 15e076c470fa1a713e4868faf8b2c0b31d96c24b Mon Sep 17 00:00:00 2001 From: a-maurice Date: Wed, 16 Oct 2024 18:26:38 -0700 Subject: [PATCH 6/8] Update readme.md --- release_build_files/readme.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/release_build_files/readme.md b/release_build_files/readme.md index 61ad3b5abc..95f1e351e2 100644 --- a/release_build_files/readme.md +++ b/release_build_files/readme.md @@ -634,6 +634,8 @@ code. ### Upcoming Release - Changes - General (Android): Reduced minSdkVersion back to 23. + - Analytics: Add support for Parameters of Lists of Dictionaries, needed + by some events such as ViewCart. - Auth (Android): Setting photo_url to empty string with UpdateUserProfile clears the field, making it consistent with the other platforms. From 9681ec3805158eb2a058c426ab1cf257583dab67 Mon Sep 17 00:00:00 2001 From: a-maurice Date: Wed, 16 Oct 2024 18:27:56 -0700 Subject: [PATCH 7/8] Update util_android.h --- app/src/util_android.h | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/app/src/util_android.h b/app/src/util_android.h index 2506bf12bb..3467c53b22 100644 --- a/app/src/util_android.h +++ b/app/src/util_android.h @@ -491,15 +491,15 @@ METHOD_LOOKUP_DECLARATION(activity, ACTIVITY_METHODS) // Used to setup the cache of Bundle class method IDs to reduce time spent // looking up methods by string. // clang-format off -#define BUNDLE_METHODS(X) \ - X(Constructor, "", "()V"), \ - X(GetString, "getString", "(Ljava/lang/String;)Ljava/lang/String;"), \ - X(KeySet, "keySet", "()Ljava/util/Set;"), \ - X(PutFloat, "putFloat", "(Ljava/lang/String;F)V"), \ - X(PutLong, "putLong", "(Ljava/lang/String;J)V"), \ - X(PutString, "putString", "(Ljava/lang/String;Ljava/lang/String;)V"), \ - X(PutBundle, "putBundle", "(Ljava/lang/String;Landroid/os/Bundle;)V"),\ - X(PutParcelableArrayList, "putParcelableArrayList", \ +#define BUNDLE_METHODS(X) \ + X(Constructor, "", "()V"), \ + X(GetString, "getString", "(Ljava/lang/String;)Ljava/lang/String;"), \ + X(KeySet, "keySet", "()Ljava/util/Set;"), \ + X(PutFloat, "putFloat", "(Ljava/lang/String;F)V"), \ + X(PutLong, "putLong", "(Ljava/lang/String;J)V"), \ + X(PutString, "putString", "(Ljava/lang/String;Ljava/lang/String;)V"), \ + X(PutBundle, "putBundle", "(Ljava/lang/String;Landroid/os/Bundle;)V"), \ + X(PutParcelableArrayList, "putParcelableArrayList", \ "(Ljava/lang/String;Ljava/util/ArrayList;)V") // clang-format on METHOD_LOOKUP_DECLARATION(bundle, BUNDLE_METHODS) From 3c6eb9c609c5862b5cdff2400fa0bbf1709d973b Mon Sep 17 00:00:00 2001 From: a-maurice Date: Thu, 17 Oct 2024 15:16:48 -0700 Subject: [PATCH 8/8] Renamed conversion function to be clearer --- analytics/src/analytics_android.cc | 7 ++++--- analytics/src/analytics_ios.mm | 8 ++++---- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/analytics/src/analytics_android.cc b/analytics/src/analytics_android.cc index fd0d9c4618..d1279c6ba7 100644 --- a/analytics/src/analytics_android.cc +++ b/analytics/src/analytics_android.cc @@ -382,7 +382,8 @@ jobject MapToBundle(JNIEnv* env, const std::map& map); // Converts the given vector into a Java ArrayList. It is up to the // caller to delete the local reference when done. -jobject VectorToArrayList(JNIEnv* env, const std::vector& vector) { +jobject VectorOfMapsToArrayList(JNIEnv* env, + const std::vector& vector) { jobject arraylist = env->NewObject( util::array_list::GetClass(), util::array_list::GetMethodId(util::array_list::kConstructor)); @@ -396,7 +397,7 @@ jobject VectorToArrayList(JNIEnv* env, const std::vector& vector) { util::CheckAndClearJniExceptions(env); env->DeleteLocalRef(bundle); } else { - LogError("VectorToArrayList: Unsupported type (%s) within vector.", + LogError("VectorOfMapsToArrayList: Unsupported type (%s) within vector.", Variant::TypeName(element.type())); } } @@ -421,7 +422,7 @@ bool AddVariantToBundle(JNIEnv* env, jobject bundle, const char* key, // Just use integer 0 for null. AddToBundle(env, bundle, key, static_cast(0L)); } else if (value.is_vector()) { - jobject arraylist = VectorToArrayList(env, value.vector()); + jobject arraylist = VectorOfMapsToArrayList(env, value.vector()); AddArrayListToBundle(env, bundle, key, arraylist); env->DeleteLocalRef(arraylist); } else if (value.is_map()) { diff --git a/analytics/src/analytics_ios.mm b/analytics/src/analytics_ios.mm index b74d388b89..d56fd61356 100644 --- a/analytics/src/analytics_ios.mm +++ b/analytics/src/analytics_ios.mm @@ -234,15 +234,15 @@ void LogEvent(const char* name) { // Declared here so that it can be used, defined below. NSDictionary* MapToDictionary(const std::map& map); -// Converts the given vector into an ObjC NSArray of ObjC objects. -NSArray* VectorToArray(const std::vector& vector) { +// Converts the given vector of Maps into an ObjC NSArray of ObjC NSDictionaries. +NSArray* VectorOfMapsToArray(const std::vector& vector) { NSMutableArray* array = [NSMutableArray arrayWithCapacity:vector.size()]; for (const Variant& element : vector) { if (element.is_map()) { NSDictionary* dict = MapToDictionary(element.map()); [array addObject:dict]; } else { - LogError("VectorToArray: Unsupported type (%s) within vector.", + LogError("VectorOfMapsToArray: Unsupported type (%s) within vector.", Variant::TypeName(element.type())); } } @@ -264,7 +264,7 @@ bool AddVariantToDictionary(NSMutableDictionary* dict, NSString* key, const Vari // Just use integer 0 for null. [dict setObject:[NSNumber numberWithLongLong:0] forKey:key]; } else if (value.is_vector()) { - NSArray* array = VectorToArray(value.vector()); + NSArray* array = VectorOfMapsToArray(value.vector()); [dict setObject:array forKey:key]; } else if (value.is_map()) { NSDictionary* inner_dict = MapToDictionary(value.map());