Personalização do feed padrão do cartão de conteúdo
Um feed de cartão de conteúdo é a sequência de cartões de conteúdo em seus aplicativos móveis ou da Web. Este artigo aborda a configuração de quando o feed é atualizado, a ordem dos cartões, o gerenciamento de vários feeds e as mensagens de erro de “feed vazio”. Para obter uma visão geral básica dos tipos de opções de personalização que você tem com os cartões de conteúdo, consulte Visão geral da personalização.
Atualizar o feed
Por padrão, o feed do cartão de conteúdo será atualizado automaticamente nas seguintes instâncias:
- Uma nova sessão é iniciada
- Quando o feed é aberto e mais de 60 segundos se passaram desde a última atualização
Você também pode configurar o SDK para atualizar manualmente em horários específicos.
Para mostrar dinamicamente os cartões de conteúdo atualizados sem atualizar manualmente, selecione Na primeira impressão durante a criação do cartão. Esses cartões serão atualizados quando estiverem disponíveis.
Solicite uma atualização manual dos cartões de conteúdo Braze a partir do SDK do Android a qualquer momento, chamando requestContentCardsRefresh
.
1
Braze.getInstance(context).requestContentCardsRefresh();
1
Braze.getInstance(context).requestContentCardsRefresh()
Solicite uma atualização manual dos cartões de conteúdo da Braze a partir do SDK Swift a qualquer momento, chamando o método requestRefresh
na Braze.ContentCards
classe:
No Swift, os cartões de conteúdo podem ser atualizados com um manipulador de conclusão opcional ou com um retorno assíncrono usando as APIs de simultaneidade nativas do Swift.
Manipulador de conclusão
1
2
3
AppDelegate.braze?.contentCards.requestRefresh { result in
// Implement completion handler
}
Async/Await
1
let contentCards = await AppDelegate.braze?.contentCards.requestRefresh()
1
2
3
[AppDelegate.braze.contentCards requestRefreshWithCompletion:^(NSArray<BRZContentCardRaw *> * contentCards, NSError * error) {
// Implement completion handler
}];
Solicite uma atualização manual dos cartões de conteúdo da Braze a partir do SDK para Web a qualquer momento, chamando requestContentCardsRefresh()
.
Você também pode chamar getCachedContentCards
para obter todos os cartões disponíveis no momento a partir da última atualização dos cartões de conteúdo.
1
2
3
4
5
import * as braze from "@braze/web-sdk";
function refresh() {
braze.requestContentCardsRefresh();
}
Você pode fazer até cinco chamadas em rápida sucessão. Depois disso, uma nova chamada estará disponível a cada 180 segundos. O sistema armazenará até cinco chamadas para serem usadas a qualquer momento.
Personalização da ordem dos cartões exibidos
Você pode alterar a ordem em que seus cartões de conteúdo são exibidos. Isso permite ajustar a experiência do usuário, priorizando determinados tipos de conteúdo, como promoções sensíveis ao tempo.
O ContentCardsFragment
se baseia em um IContentCardsUpdateHandler
para lidar com qualquer classificação ou modificação dos cartões de conteúdo antes que eles sejam exibidos no feed. Um manipulador de atualização personalizado pode ser definido por meio de setContentCardUpdateHandler
em seu site ContentCardsFragment
.
O seguinte é o padrão IContentCardsUpdateHandler
e pode ser usado como ponto de partida para a personalização:
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
61
62
63
public class DefaultContentCardsUpdateHandler implements IContentCardsUpdateHandler {
// Interface that must be implemented and provided as a public CREATOR
// field that generates instances of your Parcelable class from a Parcel.
public static final Parcelable.Creator<DefaultContentCardsUpdateHandler> CREATOR = new Parcelable.Creator<DefaultContentCardsUpdateHandler>() {
public DefaultContentCardsUpdateHandler createFromParcel(Parcel in) {
return new DefaultContentCardsUpdateHandler();
}
public DefaultContentCardsUpdateHandler[] newArray(int size) {
return new DefaultContentCardsUpdateHandler[size];
}
};
@Override
public List<Card> handleCardUpdate(ContentCardsUpdatedEvent event) {
List<Card> sortedCards = event.getAllCards();
// Sort by pinned, then by the 'updated' timestamp descending
// Pinned before non-pinned
Collections.sort(sortedCards, new Comparator<Card>() {
@Override
public int compare(Card cardA, Card cardB) {
// A displays above B
if (cardA.getIsPinned() && !cardB.getIsPinned()) {
return -1;
}
// B displays above A
if (!cardA.getIsPinned() && cardB.getIsPinned()) {
return 1;
}
// At this point, both A & B are pinned or both A & B are non-pinned
// A displays above B since A is newer
if (cardA.getUpdated() > cardB.getUpdated()) {
return -1;
}
// B displays above A since A is newer
if (cardA.getUpdated() < cardB.getUpdated()) {
return 1;
}
// At this point, every sortable field matches so keep the natural ordering
return 0;
}
});
return sortedCards;
}
// Parcelable interface method
@Override
public int describeContents() {
return 0;
}
// Parcelable interface method
@Override
public void writeToParcel(Parcel dest, int flags) {
// No state is kept in this class so the parcel is left unmodified
}
}
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
class DefaultContentCardsUpdateHandler : IContentCardsUpdateHandler {
override fun handleCardUpdate(event: ContentCardsUpdatedEvent): List<Card> {
val sortedCards = event.allCards
// Sort by pinned, then by the 'updated' timestamp descending
// Pinned before non-pinned
sortedCards.sortWith(Comparator sort@{ cardA: Card, cardB: Card ->
// A displays above B
if (cardA.isPinned && !cardB.isPinned) {
return@sort -1
}
// B displays above A
if (!cardA.isPinned && cardB.isPinned) {
return@sort 1
}
// At this point, both A & B are pinned or both A & B are non-pinned
// A displays above B since A is newer
if (cardA.updated > cardB.updated) {
return@sort -1
}
// B displays above A since A is newer
if (cardA.updated < cardB.updated) {
return@sort 1
}
0
})
return sortedCards
}
// Parcelable interface method
override fun describeContents(): Int {
return 0
}
// Parcelable interface method
override fun writeToParcel(dest: Parcel, flags: Int) {
// No state is kept in this class so the parcel is left unmodified
}
companion object {
// Interface that must be implemented and provided as a public CREATOR
// field that generates instances of your Parcelable class from a Parcel.
val CREATOR: Parcelable.Creator<DefaultContentCardsUpdateHandler?> = object : Parcelable.Creator<DefaultContentCardsUpdateHandler?> {
override fun createFromParcel(`in`: Parcel): DefaultContentCardsUpdateHandler? {
return DefaultContentCardsUpdateHandler()
}
override fun newArray(size: Int): Array<DefaultContentCardsUpdateHandler?> {
return arrayOfNulls(size)
}
}
}
}
O código-fonte do ContentCardsFragment
pode ser encontrado no GitHub.
Para filtrar e classificar os cartões de conteúdo no Jetpack Compose, defina o parâmetro cardUpdateHandler
. Por exemplo:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
ContentCardsList(
cardUpdateHandler = {
it.sortedWith { cardA, cardB ->
// A displays above B
if (cardA.isPinned && !cardB.isPinned) {
return@sortedWith -1
}
// B displays above A
if (!cardA.isPinned && cardB.isPinned) {
return@sortedWith 1
}
// At this point, both A & B are pinned or both A & B are non-pinned
// A displays above B since A is newer
if (cardA.updated > cardB.updated) {
return@sortedWith -1
}
// B displays above A since A is newer
if (cardA.updated < cardB.updated) {
return@sortedWith 1
}
0
}
}
)
Personalize a ordem de alimentação do cartão modificando diretamente a variável estática Attributes.defaults
.
1
2
3
4
5
6
7
8
9
10
11
12
13
var attributes = BrazeContentCardUI.ViewController.Attributes.defaults
attributes.transform = { cards in
cards.sorted {
if $0.pinned && !$1.pinned {
return true
} else if !$0.pinned && $1.pinned {
return false
} else {
return $0.createdAt > $1.createdAt
}
}
}
let viewController = BrazeContentCardUI.ViewController(braze: AppDelegate.braze, attributes: attributes)
A personalização via BrazeContentCardUI.ViewController.Attributes
não está disponível em Objective C.
Personalize a ordem de exibição dos cartões de conteúdo em seu feed usando o param de filterFunction
showContentCards():
. Por exemplo:
1
2
3
braze.showContentCards(null, (cards) => {
return sortBrazeCards(cards); // Where sortBrazeCards is your sorting function that returns the sorted card array
});
Personalização da mensagem “feed vazio
Quando um usuário não se qualifica para nenhum cartão de conteúdo, o SDK exibe uma mensagem de erro de “feed vazio” informando: “Não temos atualizações. Por favor, verifique novamente mais tarde”. Você pode personalizar essa mensagem de erro de “feed vazio” de forma semelhante à seguinte:
Se o ContentCardsFragment
determinar que o usuário não se qualifica para nenhum cartão de conteúdo, ele exibirá a mensagem de erro de feed vazio.
Um adaptador especial, o EmptyContentCardsAdapter
substitui o adaptador padrão ContentCardAdapter
para exibir essa mensagem de erro. Para definir a própria mensagem personalizada, substitua o recurso de string com_braze_feed_empty
.
O estilo usado para exibir essa mensagem pode ser encontrado em Braze.ContentCardsDisplay.Empty
e é reproduzido no trecho de código a seguir:
1
2
3
4
5
6
7
8
9
<style name="Braze.ContentCardsDisplay.Empty">
<item name="android:lineSpacingExtra">1.5dp</item>
<item name="android:text">@string/com_braze_feed_empty</item>
<item name="android:textColor">@color/com_braze_content_card_empty_text_color</item>
<item name="android:textSize">18.0sp</item>
<item name="android:gravity">center</item>
<item name="android:layout_height">match_parent</item>
<item name="android:layout_width">match_parent</item>
</style>
Para saber mais sobre como personalizar os elementos de estilo do cartão de conteúdo, consulte Personalização de estilo.
Para personalizar a mensagem de erro “feed vazio” com o Jetpack Compose, você pode passar um emptyString
para ContentCardsList
. Você também pode enviar emptyTextStyle
para ContentCardListStyling
para personalizar ainda mais essa mensagem.
1
2
3
4
5
6
ContentCardsList(
emptyString = "No messages today",
style = ContentCardListStyling(
emptyTextStyle = TextStyle(...)
)
)
Se você tiver um criador que gostaria de exibir, pode passar emptyComposable
para ContentCardsList
. Se emptyComposable
for especificado, o emptyString
não será usado.
1
2
3
4
5
6
7
8
ContentCardsList(
emptyComposable = {
Image(
painter = painterResource(id = R.drawable.noMessages),
contentDescription = "No messages"
)
}
)
Personalize o estado vazio do view controller configurando as opções relacionadas Attributes
.
1
2
3
4
var attributes = BrazeContentCardUI.ViewController.Attributes.defaults
attributes.emptyStateMessage = "This is a custom empty state message"
attributes.emptyStateMessageFont = .preferredFont(forTextStyle: .title1)
attributes.emptyStateMessageColor = .secondaryLabel
Altere o idioma que aparece automaticamente nos feeds vazios do cartão de conteúdo redefinindo as strings localizáveis do cartão de conteúdo no arquivo ContentCardsLocalizable.strings
do aplicativo.
Se você quiser atualizar essa mensagem em diferentes idiomas de localização, localize o idioma correspondente na estrutura da pasta Resources com a string ContentCardsLocalizable.strings
.
O Web SDK não oferece suporte à substituição programática da linguagem “feed vazio”. Você pode aceitar substituí-lo sempre que o feed for exibido, mas isso não é recomendado porque o feed pode levar algum tempo para ser atualizado e o texto vazio do feed não será exibido imediatamente.
Vários feeds
Os cartões de conteúdo podem ser filtrados em seu app para que apenas cartões específicos sejam exibidos, o que o capacita a ter vários feeds de cartões de conteúdo para diferentes casos de uso. Por exemplo, você pode manter um feed transacional e um feed de marketing. Para isso, crie diferentes categorias de cartões de conteúdo definindo pares de valores-chave no dashboard do Braze. Em seguida, crie feeds em seu app ou site que tratem esses tipos de cartões de conteúdo de forma diferente, filtrando alguns tipos e exibindo outros.
Etapa 1: Definir pares de valores-chave nos cartões
Ao criar uma campanha de cartão de conteúdo, defina os dados do par chave-valor em cada cartão. Você usará esse par chave-valor para categorizar os cartões. Os pares de valores-chave são armazenados na propriedade extras
no modelo de dados do cartão.
Para este exemplo, definiremos um par de valores chave com a chave feed_type
que designará em qual feed do cartão de conteúdo o cartão deve ser exibido. O valor será o valor de seus feeds personalizados, como home_screen
ou marketing
.
Etapa 2: Filtrar cartões de conteúdo
Depois que os pares de valores-chave tiverem sido atribuídos, crie um feed com lógica que exibirá os cartões que você deseja exibir e filtrará cartões de outros tipos. Neste exemplo, exibiremos apenas os cartões com um par de valores-chave correspondente a feed_type: "Transactional"
.
A filtragem dos cartões de conteúdo pode ser feita lendo os pares de valores-chave definidos no dashboard por meio de Card.getExtras()
e filtrando (ou executando qualquer outra lógica que desejar) usando um manipulador de atualização personalizado.
Para elaborar, o feed do seu cartão de conteúdo é exibido em um arquivo ContentCardsFragment
. O padrão IContentCardsUpdateHandler
recebe um ContentCardsUpdatedEvent
do SDK da Braze e retorna uma lista de cartões para exibição, mas apenas classifica os cartões e não realiza nenhuma remoção ou filtragem por conta própria.
Para permitir que um ContentCardsFragment
filtre, crie um arquivo IContentCardsUpdateHandler
. Modifique este IContentCardsUpdateHandler
para remover todos os cartões da lista que não correspondam ao valor desejado para o feed_type
que definimos anteriormente. Por exemplo:
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
private IContentCardsUpdateHandler getUpdateHandlerForFeedType(final String desiredFeedType) {
return new IContentCardsUpdateHandler() {
@Override
public List<Card> handleCardUpdate(ContentCardsUpdatedEvent event) {
// Use the default card update handler for a first
// pass at sorting the cards. This is not required
// but is done for convenience.
final List<Card> cards = new DefaultContentCardsUpdateHandler().handleCardUpdate(event);
final Iterator<Card> cardIterator = cards.iterator();
while (cardIterator.hasNext()) {
final Card card = cardIterator.next();
// Make sure the card has our custom KVP
// from the dashboard with the key "feed_type"
if (card.getExtras().containsKey("feed_type")) {
final String feedType = card.getExtras().get("feed_type");
if (!desiredFeedType.equals(feedType)) {
// The card has a feed type, but it doesn't match
// our desired feed type, remove it.
cardIterator.remove();
}
} else {
// The card doesn't have a feed
// type at all, remove it
cardIterator.remove();
}
}
// At this point, all of the cards in this list have
// a feed type that explicitly matches the value we put
// in the dashboard.
return cards;
}
};
}
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
private fun getUpdateHandlerForFeedType(desiredFeedType: String): IContentCardsUpdateHandler {
return IContentCardsUpdateHandler { event ->
// Use the default card update handler for a first
// pass at sorting the cards. This is not required
// but is done for convenience.
val cards = DefaultContentCardsUpdateHandler().handleCardUpdate(event)
val cardIterator = cards.iterator()
while (cardIterator.hasNext()) {
val card = cardIterator.next()
// Make sure the card has our custom KVP
// from the dashboard with the key "feed_type"
if (card.extras.containsKey("feed_type")) {
val feedType = card.extras["feed_type"]
if (desiredFeedType != feedType) {
// The card has a feed type, but it doesn't match
// our desired feed type, remove it.
cardIterator.remove()
}
} else {
// The card doesn't have a feed
// type at all, remove it
cardIterator.remove()
}
}
// At this point, all of the cards in this list have
// a feed type that explicitly matches the value we put
// in the dashboard.
cards
}
}
Depois de criar um IContentCardsUpdateHandler
, crie um ContentCardsFragment
que o utilize. Esse feed personalizado pode ser usado como qualquer outro ContentCardsFragment
. Nas diferentes partes de seu app, exiba diferentes feeds de cartão de conteúdo com base na chave fornecida no dashboard. Cada feed do ContentCardsFragment
terá um conjunto exclusivo de cartões exibidos graças ao IContentCardsUpdateHandler
personalizado em cada fragmento.
Por exemplo:
1
2
3
// We want a Content Cards feed that only shows "Transactional" cards.
ContentCardsFragment customContentCardsFragment = new ContentCardsFragment();
customContentCardsFragment.setContentCardUpdateHandler(getUpdateHandlerForFeedType("Transactional"));
1
2
3
// We want a Content Cards feed that only shows "Transactional" cards.
val customContentCardsFragment = ContentCardsFragment()
customContentCardsFragment.contentCardUpdateHandler = getUpdateHandlerForFeedType("Transactional")
Para filtrar quais cartões de conteúdo são mostrados nesse feed, use cardUpdateHandler
. Por exemplo:
1
2
3
4
5
6
7
ContentCardsList(
cardUpdateHandler = {
it.filter { card ->
card.extras["feed_type"] == "Transactional"
}
}
)
O exemplo a seguir mostrará o feed dos cartões de conteúdo para cartões do tipo Transactional
:
1
2
// Filter cards by the `Transactional` feed type based on your key-value pair.
let transactionalCards = cards.filter { $0.extras["feed_type"] as? String == "Transactional" }
Para ir além, os cartões apresentados no view controller podem ser filtrados definindo a propriedade transform
em sua estrutura Attributes
para exibir apenas os cartões filtrados por seus critérios.
1
2
3
4
5
6
7
var attributes = BrazeContentCardUI.ViewController.Attributes.defaults
attributes.transform = { cards in
cards.filter { $0.extras["feed_type"] as? String == "Transactional" }
}
// Pass your attributes containing the transformed cards to the Content Card UI.
let viewController = BrazeContentCardUI.ViewController(braze: AppDelegate.braze, attributes: attributes)
1
2
3
4
5
6
7
// Filter cards by the `Transactional` feed type based on your key-value pair.
NSMutableArray<BRZContentCardRaw *> *transactionalCards = [[NSMutableArray alloc] init];
for (BRZContentCardRaw *card in AppDelegate.braze.contentCards.cards) {
if ([card.extras[@"feed_type"] isEqualToString:@"Transactional"]) {
[transactionalCards addObject:card];
}
}
O exemplo a seguir mostrará o feed dos cartões de conteúdo para cartões do tipo Transactional
:
1
2
3
4
5
6
7
8
9
/**
* @param {String} feed_type - value of the "feed_type" KVP to filter
*/
function showCardsByFeedType(feed_type) {
braze.showContentCards(null, function(cards) {
return cards.filter((card) => card.extras["feed_type"] === feed_type);
});
}
Em seguida, você pode configurar um botão de alternância para seu feed personalizado:
1
2
3
4
// show the "Transactional" feed when this button is clicked
document.getElementById("show-transactional-feed").onclick = function() {
showCardsByFeedType("Transactional");
};
Para saber mais, consulte a documentação do método SDK.