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を呼び出して、最新のコンテンツカード更新から現在利用可能なすべてのカードを取得することもできます。

1
2
3
4
5
import * as braze from "@braze/web-sdk";

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

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

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

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

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

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

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で見つけることができ、次のコードスニペットで再現されます。

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>

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

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の目的の値と一致しないカードをリストから削除します。以下に例を示します。

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

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構造体に設定して、条件でフィルタリングされたカードのみを表示することでフィルタリングできます。

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];
  }
}

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

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

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!