Ir para o conteúdo



Guia de implementação de Content Cards

Este guia de implementação opcional e avançado aborda considerações sobre o código de Content Cards, três casos de uso personalizados criados por nossa equipe, trechos de código que os acompanham e orientações sobre o registro de impressões, cliques e descartes. Visite nosso repositório de demonstrações da Braze Repositório de Demonstrações da Braze no GitHub! Este guia de implementação está centrado em uma implementação Swift, mas são fornecidos trechos em Objective-C para os interessados.

Considerações sobre o código

Content Cards como objetos personalizados

Assim como um foguete que adiciona um propulsor, seus próprios objetos personalizados podem ser estendidos para funcionar como Content Cards. Superfícies de API limitadas como essa proporcionam flexibilidade para trabalhar com diferentes backends de dados de forma intercambiável. Isso pode ser feito conformando-se ao protocolo ContentCardable e implementando o inicializador (como visto nos trechos de código a seguir) e, por meio do uso do struct ContentCardData, permite que você acesse os dados do ABKContentCard. A carga útil do ABKContentCard será usada para inicializar o struct ContentCardData e o próprio objeto personalizado, tudo a partir de um tipo Dictionary por meio do inicializador que vem com o protocolo.

O inicializador também inclui um enum ContentCardClassType. Esse enum é usado para decidir qual objeto inicializar. Por meio do uso de pares chave-valor no dashboard da Braze, você pode definir uma chave class_type explícita que será usada para determinar qual objeto inicializar. Esses pares chave-valor para Content Cards estão disponíveis na variável extras do ABKContentCard. Outro componente central do inicializador é o parâmetro de dicionário metaData. O metaData inclui tudo do ABKContentCard, analisado em uma série de chaves e valores. Depois que os cartões relevantes são analisados e convertidos em seus objetos personalizados, o app está pronto para começar a trabalhar com eles como se tivessem sido instanciados a partir de JSON ou qualquer outra fonte.

Depois de ter uma compreensão sólida dessas considerações sobre o código, confira nossos casos de uso para começar a implementar seus objetos personalizados.

Protocolo ContentCardable
Um objeto ContentCardData que representa os dados do ABKContentCard junto com um enum ContentCardClassType. Um inicializador usado para instanciar objetos personalizados com metadados do ABKContentCard.

protocol ContentCardable {
  var contentCardData: ContentCardData? { get }
  init?(metaData: [ContentCardKey: Any], classType contentCardClassType: ContentCardClassType)
}

extension ContentCardable {
  var isContentCard: Bool {
    return contentCardData != nil
  }

  func logContentCardClicked() {
    BrazeManager.shared.logContentCardClicked(idString: contentCardData?.contentCardId)
  }

  func logContentCardDismissed() {
    BrazeManager.shared.logContentCardDismissed(idString: contentCardData?.contentCardId)
  }

  func logContentCardImpression() {
    BrazeManager.shared.logContentCardImpression(idString: contentCardData?.contentCardId)
  }
}

Struct de dados de Content Card
ContentCardData representa os valores analisados de um ABKContentCard.

struct ContentCardData: Hashable {
  let contentCardId: String
  let contentCardClassType: ContentCardClassType
  let createdAt: Double
  let isDismissable: Bool
  ...
  // other Content Card properties such as expiresAt, pinned, etc.
}

extension ContentCardData: Equatable {
  static func ==(lhs: ContentCardData, rhs: ContentCardData) -> Bool {
    return lhs.contentCardId == rhs.contentCardId
  }
}

Protocolo ContentCardable
Um objeto ContentCardData que representa os dados do ABKContentCard junto com um enum ContentCardClassType, um inicializador usado para instanciar objetos personalizados com metadados do ABKContentCard.

@protocol ContentCardable <NSObject>

@property (nonatomic, strong) ContentCardData *contentCardData;
- (instancetype __nullable)initWithMetaData:(NSDictionary *)metaData
                                  classType:(enum ContentCardClassType)classType;

- (BOOL)isContentCard;
- (void)logContentCardImpression;
- (void)logContentCardClicked;
- (void)logContentCardDismissed;

