Skip to content

Enregistrer les données des notifications push

Découvrez comment enregistrer les données des notifications push via le SDK de Braze.

Enregistrement des données avec l’API de Braze (recommandé)

Vous pouvez enregistrer des analyses/analytiques en temps réel en effectuant des appels vers l’endpoint/users/track . Pour l’analyse/analytique, envoyez la valeur braze_id depuis le tableau de bord de Braze afin d’identifier le profil utilisateur à mettre à jour.

Exemple de tableau de bord personnalisé

Enregistrement manuel des données

En fonction des détails de votre charge utile, vous pouvez enregistrer les analyses/analytiques manuellement dans votre implémentation FirebaseMessagingService.onMessageReceived ou dans votre activité de démarrage. N’oubliez pas que votre sous-classe FirebaseMessagingService doit terminer son exécution dans les 9 secondes suivant l’invocation pour éviter d’être signalée ou interrompue par le système Android.

Vous pouvez enregistrer des analyses en temps réel à l’aide de l’endpoint /users/track de l’API Braze. Pour enregistrer les analyses, envoyez la valeur braze_id dans le champ des paires clé-valeur (comme illustré dans la capture d’écran suivante) afin d’identifier le profil utilisateur à mettre à jour.

Une notification push avec trois ensembles de paires clé-valeur. 1. « Braze_id » défini comme un appel Liquid pour récupérer l'ID Braze. 2. « cert_title » défini comme « Braze Marketer Certification ». 3. « Cert_description » défini comme « Certified Braze marketers drive... ».

Enregistrement manuel des données

L’enregistrement manuel nécessite de configurer au préalable des espaces de travail dans Xcode, puis de créer, enregistrer et récupérer les analyses. Cela implique un travail de développement personnalisé de votre côté. Les extraits de code suivants vous aideront à résoudre ce problème.

Il est important de noter que les analyses ne sont pas envoyées à Braze tant que l’application mobile n’a pas été relancée. Cela signifie que, selon vos paramètres de rejet, il existe souvent une période indéterminée entre le moment où une notification push est rejetée, le lancement de l’application mobile et la récupération des analyses. Même si cette période tampon n’affecte pas tous les cas d’usage, vous devez prendre en compte cet impact et ajuster votre parcours utilisateur si nécessaire afin d’inclure l’ouverture de l’application pour répondre à ce problème.

Un graphique décrivant la manière dont les analyses sont traitées par Braze. 1. Les données d'analyse sont créées. 2. Les données d'analyse sont enregistrées. 3. La notification push est rejetée. 4. Période indéterminée entre le moment où la notification push est rejetée et le lancement de l'application mobile. 5. L'application mobile est lancée. 6. Les données d'analyse sont reçues. 7. Les données d'analyse sont envoyées à Braze.

Étape 1 : Configurer les groupes d’applications dans Xcode

Dans Xcode, ajoutez la capacité App Groups. Si vous n’avez pas d’espace de travail dans votre application, accédez à la capacité de la cible d’application principale, activez App Groups, puis cliquez sur le bouton + Ajouter. Ensuite, utilisez l’ID du bundle de votre application pour créer l’espace de travail. Par exemple, si l’ID du bundle de votre application est com.company.appname, vous pouvez nommer votre espace de travail group.com.company.appname.xyz. Assurez-vous que les App Groups sont activés à la fois pour la cible de votre application principale et la cible de l’extension de contenu.

Écran Signing and Capabilities de Xcode avec App Groups activé pour la cible de l'application principale et celle de l'extension.

Étape 2 : Intégrer les extraits de code

Les extraits de code suivants constituent une référence utile sur la façon d’enregistrer et d’envoyer des événements personnalisés, des attributs personnalisés et des attributs utilisateur. Ce guide s’exprime en termes de UserDefaults, mais le code sera représenté sous la forme du fichier d’aide RemoteStorage. Il existe des fichiers d’aide supplémentaires, UserAttributes et EventName Dictionary, qui sont utilisés pour envoyer et enregistrer les attributs utilisateur.

Enregistrement des événements personnalisés

