Skip to content

デフォルトのコンテンツカードフィードのカスタマイズ

コンテンツカードフィードは、モバイルまたは Web アプリケーションの一連のコンテンツカードです。この記事では、フィードの更新時の設定、カードの順序、複数フィードの管理、「空のフィード」エラーメッセージについて説明します。

フィードの更新

デフォルトでは、コンテンツカードフィードは次の場合に自動的に更新されます。

  1. 新しいセッションが開始される場合
  2. フィードが開かれ、最後の更新から 60 秒以上経過した場合

SDK は、特定の時間に手動で更新するように設定することもできます。

Android SDK からrequestContentCardsRefreshを呼び出すことで、いつでも手動で Braze コンテンツカードの更新を要求します。

1
Braze.getInstance(context).requestContentCardsRefresh();
1
Braze.getInstance(context).requestContentCardsRefresh()

Braze.ContentCardsクラスでrequestRefreshメソッドを呼び出すことで、いつでも Swift SDK から Braze コンテンツカードの手動更新を要求します。

Swift では、オプションの補完ハンドラまたはネイティブの Swift concurrency API を使用した非同期リターンを使用して、コンテンツカードを更新できます。

完了ハンドラ

1
2
3
AppDelegate.braze?.contentCards.requestRefresh { result in
  // Implement completion handler
}

非同期/待機

1
let contentCards = await AppDelegate.braze?.contentCards.requestRefresh()
1
2
3
[AppDelegate.braze.contentCards requestRefreshWithCompletion:^(NSArray<BRZContentCardRaw *> * contentCards, NSError * error) {
  // Implement completion handler
}];

Web SDK からrequestContentCardsRefresh()を呼び出して、いつでも手動で Braze コンテンツカードのリフレッシュを要求します。

また、getCachedContentCardsを呼び出して、最新のコンテンツカード更新から現在利用可能なすべてのカードを取得することもできます。

```javascript import * as braze from “@braze/web-sdk”;

function refresh() { braze.requestContentCardsRefresh();
} ```

表示されるカードの順序をカスタマイズする

コンテンツカードの表示順序を変更できます。これにより、時間的制約のあるプロモーションなど、特定のタイプのコンテンツに優先順位を付けることで、ユーザーエクスペリエンスを微調整できます。

ContentCardsFragmentは、IContentCardsUpdateHandlerに依存して、フィードに表示される前にコンテンツカードのソートまたは変更を処理します。カスタム更新ハンドラは、ContentCardsFragmentsetContentCardUpdateHandlerで設定できます。

以下はデフォルトのIContentCardsUpdateHandlerであり、カスタマイズの開始点として使用できます。

```java 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 CREATOR = new Parcelable.Creator() { public DefaultContentCardsUpdateHandler createFromParcel(Parcel in) { return new DefaultContentCardsUpdateHandler(); }

1
2
3
public DefaultContentCardsUpdateHandler[] newArray(int size) {
  return new DefaultContentCardsUpdateHandler[size];
}   };

@Override public List handleCardUpdate(ContentCardsUpdatedEvent event) { List sortedCards = event.getAllCards(); // Sort by pinned, then by the 'updated' timestamp descending // Pinned before non-pinned Collections.sort(sortedCards, new Comparator() { @Override public int compare(Card cardA, Card cardB) { // A が B の上に表示 if (cardA.getIsPinned() && !cardB.getIsPinned()) { return -1; }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
    // 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;   }

// パーセル可能なインターフェイス方式 @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 } } ```

```kotlin class DefaultContentCardsUpdateHandler :IContentCardsUpdateHandler { override fun handleCardUpdate(event:ContentCardsUpdatedEvent):List { 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 }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
  // 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   }

// パーセル可能なインターフェイス方式 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() }

1
2
3
4
  override fun newArray(size: Int): Array<DefaultContentCardsUpdateHandler?> {
    return arrayOfNulls(size)
  }
}   } } \`\`\`

ContentCardsFragmentソースは GitHub にあります。

Jetpack Compose でコンテンツカードをフィルタリングおよびソートするには、cardUpdateHandlerパラメータを設定します。次に例を示します。

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
        }
    }
)

静的な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)

BrazeContentCardUI.ViewController.Attributesによるカスタマイズは OBJECTIVE-C では使用できません。

showContentCards():filterFunctionパラメーターを使用して、フィードのコンテンツカードの表示順序をカスタマイズします。次に例を示します。

1
2
3
braze.showContentCards(null, (cards) => {
  return sortBrazeCards(cards); // Where sortBrazeCards is your sorting function that returns the sorted card array
});

「空のフィード」メッセージのカスタマイズ

ユーザーがコンテンツカードに適格でない場合、SDK は次のような「空のフィード」エラーメッセージを表示します。「更新はありません。後で再度確認してください。」 この「空のフィード」エラーメッセージは、次のようにカスタマイズできます。

「これはカスタムの空状態のメッセージです。」と表示される空のフィードエラーメッセージ

ContentCardsFragmentによって、ユーザがコンテンツカードに対応していないと判断された場合は、空のフィードエラーメッセージが表示されます。

特殊なアダプタEmptyContentCardsAdapterは、標準のContentCardAdapterを置き換えてこのエラーメッセージを表示します。カスタムメッセージ自体を設定するには、文字列リソースcom_braze_feed_emptyを上書きします。

このメッセージを表示するために使用されるスタイルは、Braze.ContentCardsDisplay.Emptyで見つけることができ、次のコードスニペットで再現されます。

```xml

