Ir al contenido

Registrar datos de notificaciones push

Aprende a registrar datos de notificaciones push a través del SDK de Braze.

Puedes registrar análisis en tiempo real haciendo llamadas al endpoint /users/track. Para registrar análisis, envía el valor braze_id desde el panel de Braze para identificar qué perfil de usuario actualizar.

Ejemplo de panel de push personalizado

Registrar datos manualmente

Dependiendo de los detalles de tu carga útil, puedes registrar análisis manualmente dentro de tu implementación de FirebaseMessagingService.onMessageReceived o de tu actividad de inicio. Tu subclase de FirebaseMessagingService debe finalizar la ejecución en un plazo de 9 segundos desde la invocación para evitar ser marcada o terminada por el sistema Android.

Puedes registrar análisis en tiempo real con la ayuda del endpoint /users/track de la API de Braze. Para registrar los análisis, envía el valor braze_id en el campo de pares clave-valor (como se muestra en la siguiente captura de pantalla) para identificar qué perfil de usuario hay que actualizar.

Un mensaje push con tres conjuntos de pares clave-valor. 1. "Braze_id" configurado como una llamada Liquid para obtener el ID de Braze. 2. "cert_title" configurado como "Braze Marketer Certification". 3. "Cert_description" configurado como "Certified Braze marketers drive...".

Registrar datos manualmente

Registrar datos manualmente requerirá que primero configures los espacios de trabajo dentro de Xcode y, a continuación, crees, guardes y recuperes los análisis. Esto requerirá algo de trabajo personalizado de desarrollo de tu parte. Los siguientes fragmentos de código que se muestran te ayudarán a abordar esto.

Es importante tener en cuenta que los análisis no se envían a Braze hasta que la aplicación móvil se lanza posteriormente. Esto significa que, dependiendo de tu configuración de descarte, a menudo existe un período de tiempo indeterminado entre el momento en que se descarta una notificación push y el momento en que se lanza la aplicación móvil y se recuperan los análisis. Si bien este margen de tiempo puede no afectar a todos los ejemplos, debes considerar este impacto y ajustar el recorrido del usuario según sea necesario para incluir la apertura de la aplicación y abordar esta cuestión.

Un gráfico que describe cómo se procesan los análisis en Braze. 1. Se crean los datos de análisis. 2. Se guardan los datos de análisis. 3. Se descarta la notificación push. 4. Período de tiempo indeterminado entre el descarte de la notificación push y el lanzamiento de la aplicación móvil. 5. Se lanza la aplicación móvil. 6. Se reciben los datos de análisis. 7. Se envían los datos de análisis a Braze.

Paso 1: Configurar grupos de aplicaciones dentro de Xcode

En Xcode, añade la capacidad App Groups. Si no has tenido ningún espacio de trabajo en tu aplicación, ve a la capacidad del objetivo principal de la aplicación, activa App Groups y haz clic en el botón + Añadir. A continuación, utiliza el ID de paquete de tu aplicación para crear el espacio de trabajo. Por ejemplo, si el ID de paquete de tu aplicación es com.company.appname, puedes nombrar tu espacio de trabajo group.com.company.appname.xyz. Asegúrate de que App Groups esté activado tanto para el objetivo principal de tu aplicación como para el objetivo de la extensión de contenido.

Pantalla de firma y capacidades en Xcode con App Groups habilitado para los objetivos de la aplicación principal y la extensión.

Paso 2: Integrar fragmentos de código

Los siguientes fragmentos de código son una referencia útil sobre cómo guardar y enviar eventos personalizados, atributos personalizados y atributos de usuario. Esta guía hablará en términos de UserDefaults, pero la representación del código estará en la forma del archivo auxiliar RemoteStorage. Hay archivos auxiliares adicionales, UserAttributes y EventName Dictionary, que se utilizan al enviar y guardar atributos de usuario.

Guardar eventos personalizados