Pour enregistrer des événements personnalisés, vous devez créer les analyses à partir de zéro. Pour ce faire, créez un dictionnaire, renseignez-le avec des métadonnées et enregistrez les données via un fichier d’aide.

  1. Initialiser un dictionnaire avec les métadonnées de l’événement
  2. Initialiser userDefaults pour récupérer et stocker les données d’événements
  3. Si un tableau existe déjà, ajouter les nouvelles données au tableau existant et enregistrer
  4. S’il n’y a pas de tableau existant, enregistrer le nouveau tableau dans userDefaults
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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)
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
- (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];
  }
}

Envoi d’événements personnalisés à Braze

Le meilleur moment pour enregistrer les analyses sauvegardées à partir d’une extension d’application de contenu de notification est juste après l’initialisation du SDK. Pour ce faire, parcourez en boucle tous les événements en attente, vérifiez la présence de la clé « Event Name », définissez les valeurs appropriées dans Braze, puis effacez le stockage pour la prochaine utilisation de cette fonction.

  1. Parcourir le tableau des événements en attente
  2. Parcourir chaque paire clé-valeur dans le dictionnaire pendingEvents
  3. Vérifier explicitement la clé « Event Name » afin de définir la valeur en conséquence
  4. Toutes les autres paires clé-valeur seront ajoutées au dictionnaire properties
  5. Enregistrer chaque événement personnalisé individuellement
  6. Supprimer tous les événements en attente du stockage
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
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)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
- (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];
}

Enregistrement des attributs personnalisés

Pour enregistrer des attributs personnalisés, vous devez créer les analyses à partir de zéro. Pour ce faire, créez un dictionnaire, renseignez-le avec des métadonnées et enregistrez les données via un fichier d’aide.

  1. Initialiser un dictionnaire avec les métadonnées d’attribut
  2. Initialiser userDefaults pour récupérer et stocker les données d’attributs
  3. Si un tableau existe déjà, ajouter les nouvelles données au tableau existant et enregistrer
  4. S’il n’y a pas de tableau existant, enregistrer le nouveau tableau dans userDefaults
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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)
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
- (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];
  }
}

Envoi d’attributs personnalisés à Braze

Le meilleur moment pour enregistrer les analyses sauvegardées à partir d’une extension d’application de contenu de notification est juste après l’initialisation du SDK. Pour ce faire, parcourez les attributs en attente, définissez l’attribut personnalisé approprié dans Braze, puis effacez le stockage pour la prochaine utilisation de cette fonction.

  1. Parcourir le tableau des attributs en attente
  2. Parcourir chaque paire clé-valeur dans le dictionnaire pendingAttributes
  3. Enregistrer les attributs personnalisés individuels avec la clé et la valeur correspondantes
  4. Supprimer tous les attributs en attente du stockage
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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)
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
- (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]];
  }
}

Enregistrement des attributs utilisateur

Lorsque vous enregistrez des attributs utilisateur, nous vous recommandons de créer un objet personnalisé pour déterminer le type d’attribut mis à jour (email, first_name, phone_number, etc.). L’objet doit être compatible avec le stockage et la récupération depuis UserDefaults. Consultez le fichier d’aide UserAttribute pour un exemple de mise en œuvre.

  1. Initialiser un objet UserAttribute encodé avec le type correspondant
  2. Initialiser userDefaults pour récupérer et stocker les données d’événements
  3. Si un tableau existe déjà, ajouter les nouvelles données au tableau existant et enregistrer
  4. S’il n’y a pas de tableau existant, enregistrer le nouveau tableau dans userDefaults
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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)
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
- (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];
  }
}

Envoi d’attributs utilisateur à Braze

Le meilleur moment pour enregistrer les analyses sauvegardées à partir d’une extension d’application de contenu de notification est juste après l’initialisation du SDK. Pour ce faire, parcourez les attributs en attente, définissez l’attribut personnalisé approprié dans Braze, puis effacez le stockage pour la prochaine utilisation de cette fonction.

  1. Parcourir le tableau de données pendingAttributes
  2. Initialiser un objet UserAttribute encodé à partir des données d’attribut
  3. Définir le champ utilisateur spécifique en fonction du type d’attribut utilisateur (e-mail)
  4. Supprimer tous les attributs utilisateur en attente du stockage
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
- (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];
}

Fichiers d’aide

RemoteStorage Helper File
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
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)
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
@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."];
  }
}
UserAttribute Helper File
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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)
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@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
EventName Dictionary Helper File
1
2
3
4
5
6
7
8
9
10
11
12
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
      }
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@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!