```

コンテンツカードスタイル要素のカスタマイズの詳細については、スタイルのカスタマイズを参照してください。

Jetpack Compose で「空のフィード」エラーメッセージをカスタマイズするには、emptyStringContentCardsListに渡します。emptyTextStyleContentCardListStylingに渡して、このメッセージをさらにカスタマイズすることもできます。

1
2
3
4
5
6
ContentCardsList(
    emptyString = "No messages today",
    style = ContentCardListStyling(
        emptyTextStyle = TextStyle(...)
    )
)

代わりに表示するコンポーザブルがある場合は、emptyComposableContentCardsListに渡します。emptyComposableを指定した場合、emptyStringは使用されません。

1
2
3
4
5
6
7
8
ContentCardsList(
    emptyComposable = {
        Image(
            painter = painterResource(id = R.drawable.noMessages),
            contentDescription = "No messages"
        )
    }
)

関連する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

空のコンテンツカードフィードに自動的に表示される言語を変更するには、アプリのContentCardsLocalizable.stringsファイルでローカライズ可能なコンテンツカード文字列を再定義します。

Web SDK では、「空のフィード」の言語をプログラムで置き換えることはできません。フィードが表示されるたびに置き換えることを選択できますが、フィードが更新され、空のフィードテキストがすぐに表示されないため、これはお勧めしません。

複数のフィード

コンテンツカードは、特定のカードのみが表示されるようにアプリでフィルタリングできます。これにより、さまざまなユースケースで複数のコンテンツカードフィードを使用できます。たとえば、トランザクションフィードとマーケティングフィードの両方を維持できます。これを行うには、Braze ダッシュボードでキーと値のペアを設定して、コンテンツカードのさまざまなカテゴリーを作成します。次に、これらのタイプのコンテンツカードを異なる方法で処理し、一部のタイプをフィルタリングして他のタイプを表示するフィードをアプリまたはサイトに作成します。

ステップ1: カードにキーと値のペアを設定する

コンテンツカードキャンペーンを作成する場合は、各カードでキーと値のペアデータを設定します。このキーと値のペアを使用して、カードを分類します。キーと値のペアは、カードのデータモデルのextrasプロパティに保存されます。

この例では、カードが表示されるコンテンツカードフィードを指定するキーfeed_typeを使用してキーと値のペアを設定します。この値は、home_screenmarketingなど、カスタムフィードの値になります。

ステップ2: コンテンツカードのフィルタリング

キーと値のペアが割り当てられたら、他のタイプのカードを表示およびフィルタリングするカードを表示するロジックを含むフィードを作成します。この例では、feed_type: "Transactional"のキーと値のペアが一致するカードのみを表示します。

コンテンツカードのフィルタリングは、Card.getExtras()を介してダッシュボードに設定されたキーと値のペアを読み取り、カスタム更新ハンドラを使用してフィルタリング (または他の必要なロジックを実行) することで実現できます。

詳細に説明すると、コンテンツカードフィードがContentCardsFragmentに表示されます。デフォルトのIContentCardsUpdateHandlerは、Braze SDK からContentCardsUpdatedEventを受け取り、表示するカードのリストを返しますが、カードの並べ替えのみを行い、それ自体は削除やフィルタリングを実行しません。

ContentCardsFragmentでフィルタリングできるようにするには、カスタムのIContentCardsUpdateHandlerを作成します。このIContentCardsUpdateHandlerを変更して、前に設定したfeed_typeの目的の値と一致しないカードをリストから削除します。次に例を示します。

```java private IContentCardsUpdateHandler getUpdateHandlerForFeedType(final String desiredFeedType) { return new IContentCardsUpdateHandler() { @Override public List handleCardUpdate(ContentCardsUpdatedEvent event) { // Use the default card update handler for a first // pass at sorting the cards. This is not required // ただし、便宜上行われる。 final List cards = new DefaultContentCardsUpdateHandler().handleCardUpdate(event);

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
  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;
}   }; } \`\`\`

```kotlin 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 // ただし、便宜上行われる。 val cards = DefaultContentCardsUpdateHandler().handleCardUpdate(event)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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   } } \`\`\`

IContentCardsUpdateHandlerを作成したら、それを使用するContentCardsFragmentを作成します。このカスタムフィードは、他のContentCardsFragmentと同様に使用できます。アプリのさまざまな部分で、ダッシュボードに用意されているキーに基づいて、さまざまなコンテンツカードフィードを表示します。各ContentCardsFragmentフィードには、各フラグメントのカスタムIContentCardsUpdateHandlerのおかげで一意のカードセットが表示されます。

次に例を示します。

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")

このフィードに表示されるコンテンツカードをフィルタリングするには、cardUpdateHandlerを使用します。次に例を示します。

1
2
3
4
5
6
7
ContentCardsList(
     cardUpdateHandler = {
         it.filter { card ->
             card.extras["feed_type"] == "Transactional"
         }
     }
 )

次の例では、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" }

さらに一歩進むために、ビューコントローラーに表示されるカードは、transformプロパティをAttributes構造体に設定して、条件でフィルタリングされたカードのみを表示することでフィルタリングできます。

```swift var attributes = BrazeContentCardUI.ViewController.Attributes.defaults attributes.transform = { cards in cards.filter { $0.extras[“feed_type”] as?String == “Transactional” } }

// 変換されたカードを含む属性をコンテンツカード 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];
  }
}

次の例では、Transactionalタイプカードのコンテンツカードフィードを示します。

```javascript

/**

  • @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); }); } ```

次に、カスタムフィードのトグルを設定できます。

1
2
3
4
// show the "Transactional" feed when this button is clicked
document.getElementById("show-transactional-feed").onclick = function() {
  showCardsByFeedType("Transactional"); 
};

詳細については、SDK メソッドドキュメントを参照してください。

「このページはどの程度役に立ちましたか?」
New Stuff!