Intégrer des GIF dans les cartes de contenu
Découvrez comment intégrer des GIF dans les cartes de contenu à l’aide du SDK de Braze.

Pour les SDK wrapper non répertoriés, utilisez plutôt la méthode native Android ou Swift correspondante. Gardez à l’esprit que les SDK Android et Swift Braze ne prennent pas en charge les GIF animés de manière native, vous mettrez donc en œuvre les GIF de la carte de contenu à l’aide d’outils tiers à la place.
La prise en charge du format GIF est incluse par défaut dans l’intégration SDK Web.
À propos des GIF
Braze offre la possibilité d’utiliser une bibliothèque d’images personnalisée pour afficher des GIF animés. Bien que l’exemple suivant utilise Glide, toute bibliothèque d’images prenant en charge les GIF est compatible.
Intégration d’une bibliothèque d’images personnalisée
Étape 1 : Créer le délégué du chargeur d’images
Le délégué du chargeur d’images doit implémenter les méthodes suivantes :
getInAppMessageBitmapFromUrl()getPushBitmapFromUrl()renderUrlIntoCardView()renderUrlIntoInAppMessageView()setOffline()
L’exemple d’intégration suivant est tiré de l’application d’exemple d’intégration Glide incluse avec le SDK Android de Braze.
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)
}
}
Correctif pour le chargement d’images à partir du SDK Android 36.0.0
À partir du SDK Android 36.0.0, displayInAppMessage() est une fonction suspend. Cela signifie que renderUrlIntoInAppMessageView() s’exécute sur un thread en arrière-plan plutôt que sur le thread principal.
Si votre chargeur d’images personnalisé appelle Glide.into(imageView) dans renderUrlIntoInAppMessageView(), votre application peut échouer avec l’erreur « You must call this method on the main thread. »
Pour éviter cela :
- Chargez l’image sur le thread en arrière-plan avec
submit().get(). - Publiez la mise à jour de l’interface utilisateur sur le thread principal avec
imageView.post { ... }. - Si le résultat chargé est un drawable GIF, lancez l’animation après l’avoir défini sur la vue.
Cela sépare le chargement d’images du rendu de l’interface utilisateur et garantit la compatibilité de votre chargeur d’images personnalisé avec le SDK Android 36.0.0 et les versions ultérieures.
Ces recommandations s’appliquent aux chargeurs d’images personnalisés Android. Les messages in-app web prennent en charge les GIF nativement.
L’exemple Kotlin suivant utilise des valeurs de substitution pour illustrer ce schéma :
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)
}
}
Étape 2 : Définir le délégué du chargeur d’images
Le SDK Braze utilisera tout chargeur d’images personnalisé défini avec IBrazeImageLoader. Nous recommandons de définir le chargeur d’images personnalisé dans une sous-classe d’application personnalisée :
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()
}
}
Résolution des problèmes de chargement d’images Glide
Si les images ne se chargent plus après avoir défini un IBrazeImageLoader personnalisé (par exemple avec Glide), vérifiez si un intercepteur OkHttp global ajoute des en-têtes d’authentification à chaque requête.
L’exemple Glide sur cette page utilise le même chemin de chargement pour les Content Cards, les messages in-app et les notifications push. Les images hébergées par Braze sont des URL CDN et n’utilisent pas l’authentification de votre REST API. Limitez la portée des intercepteurs à vos propres hôtes d’API, ou excluez les hôtes d’images Braze. Une image de Content Card qui échoue après une intégration Glide est un symptôme courant de ce schéma d’intercepteur.
Chargement personnalisé d’images avec Jetpack Compose
Pour remplacer le chargement d’images avec Jetpack Compose, vous pouvez passer une valeur à imageComposable. Cette fonction prend une Card et effectue le rendu de l’image ainsi que des modificateurs nécessaires. Vous pouvez également utiliser customCardComposer de ContentCardsList pour effectuer le rendu de la carte entière.
Dans l’exemple suivant, la bibliothèque Compose de Glide est utilisée pour les cartes répertoriées dans la fonction imageComposable :
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
}
}
)
)
Conditions préalables
Avant de pouvoir utiliser cette fonctionnalité, vous devrez intégrer le SDK Swift Braze.
Intégration d’une bibliothèque d’images personnalisée
Étape 1 : Intégration de SDwebimage
Intégrez le référentiel SDwebimage dans votre projet Xcode.
Étape 2 : Créer un nouveau fichier Swift
Dans votre projet Xcode, créez un nouveau fichier nommé SDWebImageGIFViewProvider.swift et importez ce qui suit :
1
2
3
import UIKit
import BrazeUI
import SDWebImage
Étape 3 : Ajouter GIFViewProvider
Ensuite, ajoutez notre SDwebimage d’exemple GIFViewProvider. Votre fichier devrait ressembler à ce qui suit :
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)
}
}
Étape 4 : Modifiez votre AppDelegate.swift
Dans votre projet AppDelegate.swift, ajoutez le support GIF à vos composants BrazeUI à l’aide de GIFViewProvider. Votre fichier devrait ressembler à ce qui suit :
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
}
}