@end

Struct de dados de Content Card
ContentCardData representa os valores analisados de um ABKContentCard.

@interface ContentCardData : NSObject

+ (ContentCardClassType)contentCardClassTypeForString:(NSString *)rawValue;

- (instancetype)initWithIdString:(NSString *)idString
                       classType:(ContentCardClassType)classType
                       createdAt:(double)createdAt isDismissible:(BOOL)isDismissible;

@property (nonatomic, readonly) NSString *contentCardId;
@property (nonatomic) ContentCardClassType classType;
@property (nonatomic, readonly) double *createdAt;
@property (nonatomic, readonly) BOOL isDismissible;
...
// other Content Card properties such as expiresAt, pinned, etc.

@end

Inicializador de objeto personalizado
Os metadados de um ABKContentCard são usados para preencher as variáveis do seu objeto. Os pares chave-valor configurados no dashboard da Braze são representados no dicionário “extras”.

extension CustomObject: ContentCardable {
  init?(metaData: [ContentCardKey: Any], classType contentCardClassType: ContentCardClassType) {
    guard let idString = metaData[.idString] as? String,
      let createdAt = metaData[.created] as? Double,
      let isDismissable = metaData[.dismissable] as? Bool,
      let extras = metaData[.extras] as? [AnyHashable: Any],
      else { return nil }

    let contentCardData = ContentCardData(contentCardId: idString, contentCardClassType: contentCardClassType, createdAt: createdAt, isDismissable: isDismissable)
    let customObjectProperty = extras["YOUR-CUSTOM-OBJECT-PROPERTY"] as? String

    self.init(contentCardData: contentCardData, property: customObjectProperty)
  }
}

Identificando tipos
O enum ContentCardClassType representa o valor de class_type no dashboard da Braze. Esse valor também é usado como identificador de filtro para exibir Content Cards em diferentes locais.

enum ContentCardClassType: Hashable {
  case yourValue
  case yourOtherValue
  ...
  case none

  init(rawType: String?) {
    switch rawType?.lowercased() {
    case "your_value": // these values much match the value set in the Braze dashboard
      self = .yourValue
    case "your_other_value": // these values much match the value set in the Braze dashboard
      self = .yourOtherValue
    ...
    default:
      self = .none
    }
  }
}

Inicializador de objeto personalizado
Os metadados de um ABKContentCard são usados para preencher as variáveis do seu objeto. Os pares chave-valor configurados no dashboard da Braze são representados no dicionário “extras”.

