Logging analytics
If you would like to display the Content Cards in a completely custom manner, you can implement your own Content Cards presentation UI. However, data such as impressions, clicks, and dismissals are only handled automatically when using the default card models. When implementing completely custom UI, you will need to handle this data manually. To do this, populate your custom UI with data from the Braze data models and manually log analytics like impressions and clicks. Once you understand how to log analytics, you can see common ways Braze customers create custom Content Cards.
Listening for card updates
When implementing your custom Content Cards, you can parse the Content Card objects and extract their payload data such as title
, cardDescription
, and imageUrl
. Then, you can use the resulting model data to populate your custom UI.
To obtain the Content Card data models, subscribe to Content Card updates. There are two properties to pay particular attention to:
id
: Represents the Content Card ID string. This is the unique identifier used to log analytics from custom Content Cards.extras
: Encompasses all the key-value pairs from the Braze dashboard.
All properties outside of id
and extras
are optional to parse for custom Content Cards. For more information on the data model, see each platform’s integration article: Android, iOS, Web.
Step 1: Create a private subscriber variable
To subscribe to card updates, first declare a private variable in your custom class to hold your subscriber:
1
2
// subscriber variable
private IEventSubscriber<ContentCardsUpdatedEvent> mContentCardsUpdatedSubscriber;
Step 2: Subscribe to updates
Next, add the following code to subscribe to Content Card updates from Braze, typically inside of your custom Content Cards activity’s Activity.onCreate()
:
1
2
3
4
5
6
7
8
9
10
11
12
13
// Remove the previous subscriber before rebuilding a new one with our new activity.
Braze.getInstance(context).removeSingleSubscription(mContentCardsUpdatedSubscriber, ContentCardsUpdatedEvent.class);
mContentCardsUpdatedSubscriber = new IEventSubscriber<ContentCardsUpdatedEvent>() {
@Override
public void trigger(ContentCardsUpdatedEvent event) {
// List of all Content Cards
List<Card> allCards = event.getAllCards();
// Your logic below
}
};
Braze.getInstance(context).subscribeToContentCardsUpdates(mContentCardsUpdatedSubscriber);
Braze.getInstance(context).requestContentCardsRefresh();
Step 3: Unsubscribe
We also recommend unsubscribing when your custom activity moves out of view. Add the following code to your activity’s onDestroy()
lifecycle method:
1
Braze.getInstance(context).removeSingleSubscription(mContentCardsUpdatedSubscriber, ContentCardsUpdatedEvent.class);
Step 1: Create a private subscriber variable
To subscribe to card updates, first declare a private variable in your custom class to hold your subscriber:
1
private var contentCardsUpdatedSubscriber: IEventSubscriber<ContentCardsUpdatedEvent>? = null
Step 2: Subscribe to updates
Next, add the following code to subscribe to Content Card updates from Braze, typically inside of your custom Content Cards activity’s Activity.onCreate()
:
1
2
3
4
5
6
7
8
9
10
// Remove the previous subscriber before rebuilding a new one with our new activity.
Braze.getInstance(context).subscribeToContentCardsUpdates(contentCardsUpdatedSubscriber)
Braze.getInstance(context).requestContentCardsRefresh()
// List of all Content Cards
val allCards = event.allCards
// Your logic below
}
Braze.getInstance(context).subscribeToContentCardsUpdates(mContentCardsUpdatedSubscriber)
Braze.getInstance(context).requestContentCardsRefresh(true)
Step 3: Unsubscribe
We also recommend unsubscribing when your custom activity moves out of view. Add the following code to your activity’s onDestroy()
lifecycle method:
1
Braze.getInstance(context).removeSingleSubscription(contentCardsUpdatedSubscriber, ContentCardsUpdatedEvent::class.java)
To access the Content Cards data model, call contentCards.cards
on your braze
instance.
1
let cards: [Braze.ContentCard] = AppDelegate.braze?.contentCards.cards
Additionally, you can also maintain a subscription to observe for changes in your Content Cards. You can do so in one of two ways:
- Maintaining a cancellable; or
- Maintaining an
AsyncStream
.
Cancellable
1
2
3
4
5
6
// This subscription is maintained through a Braze cancellable, which will observe for changes until the subscription is cancelled.
// You must keep a strong reference to the cancellable to keep the subscription active.
// The subscription is canceled either when the cancellable is deinitialized or when you call its `.cancel()` method.
let cancellable = AppDelegate.braze?.contentCards.subscribeToUpdates { [weak self] contentCards in
// Implement your completion handler to respond to updates in `contentCards`.
}
AsyncStream
1
let stream: AsyncStream<[Braze.ContentCard]> = AppDelegate.braze?.contentCards.cardsStream
1
NSArray<BRZContentCardRaw *> *contentCards = AppDelegate.braze.contentCards.cards;
Additionally, if you wish to maintain a subscription to your content cards, you can call subscribeToUpdates
:
1
2
3
4
// This subscription is maintained through Braze cancellable, which will continue to observe for changes until the subscription is cancelled.
BRZCancellable *cancellable = [self.braze.contentCards subscribeToUpdates:^(NSArray<BRZContentCardRaw *> *contentCards) {
// Implement your completion handler to respond to updates in `contentCards`.
}];
Register a callback function to subscribe for updates when cards are refreshed.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import * as braze from "@braze/web-sdk";
braze.subscribeToContentCardsUpdates((updates) => {
const cards = updates.cards;
// For example:
cards.forEach(card => {
if (card.isControl) {
// Do not display the control card, but remember to call `logContentCardImpressions([card])`
}
else if (card instanceof braze.ClassicCard || card instanceof braze.CaptionedImage) {
// Use `card.title`, `card.imageUrl`, etc.
}
else if (card instanceof braze.ImageOnly) {
// Use `card.imageUrl`, etc.
}
})
});
braze.openSession();
Content Cards will only refresh on session start if a subscribe request is called before openSession()
. You can always choose to manually refresh the feed as well.
Logging events
Logging valuable metrics like impressions, clicks, and dismissals is quick and simple. Set a custom click listener to manually handle these analytics.
The BrazeManager
can reference Braze SDK dependencies such as the Content Card objects array list to get the Card
to call the Braze logging methods. Use the ContentCardable
base class to easily reference and provide data to the BrazeManager
.
To log an impression or click on a card, call Card.logClick()
or Card.logImpression()
respectively.
You can manually log or set a Content Card as “dismissed” to Braze for a particular card with isDismissed
. If a card is already marked as dismissed, it cannot be marked as dismissed again.
To create a custom click listener, create a class that implements IContentCardsActionListener
and register it with BrazeContentCardsManager
. Implement the onContentCardClicked()
method, which will be called when the user clicks a Content Card. Then, instruct Braze to use your Content Card click listener.
For example:
1
2
3
4
5
6
7
8
9
10
11
BrazeContentCardsManager.getInstance().setContentCardsActionListener(new IContentCardsActionListener() {
@Override
public boolean onContentCardClicked(Context context, Card card, IAction cardAction) {
return false;
}
@Override
public void onContentCardDismissed(Context context, Card card) {
}
});
For example:
1
2
3
4
5
6
7
8
9
BrazeContentCardsManager.getInstance().contentCardsActionListener = object : IContentCardsActionListener {
override fun onContentCardClicked(context: Context, card: Card, cardAction: IAction): Boolean {
return false
}
override fun onContentCardDismissed(context: Context, card: Card) {
}
}
To handle control variant Content Cards in your custom UI, pass in your com.braze.models.cards.Card
object, then call the logImpression
method as you would with any other Content Card type. The object will implicitly log a control impression to inform our analytics of when a user would have seen the control card.
Implement the BrazeContentCardUIViewControllerDelegate
protocol and set your delegate object as the delegate
property of your BrazeContentCardUI.ViewController
. This delegate will handle passing the data of your custom object back to Braze to be logged. For an example, see Content Cards UI tutorial.
1
2
3
4
5
6
7
8
9
10
11
12
// Set the delegate when creating the Content Cards controller
contentCardsController.delegate = delegate
// Method to implement in delegate
func contentCard(
_ controller: BrazeContentCardUI.ViewController,
shouldProcess clickAction: Braze.ContentCard.ClickAction,
card: Braze.ContentCard
) -> Bool {
// Intercept the content card click action here.
return true
}
1
2
3
4
5
6
7
8
9
10
// Set the delegate when creating the Content Cards controller
contentCardsController.delegate = delegate;
// Method to implement in delegate
- (BOOL)contentCardController:(BRZContentCardUIViewController *)controller
shouldProcess:(NSURL *)url
card:(BRZContentCardRaw *)card {
// Intercept the content card click action here.
return YES;
}
To handle control variant Content Cards in your custom UI, pass in your Braze.ContentCard.Control
object, then call the logImpression
method as you would with any other Content Card type. The object will implicitly log a control impression to inform our analytics of when a user would have seen the control card.
Log impression events when cards are viewed by users using logContentCardImpressions
:
1
2
3
import * as braze from "@braze/web-sdk";
braze.logContentCardImpressions([card1, card2, card3]);
Log card click events when users interact with a card using logContentCardClick
:
1
2
3
import * as braze from "@braze/web-sdk";
braze.logContentCardClick(card);