コンテンツカードにGIFを埋め込む
Braze SDK を使用してGIF をコンテンツカードに埋め込む方法について説明します。
リストされていないラッパーSDK の場合は、代わりに関連するネイティブAndroid またはSwift メソッドを使用します。Android およびSwift Braze SDK はアニメーションGIF をネイティブにサポートしていないため、代わりにサードパーティツールを使用してコンテンツカードGIF を実装します。
GIF のサポートは、Web SDK 統合にデフォルトで含まれています。
GIFについて
Brazeはカスタム画像ライブラリーを使用してアニメーションGIFを表示する機能を提供しています。以下の例ではGlideを使用していますが、GIFをサポートする画像ライブラリーであればどれでも互換性があります。
カスタム画像ライブラリーの統合
ステップ 1: 画像ローダーデリゲートの作成
画像ローダーデリゲートは、以下のメソッドを実装する必要があります。
getInAppMessageBitmapFromUrl()getPushBitmapFromUrl()renderUrlIntoCardView()renderUrlIntoInAppMessageView()setOffline()
以下の統合例は、Braze Android SDKに含まれるGlide 統合サンプルアプリから取得したものです。
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
64
65
66
import com.braze.support.BrazeLogger;
import com.bumptech.glide.load.resource.gif.GifDrawable;
import android.graphics.drawable.Drawable;
public class GlideBrazeImageLoader implements IBrazeImageLoader {
private static final String TAG = GlideBrazeImageLoader.class.getName();
private RequestOptions mRequestOptions = new RequestOptions();
@Override
public void renderUrlIntoCardView(Context context, Card card, String imageUrl, ImageView imageView, BrazeViewBounds viewBounds) {
renderUrlIntoView(context, imageUrl, imageView);
}
@Override
public void renderUrlIntoInAppMessageView(Context context, IInAppMessage inAppMessage, String imageUrl, ImageView imageView, BrazeViewBounds viewBounds) {
renderUrlIntoView(context, imageUrl, imageView);
}
@Override
public Bitmap getPushBitmapFromUrl(Context context, Bundle extras, String imageUrl, BrazeViewBounds viewBounds) {
return getBitmapFromUrl(context, imageUrl, viewBounds);
}
@Override
public Bitmap getInAppMessageBitmapFromUrl(Context context, IInAppMessage inAppMessage, String imageUrl, BrazeViewBounds viewBounds) {
return getBitmapFromUrl(context, imageUrl, viewBounds);
}
private void renderUrlIntoView(Context context, String imageUrl, ImageView imageView) {
try {
final Drawable drawable = Glide.with(context)
.load(imageUrl)
.apply(mRequestOptions)
.submit()
.get();
imageView.post(() -> {
imageView.setImageDrawable(drawable);
if (drawable instanceof GifDrawable) {
((GifDrawable) drawable).start();
}
});
} catch (Exception e) {
BrazeLogger.e(TAG, "Failed to render URL into view: " + imageUrl, e);
}
}
private Bitmap getBitmapFromUrl(Context context, String imageUrl, BrazeViewBounds viewBounds) {
try {
return Glide.with(context)
.asBitmap()
.apply(mRequestOptions)
.load(imageUrl).submit().get();
} catch (Exception e) {
Log.e(TAG, "Failed to retrieve bitmap at url: " + imageUrl, e);
}
return null;
}
@Override
public void setOffline(boolean isOffline) {
// If the loader is offline, then we should only be retrieving from the cache
mRequestOptions = mRequestOptions.onlyRetrieveFromCache(isOffline);
}
}
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
import com.braze.support.BrazeLogger
import com.bumptech.glide.load.resource.gif.GifDrawable
class GlideBrazeImageLoader : IBrazeImageLoader {
companion object {
private val TAG = GlideBrazeImageLoader::class.qualifiedName
}
private var mRequestOptions = RequestOptions()
override fun renderUrlIntoCardView(context: Context, card: Card, imageUrl: String, imageView: ImageView, viewBounds: BrazeViewBounds) {
renderUrlIntoView(context, imageUrl, imageView)
}
override fun renderUrlIntoInAppMessageView(context: Context, inAppMessage: IInAppMessage, imageUrl: String, imageView: ImageView, viewBounds: BrazeViewBounds) {
renderUrlIntoView(context, imageUrl, imageView)
}
override fun getPushBitmapFromUrl(context: Context, extras: Bundle, imageUrl: String, viewBounds: BrazeViewBounds): Bitmap? {
return getBitmapFromUrl(context, imageUrl, viewBounds)
}
override fun getInAppMessageBitmapFromUrl(context: Context, inAppMessage: IInAppMessage, imageUrl: String, viewBounds: BrazeViewBounds): Bitmap? {
return getBitmapFromUrl(context, imageUrl, viewBounds)
}
private fun renderUrlIntoView(context: Context, imageUrl: String, imageView: ImageView) {
try {
val drawable = Glide.with(context)
.load(imageUrl)
.apply(mRequestOptions)
.submit()
.get()
imageView.post {
imageView.setImageDrawable(drawable)
if (drawable is GifDrawable) {
drawable.start()
}
}
} catch (e: Exception) {
BrazeLogger.e(TAG, "Failed to render URL into view: $imageUrl", e)
}
}
private fun getBitmapFromUrl(context: Context, imageUrl: String, viewBounds: BrazeViewBounds): Bitmap? {
try {
return Glide.with(context)
.asBitmap()
.apply(mRequestOptions)
.load(imageUrl).submit().get()
} catch (e: Exception) {
Log.e(TAG, "Failed to retrieve bitmap at url: $imageUrl", e)
}
return null
}
override fun setOffline(isOffline: Boolean) {
// If the loader is offline, then we should only be retrieving from the cache
mRequestOptions = mRequestOptions.onlyRetrieveFromCache(isOffline)
}
}
Android SDK 36.0.0 以降での画像読み込みの修正
Android SDK 36.0.0 以降では、displayInAppMessage() は suspend 関数です。これにより、renderUrlIntoInAppMessageView() はメインスレッドではなくバックグラウンドスレッドで実行されます。
カスタム画像ローダーが renderUrlIntoInAppMessageView() 内で Glide.into(imageView) を呼び出すと、「You must call this method on the main thread.」というエラーでアプリがクラッシュする可能性があります。
これを回避するには、以下の手順に従ってください。
- バックグラウンドスレッドで
submit().get()を使用して画像を読み込みます。 imageView.post { ... }を使用してUI更新をメインスレッドにポストします。- 読み込んだ結果がGIF drawableの場合、ビューに設定した後にアニメーションを開始します。
これにより、画像の読み込みとUIレンダリングが分離され、カスタム画像ローダーがAndroid SDK 36.0.0 以降との互換性を保ちます。
このガイダンスはAndroidのカスタム画像ローダーに適用されます。Webのアプリ内メッセージはGIFをそのままサポートしています。
以下の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
private const val TAG = "SampleGlideLoader"
private const val glideBrazeImageLoaderTag = "sample-loader"
private fun renderUrlIntoView(
context: Context,
imageUrl: String,
imageView: ImageView
) {
try {
val drawable: Drawable = Glide.with(context)
.load(imageUrl)
.apply(mRequestOptions)
.submit()
.get()
imageView.post {
imageView.setImageDrawable(drawable)
if (drawable is GifDrawable) {
drawable.start()
}
}
} catch (e: Exception) {
Log.e(TAG, "$glideBrazeImageLoaderTag renderUrlIntoView failed: url=$imageUrl", e)
}
}
ステップ 2: 画像ローダーデリゲートの設定
Braze SDKは、IBrazeImageLoaderで設定されたカスタム画像ローダーを使用します。カスタムアプリケーションサブクラスでカスタム画像ローダーを設定することをお勧めします。
1
2
3
4
5
6
7
public class GlideIntegrationApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
Braze.getInstance(context).setImageLoader(new GlideBrazeImageLoader());
}
}
1
2
3
4
5
6
class GlideIntegrationApplication : Application() {
override fun onCreate() {
super.onCreate()
Braze.getInstance(context).imageLoader = GlideBrazeImageLoader()
}
}
Jetpack Composeによるカスタム画像の読み込み
Jetpack Composeで画像の読み込みをオーバーライドするには、imageComposableに値を渡します。この関数は Card を受け取り、必要な画像とモディファイアをレンダリングします。または、ContentCardsList の customCardComposer を使用してカード全体をレンダリングすることもできます。
次の例では、imageComposable 関数にリストされているカードにGlideのComposeライブラリーが使用されています。
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
ContentCardsList(
cardStyle = ContentCardStyling(
imageComposable = { card ->
when (card.cardType) {
CardType.CAPTIONED_IMAGE -> {
val captionedImageCard = card as CaptionedImageCard
GlideImage(
modifier = Modifier
.fillMaxWidth()
.wrapContentHeight()
.run {
if (captionedImageCard.aspectRatio > 0) {
aspectRatio(captionedImageCard.aspectRatio)
} else {
this
}
},
contentScale = ContentScale.Crop,
model = captionedImageCard.url,
loading = placeholder(R.drawable.pushpin),
contentDescription = ""
)
}
CardType.IMAGE -> {
val imageOnlyCard = card as ImageOnlyCard
GlideImage(
modifier = Modifier
.fillMaxWidth()
.run {
if (imageOnlyCard.aspectRatio > 0) {
aspectRatio(imageOnlyCard.aspectRatio)
} else {
this
}
},
contentScale = ContentScale.Crop,
model = imageOnlyCard.url,
loading = placeholder(R.drawable.pushpin),
contentDescription = ""
)
}
CardType.SHORT_NEWS -> {
val shortNews = card as ShortNewsCard
GlideImage(
modifier = Modifier
.width(100.dp)
.height(100.dp),
model = shortNews.url,
loading = placeholder(R.drawable.pushpin),
contentDescription = ""
)
}
else -> Unit
}
}
)
)
前提条件
この機能を使う前に、Swift Braze SDKを統合する必要がある。
カスタムイメージライブラリの統合
ステップ 1: SDOAImageの統合
SDOBImageリポジトリをXコードプロジェクトに統合します。
ステップ 2:新しいSwift ファイルを作成する
Xコードプロジェクトで、SDWebImageGIFViewProvider.swiftという名前の新しいファイルを作成し、以下をインポートします。
1
2
3
import UIKit
import BrazeUI
import SDWebImage
ステップ 3:GIFViewProvider を追加する
次に、サンプルSDOAImage GIFViewProvider を追加します。ファイルは次のようになります。
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
import UIKit
import BrazeUI
import SDWebImage
extension GIFViewProvider {
/// A GIF view provider using [SDWebImage](https://github.com/SDWebImage/SDWebImage) as a
/// rendering library.
public static let sdWebImage = Self(
view: { SDAnimatedImageView(image: image(for: $0)) },
updateView: { ($0 as? SDAnimatedImageView)?.image = image(for: $1) }
)
private static func image(for url: URL?) -> UIImage? {
guard let url else { return nil }
return url.pathExtension == "gif"
? SDAnimatedImage(contentsOfFile: url.path)
: UIImage(contentsOfFile: url.path)
}
}
ステップ 4: 変更する AppDelegate.swift
プロジェクトのAppDelegate.swift で、BrazeUI コンポーネントにGIFViewProvider を使用してGIF サポートを追加します。ファイルは次のようになります。
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
import UIKit
import BrazeKit
import BrazeUI
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
static var braze: Braze? = nil
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
/* ... */
GIFViewProvider.shared = .sdWebImage
return true
}
}