Para guardar eventos personalizados, debes crear los análisis desde cero. Esto se hace creando un diccionario, rellenándolo con metadatos y guardando los datos mediante el uso de un archivo auxiliar.

  1. Inicializa un diccionario con metadatos del evento
  2. Inicializa userDefaults para recuperar y almacenar los datos del evento
  3. Si ya existe un array, añade los nuevos datos al array existente y guárdalo
  4. Si no existe un array, guarda el nuevo array en userDefaults
func saveCustomEvent(with properties: [String: Any]? = nil) {
  // 1
  let customEventDictionary = Dictionary(eventName: "YOUR-EVENT-NAME", properties: properties)

  // 2
  let remoteStorage = RemoteStorage(storageType: .suite)

  // 3
  if var pendingEvents = remoteStorage.retrieve(forKey: .pendingCustomEvents) as? [[String: Any]] {
    pendingEvents.append(contentsOf: [customEventDictionary])
    remoteStorage.store(pendingEvents, forKey: .pendingCustomEvents)
  } else {
  // 4
    remoteStorage.store([customEventDictionary], forKey: .pendingCustomEvents)
  }
}
- (void)saveCustomEvent:(NSDictionary<NSString *, id> *)properties {
  // 1
  NSDictionary<NSString *, id> *customEventDictionary = [[NSDictionary alloc] initWithEventName:@"YOUR-EVENT-NAME" properties:properties];

  // 2
  RemoteStorage *remoteStorage = [[RemoteStorage alloc] initWithStorageType:StorageTypeSuite];
  NSMutableArray *pendingEvents = [[remoteStorage retrieveForKey:RemoteStorageKeyPendingCustomEvents] mutableCopy];

  // 3
  if (pendingEvents) {
    [pendingEvents addObject:customEventDictionary];
    [remoteStorage store:pendingEvents forKey:RemoteStorageKeyPendingCustomAttributes];
  } else {
  // 4
    [remoteStorage store:@[ customEventDictionary ] forKey:RemoteStorageKeyPendingCustomAttributes];
  }
}

Enviar eventos personalizados a Braze

El mejor momento para registrar cualquier análisis guardado desde una extensión de contenido de notificación es justo después de que se inicialice el SDK. Esto se puede hacer recorriendo en bucle los eventos pendientes, comprobando la clave “Event Name”, estableciendo los valores adecuados en Braze y luego borrando el almacenamiento para la próxima vez que se necesite esta función.

  1. Recorre en bucle el array de eventos pendientes
  2. Recorre en bucle cada par clave-valor en el diccionario pendingEvents
  3. Comprueba explícitamente la clave “Event Name” para establecer el valor correspondiente
  4. Cada otro par clave-valor se añadirá al diccionario properties
  5. Registra el evento personalizado individual
  6. Elimina todos los eventos pendientes del almacenamiento
func logPendingCustomEventsIfNecessary() {
  let remoteStorage = RemoteStorage(storageType: .suite)
  guard let pendingEvents = remoteStorage.retrieve(forKey: .pendingCustomEvents) as? [[String: Any]] else { return }

  // 1
  for event in pendingEvents {
    var eventName: String?
    var properties: [AnyHashable: Any] = [:]

  // 2
    for (key, value) in event {
      if key == PushNotificationKey.eventName.rawValue {
  // 3
        if let eventNameValue = value as? String {
          eventName = eventNameValue
        } else {
          print("Invalid type for event_name key")
        }
      } else {
  // 4
        properties[key] = value
      }
    }
  // 5
    if let eventName = eventName {
      AppDelegate.braze?.logCustomEvent(eventName, properties: properties)
    }
  }

  // 6
  remoteStorage.removeObject(forKey: .pendingCustomEvents)
}
- (void)logPendingEventsIfNecessary {
  RemoteStorage *remoteStorage = [[RemoteStorage alloc] initWithStorageType:StorageTypeSuite];
  NSArray *pendingEvents = [remoteStorage retrieveForKey:RemoteStorageKeyPendingCustomEvents];

  // 1
  for (NSDictionary<NSString *, id> *event in pendingEvents) {
    NSString *eventName = nil;
    NSMutableDictionary *properties = [NSMutableDictionary dictionary];

  // 2
    for (NSString* key in event) {
      if ([key isEqualToString:@"event_name"]) {
  // 3
        if ([[event objectForKey:key] isKindOfClass:[NSString class]]) {
          eventName = [event objectForKey:key];
        } else {
          NSLog(@"Invalid type for event_name key");
        }
      } else {
  // 4
        properties[key] = event[key];
      }
    }
  // 5
    if (eventName != nil) {
      [AppDelegate.braze logCustomEvent:eventName properties:properties];
    }
  }

  // 6
  [remoteStorage removeObjectForKey:RemoteStorageKeyPendingCustomEvents];
}