- (id _Nullable)initWithMetaData:(nonnull NSDictionary *)metaData classType:(enum ContentCardClassType)classType {
  self = [super init];
  if (self) {
    if ([metaData objectForKey:ContentCardKeyIdString] && [metaData objectForKey:ContentCardKeyCreated] && [metaData objectForKey:ContentCardKeyDismissible] && [metaData objectForKey:ContentCardKeyExtras]) {
      NSDictionary  *extras = metaData[ContentCardKeyExtras];
      NSString *idString = metaData[ContentCardKeyIdString];
      double createdAt = [metaData[ContentCardKeyCreated] doubleValue];
      BOOL isDismissible = metaData[ContentCardKeyDismissible];

      if ([extras objectForKey: @"YOUR-CUSTOM-PROPERTY")
        _customObjectProperty = extras[@"YOUR-CUSTOM-OBJECT-PROPERTY"];

      self.contentCardData = [[ContentCardData alloc] initWithIdString:idString classType:classType createdAt:createdAt isDismissible:isDismissible];

      return self;
    }
  }
  return nil;
}

Identificando tipos
O enum ContentCardClassType representa o valor de class_type no dashboard da Braze. Esse valor também é usado como identificador de filtro para exibir Content Cards em diferentes locais.

typedef NS_ENUM(NSInteger, ContentCardClassType) {
  ContentCardClassTypeNone = 0,
  ContentCardClassTypeYourValue,
  ContentCardClassTypeYourOtherValue,
  ...
};

+ (NSArray *)contentCardClassTypeArray {
  return @[ @"", @"your_value", @"your_other_value" ];
}

+ (ContentCardClassType)contentCardClassTypeForString:(NSString*)rawValue {
  if ([[self contentCardClassTypeArray] indexOfObject:rawValue] == NSNotFound) {
    return ContentCardClassTypeNone;
  } else {
    NSInteger value = [[self contentCardClassTypeArray] indexOfObject:rawValue];
    return (ContentCardClassType) value;
  }
}

Solicitando Content Cards
Enquanto o observador ainda estiver retido na memória, o retorno de chamada de notificação do SDK da Braze pode ser esperado.

func loadContentCards() {
  BrazeManager.shared.addObserverForContentCards(observer: self, selector: #selector(contentCardsUpdated))
  BrazeManager.shared.requestContentCardsRefresh()
}

Tratando o retorno de chamada do SDK de Content Cards
Encaminhe o retorno de chamada de notificação para o arquivo auxiliar para analisar os dados da carga útil para seu(s) objeto(s) personalizado(s).

@objc func contentCardsUpdated(_ notification: Notification) {
  guard let contentCards = BrazeManager.shared.handleContentCardsUpdated(notification, for: [.yourValue]) as? [CustomObject],!contentCards.isEmpty else { return }

 // do something with your array of custom objects
}

Trabalhando com Content Cards
O class_type é passado como filtro para retornar apenas Content Cards que tenham um class_type correspondente.

func handleContentCardsUpdated(_ notification: Notification, for classTypes: [ContentCardClassType]) -> [ContentCardable] {
  guard let updateIsSuccessful = notification.userInfo?[ABKContentCardsProcessedIsSuccessfulKey] as? Bool, updateIsSuccessful, let cards = contentCards else { return [] }

  return convertContentCards(cards, for: classTypes)
}

Solicitando Content Cards
Enquanto o observador ainda estiver retido na memória, o retorno de chamada de notificação do SDK da Braze pode ser esperado.

- (void)loadContentCards {
  [[BrazeManager shared] addObserverForContentCards:self selector:@selector(contentCardsUpdated:)];
  [[BrazeManager shared] requestContentCardsRefresh];
}

Tratando o retorno de chamada do SDK de Content Cards
Encaminhe o retorno de chamada de notificação para o arquivo auxiliar para analisar os dados da carga útil para seu(s) objeto(s) personalizado(s).

- (void)contentCardsUpdated:(NSNotification *)notification {
  NSArray *classTypes = @[@(ContentCardClassTypeYourValue)];
  NSArray *contentCards = [[BrazeManager shared] handleContentCardsUpdated:notification forClassTypes:classTypes];

  // do something with your array of custom objects
}

Trabalhando com Content Cards
O class_type é passado como filtro para retornar apenas Content Cards que tenham um class_type correspondente.

- (NSArray *)handleContentCardsUpdated:(NSNotification *)notification forClassType:(ContentCardClassType)classType {
  BOOL updateIsSuccessful = [notification.userInfo[ABKContentCardsProcessedIsSuccessfulKey] boolValue];
  if (updateIsSuccessful) {
    return [self convertContentCards:self.contentCards forClassType:classType];
  } else {
    return @[];
  }
}

Trabalhando com dados da carga útil
Percorre o array de Content Cards e analisa apenas os cartões com um class_type correspondente. A carga útil de um ABKContentCard é analisada em um Dictionary.

func convertContentCards(_ cards: [ABKContentCard], for classTypes: [ContentCardClassType]) -> [ContentCardable] {
  var contentCardables: [ContentCardable] = []

  for card in cards {
    let classTypeString = card.extras?[ContentCardKey.classType.rawValue] as? String
    let classType = ContentCardClassType(rawType: classTypeString)
    guard classTypes.contains(classType) else { continue }

    var metaData: [ContentCardKey: Any] = [:]
    switch card {
    case let banner as ABKBannerContentCard:
      metaData[.image] = banner.image
    case let captioned as ABKCaptionedImageContentCard:
      metaData[.title] = captioned.title
      metaData[.cardDescription] = captioned.cardDescription
      metaData[.image] = captioned.image
    case let classic as ABKClassicContentCard:
      metaData[.title] = classic.title
      metaData[.cardDescription] = classic.cardDescription
    default:
      break
    }

    metaData[.idString] = card.idString
    metaData[.created] = card.created
    metaData[.dismissible] = card.dismissible
    metaData[.urlString] = card.urlString
    metaData[.extras] = card.extras
    ...
    // other Content Card properties such as expiresAt, pinned, etc.

    if let contentCardable = contentCardable(with: metaData, for: classType) {
      contentCardables.append(contentCardable)
    }
  }
  return contentCardables
}

Inicializando seus objetos personalizados a partir dos dados da carga útil de Content Card
O class_type é usado para determinar qual dos seus objetos personalizados será inicializado a partir dos dados da carga útil.

func contentCardable(with metaData: [ContentCardKey: Any], for classType: ContentCardClassType) -> ContentCardable? {
  switch classType {
  case .yourValue:
    return CustomObject(metaData: metaData, classType: classType)
  case .yourOtherValue:
    return OtherCustomObject(metaData: metaData, classType: classType)
  ...
  default:
    return nil
  }
}

Trabalhando com dados da carga útil
Percorre o array de Content Cards e analisa apenas os cartões com um class_type correspondente. A carga útil de um ABKContentCard é analisada em um Dictionary.

- (NSArray *)convertContentCards:(NSArray<ABKContentCard*> *)cards forClassType:(ContentCardClassType)classType {
  NSMutableArray *contentCardables = [[NSMutableArray alloc] init];      for (ABKContentCard *card in cards) {
    NSString *classTypeString = [card.extras objectForKey:ContentCardKeyClassType];
    ContentCardClassType cardClassType = [ContentCardData contentCardClassTypeForString: classTypeString];
    if (cardClassType != classType) { continue; }

    NSMutableDictionary *metaData = [[NSMutableDictionary alloc] init];
    if ([card isKindOfClass:[ABKBannerContentCard class]]) {
      ABKBannerContentCard *banner = (ABKBannerContentCard *)card;
      metaData[ContentCardKeyImage] = banner.image;
    } else if ([card isKindOfClass:[ABKCaptionedImageContentCard class]]) {
      ABKCaptionedImageContentCard *captioned = (ABKCaptionedImageContentCard *)card;
      metaData[ContentCardKeyTitle] = captioned.title;
      metaData[ContentCardKeyCardDescription] = captioned.cardDescription;
      metaData[ContentCardKeyImage] = captioned.image;
    } else if ([card isKindOfClass:[ABKClassicContentCard class]]) {
      ABKClassicContentCard *classic = (ABKClassicContentCard *)card;
      metaData[ContentCardKeyCardDescription] = classic.title;
      metaData[ContentCardKeyImage] = classic.image;
    }

    metaData[ContentCardKeyIdString] = card.idString;
    metaData[ContentCardKeyCreated] = [NSNumber numberWithDouble:card.created];
    metaData[ContentCardKeyDismissible] = [NSNumber numberWithBool:card.dismissible];
    metaData[ContentCardKeyUrlString] = card.urlString;
    metaData[ContentCardKeyExtras] = card.extras;
    ...
    // other Content Card properties such as expiresAt, pinned, etc.

    id<ContentCardable> contentCardable = [self contentCardableWithMetaData:metaData forClassType:classType];
    if (contentCardable) {
      [contentCardables addObject:contentCardable];
    }
  }

  return contentCardables;
}

Inicializando seus objetos personalizados a partir dos dados da carga útil de Content Card
O class_type é usado para determinar qual dos seus objetos personalizados será inicializado a partir dos dados da carga útil.

- (id<ContentCardable>)contentCardableWithMetaData:(NSDictionary *)metaData forClassType:(ContentCardClassType)classType {
  switch (classType) {
    case ContentCardClassTypeYourValue:
      return [[CustomObject alloc] initWithMetaData:metaData classType:classType];
    case ContentCardClassTypeYourOtherValue:
      return nil;
    ...
    default:
      return nil;
  }
}

Casos de uso

Fornecemos três casos de uso na seção a seguir. Cada caso de uso oferece uma explicação detalhada, trechos de código relevantes e uma visão de como as variáveis de Content Cards podem parecer e ser usadas no dashboard da Braze:

Content Cards como conteúdo suplementar

Feed com uma lista híbrida combinando dados locais e Content Cards da Braze.

Você pode combinar Content Cards em um feed existente de forma integrada, permitindo que dados de múltiplos feeds sejam carregados simultaneamente. Isso cria uma experiência coesa e harmoniosa com Content Cards da Braze e conteúdo de feed existente.

O exemplo a seguir mostra uma UICollectionView com uma lista híbrida de itens que são preenchidos por dados locais e Content Cards fornecidos pela Braze. Com isso, os Content Cards podem ser indistinguíveis em relação ao conteúdo existente.

Configuração do dashboard

Este Content Card é entregue por uma campanha disparada por API com pares chave-valor disparados por API. Isso é ideal para campanhas em que os valores do cartão dependem de fatores externos para determinar qual conteúdo exibir ao usuário. Observe que class_type deve ser conhecido no momento da configuração.

Os pares chave-valor para o caso de uso de Content Cards suplementares. Neste exemplo, diferentes aspectos do cartão, como "tile_id", "tile_deeplink" e "tile_title", são definidos usando Liquid.

Pronto para registrar análise de dados?

Acesse a seção a seguir para entender melhor como o fluxo de dados deve funcionar.

Content Cards em um centro de mensagens


Content Cards podem ser usados em formato de centro de mensagens, onde cada mensagem é seu próprio cartão. Cada mensagem no centro de mensagens é preenchida por uma carga útil de Content Card, e cada cartão contém pares chave-valor adicionais que controlam a UI/UX ao clicar. No exemplo a seguir, uma mensagem direciona você para uma visualização personalizada arbitrária, enquanto outra abre uma webview que exibe HTML personalizado.

Centro de mensagens com Content Cards e cartões de mensagem individuais.

Configuração do dashboard

Para os tipos de mensagem a seguir, o par chave-valor class_type deve ser adicionado à configuração do seu dashboard. Os valores atribuídos aqui são arbitrários, mas devem ser distinguíveis entre os tipos de classe. Esses pares chave-valor são os identificadores-chave que o aplicativo analisa ao decidir para onde ir quando o usuário clica em uma mensagem resumida da caixa de entrada.

Os pares chave-valor para este caso de uso incluem:

  • message_header definido como Full Page
  • class_type definido como message_full_page

Exemplo de mensagem de Content Card em página inteira.

Os pares chave-valor para este caso de uso incluem:

  • message_header definido como HTML
  • class_type definido como message_webview
  • message_title

Esta mensagem também procura um par chave-valor HTML, mas se você estiver trabalhando com um domínio web, um par chave-valor de URL também é válido.

Content Card abrindo uma webview HTML a partir de um par chave-valor.

Explicação adicional

A lógica do centro de mensagens é orientada pelo contentCardClassType que é fornecido pelos pares chave-valor da Braze. Usando o método addContentCardToView, você pode tanto filtrar quanto identificar esses tipos de classe.

Usando class_type para comportamento ao clicar
Quando uma mensagem é clicada, o ContentCardClassType determina como a próxima tela deve ser preenchida.

func addContentCardToView(with message: Message) {
    switch message.contentCardData?.contentCardClassType {
      case .message(.fullPage):
        loadContentCardFullPageView(with: message as! FullPageMessage)
      case .message(.webView):
        loadContentCardWebView(with: message as! WebViewMessage)
      default:
        break
    }
}

Usando class_type para comportamento ao clicar
Quando uma mensagem é clicada, o ContentCardClassType determina como a próxima tela deve ser preenchida.

- (void)addContentCardToView:(Message *)message {
  switch (message.contentCardData.classType) {
    case ContentCardClassTypeMessageFullPage:
      [self loadContentCardFullPageView:(FullPageMessage *)message];
      break;
    case ContentCardClassTypeMessageWebview:
      [self loadContentCardWebView:(WebViewMessage *)message];
      break;
    default:
      break;
  }
}
Pronto para registrar análise de dados?

Acesse a seção a seguir para entender melhor como o fluxo de dados deve funcionar.

Um Content Card interativo mostrando uma promoção de 50% aparecendo no canto inferior esquerdo da tela. Após ser clicado, uma promoção será aplicada ao carrinho.

Content Cards interativos


Content Cards podem ser usados para criar experiências dinâmicas e interativas para seus usuários. No exemplo a seguir, um pop-up de Content Card aparece no checkout para oferecer aos usuários promoções de última hora.

Cartões bem posicionados como este são uma ótima maneira de dar aos usuários um “empurrãozinho” em direção a ações específicas.


Configuração do dashboard

A configuração do dashboard para Content Cards interativos é simples. Os pares chave-valor para este caso de uso incluem um discount_percentage definido como o valor de desconto desejado e class_type definido como coupon_code. Esses pares chave-valor são como Content Cards de tipos específicos são filtrados e exibidos na tela de checkout.

Content Card interativo mostrando uma promoção de checkout.

Pronto para registrar análise de dados?

Acesse a seção a seguir para entender melhor como o fluxo de dados deve funcionar.

Personalização do modo escuro

Por padrão, as visualizações de Content Cards responderão automaticamente às alterações do modo escuro no dispositivo com um conjunto de cores temáticas.

Esse comportamento pode ser substituído conforme detalhado em nosso guia de estilização personalizada.

Registro de impressões, cliques e dispensas

Após estender seus objetos personalizados para funcionar como Content Cards, registrar métricas valiosas como impressões, cliques e dispensas é rápido. Isso pode ser feito usando um protocolo ContentCardable que referencia e fornece dados a um arquivo auxiliar para ser registrado pelo SDK da Braze.

Componentes de implementação

Registro de análise de dados
Os métodos de registro podem ser chamados diretamente a partir de objetos que estejam em conformidade com o protocolo ContentCardable.

customObject.logContentCardImpression()
customObject.logContentCardClicked()
customObject.logContentCardDismissed()

Recuperando o ABKContentCard
O idString passado pelo seu objeto personalizado é usado para identificar o Content Card associado para registrar análise de dados.

extension BrazeManager {
  func logContentCardImpression(idString: String?) {
    guard let contentCard = getContentCard(forString: idString) else { return }

    contentCard.logContentCardImpression()
  }

  private func getContentCard(forString idString: String?) -> ABKContentCard? {
    return contentCards?.first(where: { $0.idString == idString })
  }
}

Registro de análise de dados
Os métodos de registro podem ser chamados diretamente a partir de objetos que estejam em conformidade com o protocolo ContentCardable.

[customObject logContentCardImpression];
[customObject logContentCardClicked];
[customObject logContentCardDismissed];

Recuperando o ABKContentCard
O idString passado pelo seu objeto personalizado é usado para identificar o Content Card associado para registrar análise de dados.

- (void)logContentCardImpression:(NSString *)idString {
  ABKContentCard *contentCard = [self getContentCard:idString];
  [contentCard logContentCardImpression];
}

- (ABKContentCard *)getContentCard:(NSString *)idString {
  NSPredicate *predicate = [NSPredicate predicateWithFormat:@"self.idString == %@", idString];
  NSArray *filteredArray = [self.contentCards filteredArrayUsingPredicate:predicate];

  return filteredArray.firstObject;
}

Arquivos auxiliares

Arquivo auxiliar ContentCardKey
enum ContentCardKey: String {
  case idString
  case created
  case classType = "class_type"
  case dismissible
  case extras
  ...
}
static NSString *const ContentCardKeyIdString = @"idString";
static NSString *const ContentCardKeyCreated = @"created";
static NSString *const ContentCardKeyClassType = @"class_type";
static NSString *const ContentCardKeyDismissible = @"dismissible";
static NSString *const ContentCardKeyExtras = @"extras";
...
New Stuff!