Personalizar la fuente para 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 obtener la lista completa de tipos de tarjetas de contenido, consulta Acerca de las tarjetas de contenido.
Acerca del ciclo de vida de la sesión
Una sesión se refiere al período de tiempo durante el cual el SDK de Braze realiza el seguimiento de la actividad de los usuarios en tu aplicación después de su inicio. También puedes forzar una nueva sesión llamando alchangeUser()método .
De forma predeterminada, una sesión comienza cuando llamas por primera vez a braze.openSession(). La sesión permanecerá activa durante un máximo de30 minutos de inactividad (a menos que cambies el tiempo de espera predeterminado de la sesión o que el usuario cierre la aplicación).
Si has configurado la devolución de llamada del ciclo de](/docs/es/developer_guide/platform_integration_guides/android/initial_sdk_setup/android_sdk_integration/#step-4-tracking-user-sessions-in-android) vida de la actividad para Android, Braze llamará automáticamente aopenSession() ycloseSession() para cada actividad de tu aplicación.
De forma predeterminada, una sesión comienza cuandoopenSession()se llama por primera vez a . Si tu aplicación pasa a segundo plano y luego vuelve al primer plano, el SDK comprobará si han pasado más de 10 segundos desde que se inició la sesión (a menos que cambies el tiempo de espera predeterminado de la sesión). Si es así, comenzará una nueva sesión. Ten en cuenta que si el usuario cierra tu aplicación mientras está en segundo plano, es posible que los datos de la sesión no se envíen a Braze hasta que vuelva a abrir la aplicación.
Llamar nocloseSession() terminará inmediatamente la sesión. En su lugar, finalizará la sesión tras 10 segundos si el usuarioopenSession() no vuelve a llamar a iniciando otra actividad.
De forma predeterminada, una sesión comienza cuando llamas a Braze.init(configuration:). Esto ocurre cuando se desencadena laUIApplicationWillEnterForegroundNotificationnotificación, lo que significa que la aplicación ha pasado a primer plano.
Si tu aplicación pasa a segundo plano,UIApplicationDidEnterBackgroundNotification se desencadena. La aplicación no permanece en una sesión activa mientras está en segundo plano. Cuando tu aplicación vuelve al primer plano, el SDK compara el tiempo transcurrido desde el inicio de la sesión con el tiempo de espera de la sesión (a menos que cambies el tiempo de espera predeterminado). Si el tiempo transcurrido desde el inicio de la sesión supera el periodo de tiempo de espera, se inicia una nueva sesión.
Actualizar la fuente
Actualización automática
De forma predeterminada, la fuente de tarjetas de contenido se actualizará automáticamente cuando:
- Se inicia una nueva sesión
- La fuente predeterminada de tarjetas de contenido se cierra y se vuelve a abrir después de que hayan pasado más de 60 segundos desde la última actualización.
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.
Actualización manual
Para actualizar manualmente la fuente en un momento específico:
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();
}
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/Await
1
let contentCards = await AppDelegate.braze?.contentCards.requestRefresh()
1
2
3
[AppDelegate.braze.contentCards requestRefreshWithCompletion:^(NSArray<BRZContentCardRaw *> * contentCards, NSError * error) {
// Implement completion handler
}];
Límite de velocidad
Braze utiliza un algoritmo de contenedor de tokens para aplicar los siguientes límites de velocidad:
- Hasta 5 llamadas de actualización por dispositivo, compartidas entre usuarios y llamadas a
openSession() - Una vez alcanzado el límite, se puede realizar una nueva llamada cada 180 segundos (3 minutos)
- El sistema guardará hasta cinco llamadas para que las utilices en cualquier momento
subscribeToContentCards()seguirá devolviendo tarjetas almacenadas en caché incluso cuando se haya alcanzado el límite de velocidad
El SDK de Braze también aplica límites de velocidad para garantizar el rendimiento y la fiabilidad. Ten esto en cuenta al ejecutar pruebas automatizadas o realizar controles de calidad manuales. Consulta Límites de velocidad del SDK de Braze para obtener más información.
Personalizar el orden de las tarjetas mostradas
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 con tiempo limitado.
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
});
El 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 IContentCardsUpdateHandler predeterminado y puede utilizarse como punto de partida para la personalización:
Mostrar ejemplo en Java
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
}
}
Mostrar ejemplo en Kotlin
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.
Personalizar el mensaje de “fuente vacía”
Cuando un usuario no cumple los requisitos para ninguna tarjeta de contenido, el SDK muestra un mensaje de error de “fuente vacía” que indica: “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:

El SDK Web no permite sustituir el texto de “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 de fuente vacía no se mostrará inmediatamente.
Si el ContentCardsFragment determina que el usuario no cumple los requisitos para 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 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 las tarjetas de contenido, consulta Personalizar el estilo.
Para personalizar el mensaje de error de “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 vacío del controlador de vista configurando los Attributes relacionados.
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 texto que aparece automáticamente en las fuentes vacías de tarjetas de contenido redefiniendo las cadenas localizables de tarjetas de contenido en el archivo 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 de recursos con la cadena ContentCardsLocalizable.strings.
Implementar múltiples fuentes
Las tarjetas de contenido pueden filtrarse en tu aplicación para que solo se muestren tarjetas específicas, lo que te permite tener varias fuentes de tarjetas de contenido para diferentes casos de uso. Por ejemplo, puedes mantener tanto una fuente transaccional 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á en qué fuente de tarjetas de contenido 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 y filtre las tarjetas de otros tipos. En este ejemplo, solo mostraremos las tarjetas cuyo par clave-valor coincida con feed_type: "Transactional".
El siguiente ejemplo mostrará la fuente de 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 configurar un alternador para 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 del SDK.
De forma predeterminada, la fuente de tarjetas de contenido se muestra en un ContentCardsFragment y IContentCardsUpdateHandler devuelve una lista de tarjetas para mostrar después de recibir un ContentCardsUpdatedEvent del SDK de Braze. Sin embargo, solo ordena las tarjetas y no gestiona ningún filtrado directamente.
Paso 2.1: Crear un controlador personalizado
Puedes filtrar las tarjetas de contenido implementando un IContentCardsUpdateHandler personalizado utilizando los pares clave-valor establecidos por Card.getExtras() en el dashboard y, a continuación, modificándolo para eliminar de la lista cualquier tarjeta que no coincida con el valor de feed_type que estableciste anteriormente.
Mostrar ejemplo en Java
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;
}
};
}
Mostrar ejemplo en Kotlin
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
}
}
Paso 2.2: Añádelo a un fragmento
Después de crear un IContentCardsUpdateHandler, crea un ContentCardsFragment que lo utilice. Esta fuente personalizada puede utilizarse como cualquier otro 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 dashboard. Cada fuente ContentCardsFragment tendrá un conjunto único de tarjetas mostradas gracias al IContentCardsUpdateHandler personalizado en cada fragmento.
Mostrar ejemplo en Java
1
2
3
// We want a Content Cards feed that only shows "Transactional" cards.
ContentCardsFragment customContentCardsFragment = new ContentCardsFragment();
customContentCardsFragment.setContentCardUpdateHandler(getUpdateHandlerForFeedType("Transactional"));
Mostrar ejemplo en Kotlin
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"
}
}
)
The following example will show the Content Cards feed for Transactional type cards:
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 solo 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];
}
}
Editar esta página en GitHub