Skip to content

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.

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 :

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 :

  1. Chargez l’image sur le thread en arrière-plan avec submit().get().
  2. Publiez la mise à jour de l’interface utilisateur sur le thread principal avec imageView.post { ... }.
  3. 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
  }
}
New Stuff!