Guardar atributos personalizados

Para guardar atributos personalizados, debes crear los análisis desde cero. Esto se hace creando un diccionario, rellenándolo con metadatos y guardando los datos mediante el uso de un archivo auxiliar.

  1. Inicializa un diccionario con metadatos del atributo
  2. Inicializa userDefaults para recuperar y almacenar los datos del atributo
  3. Si ya existe un array, añade los nuevos datos al array existente y guárdalo
  4. Si no existe un array, guarda el nuevo array en userDefaults
func saveCustomAttribute() {
  // 1
  let customAttributeDictionary: [String: Any] = ["YOUR-CUSTOM-ATTRIBUTE-KEY": "YOUR-CUSTOM-ATTRIBUTE-VALUE"]

  // 2
  let remoteStorage = RemoteStorage(storageType: .suite)

  // 3
  if var pendingAttributes = remoteStorage.retrieve(forKey: .pendingCustomAttributes) as? [[String: Any]] {
    pendingAttributes.append(contentsOf: [customAttributeDictionary])
    remoteStorage.store(pendingAttributes, forKey: .pendingCustomAttributes)
  } else {
  // 4
    remoteStorage.store([customAttributeDictionary], forKey: .pendingCustomAttributes)
  }
}
- (void)saveCustomAttribute {
  // 1
  NSDictionary<NSString *, id> *customAttributeDictionary = @{ @"YOUR-CUSTOM-ATTRIBUTE-KEY": @"YOUR-CUSTOM-ATTRIBUTE-VALUE" };

  // 2
  RemoteStorage *remoteStorage = [[RemoteStorage alloc] initWithStorageType:StorageTypeSuite];
  NSMutableArray *pendingAttributes = [[remoteStorage retrieveForKey:RemoteStorageKeyPendingCustomAttributes] mutableCopy];

  // 3
  if (pendingAttributes) {
    [pendingAttributes addObject:customAttributeDictionary];
    [remoteStorage store:pendingAttributes forKey:RemoteStorageKeyPendingCustomAttributes];
  } else {
  // 4
    [remoteStorage store:@[ customAttributeDictionary ] forKey:RemoteStorageKeyPendingCustomAttributes];
  }
}

Enviar atributos personalizados a Braze

El mejor momento para registrar cualquier análisis guardado desde una extensión de contenido de notificación es justo después de que se inicialice el SDK. Esto se puede hacer recorriendo en bucle los atributos pendientes, estableciendo el atributo personalizado correspondiente en Braze y luego borrando el almacenamiento para la próxima vez que se necesite esta función.

  1. Recorre en bucle el array de atributos pendientes
  2. Recorre en bucle cada par clave-valor en el diccionario pendingAttributes
  3. Registra los atributos personalizados individuales con la clave y el valor correspondientes
  4. Elimina todos los atributos pendientes del almacenamiento
