GIFs
This article applies to integrations that use the Braze SDK’s default Content Cards fragment or views to display Content Cards.
Braze requires an external image library to display animated GIFs with Content Cards.
While the examples in this article are specific to GIFs, you can also follow this guide to display SVGs if the custom image library you’re integrating supports SVG files.
Custom image library integration
Braze offers the ability to use a custom image library to display animated GIFs with Content Cards.
Although the example below uses Glide, any image library that supports GIFs is compatible.
Step 1: Creating the image loader delegate
The Image Loader delegate must implement the following methods:
getInAppMessageBitmapFromUrl()
getPushBitmapFromUrl()
renderUrlIntoCardView()
renderUrlIntoInAppMessageView()
setOffline()
The integration example below is taken from the Glide integration sample app included with the Braze Android SDK.
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
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, viewBounds);
}
@Override
public void renderUrlIntoInAppMessageView(Context context, IInAppMessage inAppMessage, String imageUrl, ImageView imageView, BrazeViewBounds viewBounds) {
renderUrlIntoView(context, imageUrl, imageView, viewBounds);
}
@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, BrazeViewBounds viewBounds) {
Glide.with(context)
.load(imageUrl)
.apply(mRequestOptions)
.into(imageView);
}
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
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, viewBounds)
}
override fun renderUrlIntoInAppMessageView(context: Context, inAppMessage: IInAppMessage, imageUrl: String, imageView: ImageView, viewBounds: BrazeViewBounds) {
renderUrlIntoView(context, imageUrl, imageView, viewBounds)
}
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, viewBounds: BrazeViewBounds) {
Glide.with(context)
.load(imageUrl)
.apply(mRequestOptions)
.into(imageView)
}
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)
}
}
Step 2: Setting the image loader delegate
The Braze SDK will use any custom image loader set with setBrazeImageLoader
. We recommend setting the custom image loader in a custom application subclass:
1
2
3
4
5
6
7
public class GlideIntegrationApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
Braze.getInstance(context).setBrazeImageLoader(new GlideBrazeImageLoader());
}
}
1
2
3
4
5
6
class GlideIntegrationApplication : Application() {
override fun onCreate() {
super.onCreate()
Braze.getInstance(context).appboyImageLoader = GlideBrazeImageLoader()
}
}
Custom Image Loading with Jetpack Compose
To override image loading with Jetpack Compose, you can pass in a value to imageComposable
. This function will take a Card
and render the image and the modifiers needed. Alternatively, you can use customCardComposer
of ContentCardsList
to render the entire card.
In the following example, Glide’s Compose library is used for the cards listed in the imageComposable
function:
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
}
}
)
)