Personalizar la fuente de las tarjetas de contenido
Una fuente de tarjetas de contenido es la secuencia de tarjetas de contenido en tus aplicaciones móviles o Web. Este artículo cubre la configuración de cuándo se actualiza la fuente, el orden de las tarjetas, la gestión de múltiples fuentes y los mensajes de error de “fuente vacía”. Para ver la lista completa de tipos de tarjetas de contenido, consulta Acerca de las tarjetas de contenido.
Actualizar la fuente
Por predeterminado, la fuente de la tarjeta de contenido se actualizará automáticamente en las siguientes instancias:
- Se inicia una nueva sesión
- Cuando se abre la fuente y han pasado más de 60 segundos desde la última actualización. Esto sólo se aplica a la fuente predeterminada Tarjeta de contenido y sólo ocurre una vez por cada apertura de fuente.
También puedes configurar el SDK para que se actualice manualmente a determinadas horas.
Para mostrar dinámicamente tarjetas de contenido actualizadas sin actualizarlas manualmente, selecciona En la primera impresión durante la creación de la tarjeta. Estas tarjetas se actualizarán cuando estén disponibles.
Solicita una actualización manual de las tarjetas de contenido de Braze desde el SDK de Android en cualquier momento llamando a requestContentCardsRefresh
.
1
Braze.getInstance(context).requestContentCardsRefresh();
1
Braze.getInstance(context).requestContentCardsRefresh()
Solicita una actualización manual de las tarjetas de contenido de Braze desde el SDK Swift en cualquier momento llamando al método requestRefresh
en la clase Braze.ContentCards
:
En Swift, las tarjetas de contenido pueden actualizarse con un controlador de finalización opcional o con un retorno asíncrono utilizando las API de concurrencia nativas de Swift.
Controlador de finalización
1
2
3
AppDelegate.braze?.contentCards.requestRefresh { result in
// Implement completion handler
}
Async/Espera
1
let contentCards = await AppDelegate.braze?.contentCards.requestRefresh()
1
2
3
[AppDelegate.braze.contentCards requestRefreshWithCompletion:^(NSArray<BRZContentCardRaw *> * contentCards, NSError * error) {
// Implement completion handler
}];
Solicita una actualización manual de las tarjetas de contenido de Braze desde el SDK Web en cualquier momento llamando a requestContentCardsRefresh()
.
También puedes llamar a getCachedContentCards
para obtener todas las tarjetas actualmente disponibles desde la última actualización de las Tarjetas de Contenido.
1
2
3
4
5
import * as braze from "@braze/web-sdk";
function refresh() {
braze.requestContentCardsRefresh();
}
Puedes hacer hasta cinco llamadas seguidas. Después, habrá una nueva llamada disponible cada 180 segundos. El sistema retendrá hasta cinco llamadas para que las utilices en cualquier momento.
Personalizar el pedido de tarjeta mostrado
Puedes cambiar el orden en que se muestran tus tarjetas de contenido. Esto te permite ajustar la experiencia del usuario dando prioridad a determinados tipos de contenido, como las promociones sensibles al tiempo.
El sitio ContentCardsFragment
se basa en un IContentCardsUpdateHandler
para gestionar cualquier ordenación o modificación de las tarjetas de contenido antes de que se muestren en la fuente. Se puede configurar un controlador de actualizaciones personalizado a través de setContentCardUpdateHandler
en tu ContentCardsFragment
.
El siguiente es el predeterminado IContentCardsUpdateHandler
y puede utilizarse como punto de partida para su personalización:
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)
}
}
}
}
El código fuente de ContentCardsFragment
está disponible en GitHub.
Para filtrar y ordenar las tarjetas de contenido en Jetpack Compose, configura el parámetro cardUpdateHandler
. Por ejemplo:
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
}
}
)
Personaliza el orden de la fuente de tarjetas modificando directamente la variable 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)
La personalización a través de BrazeContentCardUI.ViewController.Attributes
no está disponible en Objective-C.
Personaliza el orden de visualización de las tarjetas de contenido en tu fuente utilizando el parámetro filterFunction
de showContentCards():
. Por ejemplo:
1
2
3
braze.showContentCards(null, (cards) => {
return sortBrazeCards(cards); // Where sortBrazeCards is your sorting function that returns the sorted card array
});
Mensaje personalizado de “fuente vacía”.
Cuando un usuario no tiene derecho a ninguna tarjeta de contenido, el SDK muestra un mensaje de error de “fuente vacía” que indica lo siguiente: “No tenemos actualizaciones. Vuelve a comprobarlo más tarde”. Puedes personalizar este mensaje de error de “fuente vacía” de forma similar a la siguiente:
Si el ContentCardsFragment
determina que el usuario no tiene derecho a ninguna tarjeta de contenido, muestra el mensaje de error de fuente vacía.
Un adaptador especial, el EmptyContentCardsAdapter
sustituye al ContentCardAdapter
estándar para mostrar este mensaje de error. Para configurar el propio mensaje personalizado, anula el recurso de cadena com_braze_feed_empty
.
El estilo utilizado para mostrar este mensaje se puede encontrar a través de Braze.ContentCardsDisplay.Empty
y se reproduce en el siguiente fragmento de código:
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 obtener más información sobre la personalización de los elementos de estilo de la tarjeta de contenido, consulta Personalizar el estilo.
Para personalizar el mensaje de error “fuente vacía” con Jetpack Compose, puedes pasar un emptyString
a ContentCardsList
. También puedes pasar emptyTextStyle
a ContentCardListStyling
para personalizar aún más este mensaje.
1
2
3
4
5
6
ContentCardsList(
emptyString = "No messages today",
style = ContentCardListStyling(
emptyTextStyle = TextStyle(...)
)
)
Si tienes un Composable que te gustaría mostrar en su lugar, puedes pasar emptyComposable
a ContentCardsList
. Si se especifica emptyComposable
, no se utilizará emptyString
.
1
2
3
4
5
6
7
8
ContentCardsList(
emptyComposable = {
Image(
painter = painterResource(id = R.drawable.noMessages),
contentDescription = "No messages"
)
}
)
Personaliza el estado de vacío del controlador de vista configurando los parámetros relacionados 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
Cambia el idioma que aparece automáticamente en las fuentes vacías de la tarjeta de contenido redefiniendo las cadenas localizables de la tarjeta de contenido en el archivo de tu aplicación ContentCardsLocalizable.strings
de tu aplicación.
Si quieres actualizar este mensaje en diferentes idiomas de localización, busca el idioma correspondiente en la estructura de carpetas Recursos con la cadena ContentCardsLocalizable.strings
.
El SDK Web no permite sustituir el lenguaje “fuente vacía” mediante programación. Puedes optar por sustituirlo cada vez que se muestre la fuente, pero no es recomendable porque la fuente puede tardar en actualizarse y el texto vacío de la fuente no se mostrará inmediatamente.
Múltiples fuentes
Las tarjetas de contenido pueden filtrarse en tu aplicación para que sólo se muestren tarjetas específicas, lo que te habilita a tener varias fuentes de tarjetas de contenido para diferentes casos de uso. Por ejemplo, puedes mantener tanto una fuente de transacciones como una fuente de marketing. Para ello, crea diferentes categorías de tarjetas de contenido estableciendo pares clave-valor en el panel de Braze. Después, crea fuentes en tu aplicación o sitio que traten estos tipos de tarjetas de contenido de forma diferente, filtrando algunos tipos y mostrando otros.
Paso 1: Establecer pares clave-valor en las tarjetas
Al crear una campaña de tarjeta de contenido, establece datos de par clave-valor en cada tarjeta. Utilizarás este par clave-valor para clasificar las tarjetas. Los pares clave-valor se almacenan en la propiedad extras
del modelo de datos de la tarjeta.
En este ejemplo, estableceremos un par clave-valor con la clave feed_type
que designará la fuente de la tarjeta de contenido en la que debe mostrarse la tarjeta. El valor será el que tengan tus fuentes personalizadas, como home_screen
o marketing
.
Paso 2: Filtrar tarjetas de contenido
Una vez asignados los pares clave-valor, crea una fuente con una lógica que muestre las tarjetas que deseas mostrar y filtre las tarjetas de otros tipos. En este ejemplo, sólo mostraremos las tarjetas cuyo par clave-valor coincida con feed_type: "Transactional"
.
Puedes filtrar las tarjetas de contenido leyendo los pares clave-valor configurados en el panel mediante Card.getExtras()
y filtrando (o realizando cualquier otra lógica que desees) mediante un controlador de actualizaciones personalizado.
Para explicarlo mejor, la fuente de tu tarjeta de contenido se muestra en un archivo ContentCardsFragment
. El IContentCardsUpdateHandler
predeterminado toma un ContentCardsUpdatedEvent
del SDK de Braze y devuelve una lista de tarjetas para mostrar, pero sólo ordena las tarjetas y no realiza ninguna eliminación ni filtrado por su cuenta.
Para que ContentCardsFragment
pueda filtrar, crea un archivo personalizado IContentCardsUpdateHandler
. Modifica este IContentCardsUpdateHandler
para eliminar de la lista las tarjetas que no coincidan con el valor que deseamos para el feed_type
que establecimos anteriormente. Por ejemplo:
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
}
}
Una vez que hayas creado un IContentCardsUpdateHandler
, crea un ContentCardsFragment
que lo utilice. Esta fuente personalizada puede utilizarse como cualquier otra ContentCardsFragment
. En las distintas partes de tu aplicación, muestra distintas fuentes de tarjetas de contenido en función de la clave proporcionada en el panel. Cada fuente ContentCardsFragment
tendrá un conjunto único de tarjetas mostradas gracias a la configuración personalizada IContentCardsUpdateHandler
de cada fragmento.
Por ejemplo:
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 qué tarjetas de contenido se muestran en esta fuente, utiliza cardUpdateHandler
. Por ejemplo:
1
2
3
4
5
6
7
ContentCardsList(
cardUpdateHandler = {
it.filter { card ->
card.extras["feed_type"] == "Transactional"
}
}
)
El siguiente ejemplo mostrará la fuente Tarjetas de contenido para las tarjetas de 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 un paso más allá, las tarjetas presentadas en el controlador de vista pueden filtrarse configurando la propiedad transform
en tu estructura Attributes
para mostrar sólo las tarjetas filtradas según tus criterios.
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];
}
}
El siguiente ejemplo mostrará la fuente Tarjetas de contenido para las tarjetas de 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);
});
}
A continuación, puedes alternar tu fuente personalizada:
1
2
3
4
// show the "Transactional" feed when this button is clicked
document.getElementById("show-transactional-feed").onclick = function() {
showCardsByFeedType("Transactional");
};
Para más información, consulta la documentación del método SDK.