func logPendingCustomAttributesIfNecessary() {
  let remoteStorage = RemoteStorage(storageType: .suite)
  guard let pendingAttributes = remoteStorage.retrieve(forKey: .pendingCustomAttributes) as? [[String: Any]] else { return }

  // 1
  pendingAttributes.forEach { setCustomAttributesWith(keysAndValues: $0) }

  // 4
  remoteStorage.removeObject(forKey: .pendingCustomAttributes)
}

func setCustomAttributesWith(keysAndValues: [String: Any]) {
  // 2
  for (key, value) in keysAndValues {
  // 3
    if let value = value as? [String] {
      setCustomAttributeArrayWithKey(key, andValue: value)
    } else {
      setCustomAttributeWithKey(key, andValue: value)
    }
  }
}
- (void)logPendingCustomAttributesIfNecessary {
  RemoteStorage *remoteStorage = [[RemoteStorage alloc] initWithStorageType:StorageTypeSuite];
  NSArray *pendingAttributes = [remoteStorage retrieveForKey:RemoteStorageKeyPendingCustomAttributes];

  // 1
  for (NSDictionary<NSString*, id> *attribute in pendingAttributes) {
    [self setCustomAttributeWith:attribute];
  }

  // 4
  [remoteStorage removeObjectForKey:RemoteStorageKeyPendingCustomAttributes];
}

- (void)setCustomAttributeWith:(NSDictionary<NSString *, id> *)keysAndValues {
  // 2
  for (NSString *key in keysAndValues) {
  // 3
    [self setCustomAttributeWith:key andValue:[keysAndValues objectForKey:key]];
  }
}

Guardar atributos de usuario

Al guardar atributos de usuario, te recomendamos crear un objeto personalizado para descifrar qué tipo de atributo se está actualizando (email, first_name, phone_number, etc.). El objeto debe ser compatible con ser almacenado/recuperado de UserDefaults. Consulta el archivo auxiliar UserAttribute para ver un ejemplo de cómo lograr esto.

  1. Inicializa un objeto UserAttribute codificado con el tipo correspondiente
  2. Inicializa userDefaults para recuperar y almacenar los datos del evento
  3. Si ya existe un array, añade los nuevos datos al array existente y guárdalo
  4. Si no existe un array, guarda el nuevo array en userDefaults
func saveUserAttribute() {
  // 1
  guard let data = try? PropertyListEncoder().encode(UserAttribute.userAttributeType("USER-ATTRIBUTE-VALUE")) else { return }

  // 2
  let remoteStorage = RemoteStorage(storageType: .suite)

  // 3
  if var pendingAttributes = remoteStorage.retrieve(forKey: .pendingUserAttributes) as? [Data] {
    pendingAttributes.append(contentsOf: [data])
    remoteStorage.store(pendingAttributes, forKey: .pendingUserAttributes)
  } else {
  // 4
    remoteStorage.store([data], forKey: .pendingUserAttributes)
  }
}
- (void)saveUserAttribute {
  // 1
  UserAttribute *userAttribute = [[UserAttribute alloc] initWithUserField:@"USER-ATTRIBUTE-VALUE" attributeType:UserAttributeTypeEmail];

  NSError *error;
  NSData *data = [NSKeyedArchiver archivedDataWithRootObject:userAttribute requiringSecureCoding:YES error:&error];

  if (error != nil) {
    // log error
  }
  // 2
  RemoteStorage *remoteStorage = [[RemoteStorage alloc] initWithStorageType:StorageTypeSuite];
  NSMutableArray *pendingAttributes = [[remoteStorage retrieveForKey:RemoteStorageKeyPendingUserAttributes] mutableCopy];

  // 3
  if (pendingAttributes) {
    [pendingAttributes addObject:data];
    [remoteStorage store:pendingAttributes forKey:RemoteStorageKeyPendingUserAttributes];
  } else {
  // 4
    [remoteStorage store:@[data] forKey:RemoteStorageKeyPendingUserAttributes];
  }
}

Enviar atributos de usuario a Braze

El mejor momento para registrar cualquier análisis guardado desde una extensión de contenido de notificación es justo después de que se inicialice el SDK. Esto se puede hacer recorriendo en bucle los atributos pendientes, estableciendo el atributo personalizado correspondiente en Braze y luego borrando el almacenamiento para la próxima vez que se necesite esta función.

  1. Recorre en bucle el array de datos pendingAttributes
  2. Inicializa un objeto UserAttribute codificado a partir de los datos del atributo
  3. Establece el campo de usuario específico en función del tipo de atributo de usuario (correo electrónico)
  4. Elimina todos los atributos de usuario pendientes del almacenamiento
func logPendingUserAttributesIfNecessary() {
  let remoteStorage = RemoteStorage(storageType: .suite)
  guard let pendingAttributes = remoteStorage.retrieve(forKey: .pendingUserAttributes) as? [Data] else { return }

  // 1
  for attributeData in pendingAttributes {
  // 2
    guard let userAttribute = try? PropertyListDecoder().decode(UserAttribute.self, from: attributeData) else { continue }

  // 3
    switch userAttribute {
    case .email(let email):
      user?.email = email
    }
  }
  // 4
  remoteStorage.removeObject(forKey: .pendingUserAttributes)
}
- (void)logPendingUserAttributesIfNecessary {
  RemoteStorage *remoteStorage = [[RemoteStorage alloc] initWithStorageType:StorageTypeSuite];
  NSArray *pendingAttributes = [remoteStorage retrieveForKey:RemoteStorageKeyPendingUserAttributes];

  // 1
  for (NSData *attributeData in pendingAttributes) {
    NSError *error;

  // 2
    UserAttribute *userAttribute = [NSKeyedUnarchiver unarchivedObjectOfClass:[UserAttribute class] fromData:attributeData error:&error];

    if (error != nil) {
      // log error
    }

  // 3
    if (userAttribute) {
      switch (userAttribute.attributeType) {
        case UserAttributeTypeEmail:
          [self user].email = userAttribute.userField;
          break;
      }
    }
  }
  // 4
  [remoteStorage removeObjectForKey:RemoteStorageKeyPendingUserAttributes];
}

Archivos auxiliares

Archivo auxiliar RemoteStorage
enum RemoteStorageKey: String, CaseIterable {

  // MARK: - Notification Content Extension Analytics
  case pendingCustomEvents = "pending_custom_events"
  case pendingCustomAttributes = "pending_custom_attributes"
  case pendingUserAttributes = "pending_user_attributes"
}

enum RemoteStorageType {
  case standard
  case suite
}

class RemoteStorage: NSObject {
  private var storageType: RemoteStorageType = .standard
  private lazy var defaults: UserDefaults = {
    switch storageType {
    case .standard:
      return .standard
    case .suite:
      return UserDefaults(suiteName: "YOUR-DOMAIN-IDENTIFIER")!
    }
  }()

  init(storageType: RemoteStorageType = .standard) {
    self.storageType = storageType
  }

  func store(_ value: Any, forKey key: RemoteStorageKey) {
    defaults.set(value, forKey: key.rawValue)
  }

  func retrieve(forKey key: RemoteStorageKey) -> Any? {
    return defaults.object(forKey: key.rawValue)
  }

  func removeObject(forKey key: RemoteStorageKey) {
    defaults.removeObject(forKey: key.rawValue)
  }

  func resetStorageKeys() {
    for key in RemoteStorageKey.allCases {
      defaults.removeObject(forKey: key.rawValue)
    }
  }
}
@interface RemoteStorage ()

@property (nonatomic) StorageType storageType;
@property (nonatomic, strong) NSUserDefaults *defaults;

@end

@implementation RemoteStorage

- (id)initWithStorageType:(StorageType)storageType {
  if (self = [super init]) {
    self.storageType = storageType;
  }
  return self;
}

- (void)store:(id)value forKey:(RemoteStorageKey)key {
  [[self defaults] setValue:value forKey:[self rawValueForKey:key]];
}

- (id)retrieveForKey:(RemoteStorageKey)key {
  return [[self defaults] objectForKey:[self rawValueForKey:key]];
}

- (void)removeObjectForKey:(RemoteStorageKey)key {
  [[self defaults] removeObjectForKey:[self rawValueForKey:key]];
}

- (void)resetStorageKeys {
  [[self defaults] removeObjectForKey:[self rawValueForKey:RemoteStorageKeyPendingCustomEvents]];
  [[self defaults] removeObjectForKey:[self rawValueForKey:RemoteStorageKeyPendingCustomAttributes]];
  [[self defaults] removeObjectForKey:[self rawValueForKey:RemoteStorageKeyPendingUserAttributes]];
}

- (NSUserDefaults *)defaults {
  if (!self.defaults) {
    switch (self.storageType) {
      case StorageTypeStandard:
        return [NSUserDefaults standardUserDefaults];
        break;
      case StorageTypeSuite:
        return [[NSUserDefaults alloc] initWithSuiteName:@"YOUR-DOMAIN-IDENTIFIER"];
    }
  } else {
    return self.defaults;
  }
}

- (NSString*)rawValueForKey:(RemoteStorageKey)remoteStorageKey {
    switch(remoteStorageKey) {
    case RemoteStorageKeyPendingCustomEvents:
      return @"pending_custom_events";
    case RemoteStorageKeyPendingCustomAttributes:
      return @"pending_custom_attributes";
    case RemoteStorageKeyPendingUserAttributes:
      return @"pending_user_attributes";
    default:
      [NSException raise:NSGenericException format:@"Unexpected FormatType."];
  }
}
Archivo auxiliar UserAttribute
enum UserAttribute: Hashable {
  case email(String?)
}

// MARK: - Codable
extension UserAttribute: Codable {
  private enum CodingKeys: String, CodingKey {
    case email
  }

  func encode(to encoder: Encoder) throws {
    var values = encoder.container(keyedBy: CodingKeys.self)

    switch self {
    case .email(let email):
      try values.encode(email, forKey: .email)
    }
  }

  init(from decoder: Decoder) throws {
    let values = try decoder.container(keyedBy: CodingKeys.self)

    let email = try values.decode(String.self, forKey: .email)
    self = .email(email)
  }
}
@implementation UserAttribute

- (id)initWithUserField:(NSString *)userField attributeType:(UserAttributeType)attributeType {
  if (self = [super init]) {
    self.userField = userField;
    self.attributeType = attributeType;
  }
  return self;
}

- (void)encodeWithCoder:(NSCoder *)encoder {
  [encoder encodeObject:self.userField forKey:@"userField"];
  [encoder encodeInteger:self.attributeType forKey:@"attributeType"];
}

- (id)initWithCoder:(NSCoder *)decoder {
  if (self = [super init]) {
    self.userField = [decoder decodeObjectForKey:@"userField"];

    NSInteger attributeRawValue = [decoder decodeIntegerForKey:@"attributeType"];
    self.attributeType = (UserAttributeType) attributeRawValue;
  }
  return self;
}

@end
Archivo auxiliar EventName Dictionary
extension Dictionary where Key == String, Value == Any {
  init(eventName: String, properties: [String: Any]? = nil) {
    self.init()
    self[PushNotificationKey.eventName.rawValue] = eventName

    if let properties = properties {
      for (key, value) in properties {
        self[key] = value
      }
    }
  }
}
@implementation NSDictionary (Helper)

- (id)initWithEventName:(NSString *)eventName properties:(NSDictionary *)properties {
  self = [self init];
  if (self) {
    dict[@"event_name"] = eventName;

    for(id key in properties) {
      dict[key] = properties[key];
    }
  }
  return self;
}

@end


New Stuff!