Customization
All of Braze’s in-app message types are highly customizable across messages, images, Font Awesome icons, click actions, analytics, editable styling, custom display options, and custom delivery options. Multiple options can be configured on a per in-app message basis from within the dashboard. Braze additionally provides multiple levels of advanced customization to satisfy a variety of use cases and needs.
Key Value Pair Extras
In-app message objects may carry key value pairs as extras
. They are specified on the dashboard under “Advanced Settings” when creating an in-app message campaign. These can be used to send data down along with an in-app message for further handling by the application.
Call the following when you get an in-app message object to retrieve its extras:
1
Map<String, String> getExtras()
See the Javadoc for more information.
Custom Styling
Braze UI elements come with a default look and feel that matches the Android standard UI guidelines and provides a seamless experience. You can see these default styles in the Braze SDK’s styles.xml
file.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<style name="Appboy"/>
<!-- In-app Message -->
<style name="Appboy.InAppMessage">
</style>
<style name="Appboy.InAppMessage.Header">
<item name="android:layout_height">wrap_content</item>
<item name="android:layout_width">wrap_content</item>
<item name="android:padding">0.0dp</item>
<item name="android:background">@android:color/transparent</item>
<item name="android:textColor">@color/com_appboy_inappmessage_header_text_light</item>
<item name="android:textSize">19.0sp</item>
<item name="android:layout_gravity">center</item>
<item name="android:singleLine">true</item>
<item name="android:textStyle">bold</item>
</style>
If you would prefer, you can override these styles to create a look and feel that better suits your app.
To override a style, copy it in its entirety to the styles.xml
file in your own project and make modifications. The whole style must be copied over to your local styles.xml
file in order for all attributes to be correctly set. Please note that these custom styles are for changes to individual UI elements, not wholesale changes to layouts. Layout-level changes need to be handled with custom views.
Using Custom Styling to Set a Custom Font
Braze allows for setting a custom font using the font family guide. To use it, override the style for message text, headers, and/or button text and use the fontFamily
attribute to instruct Braze to use your custom font family.
For example, to update the font on your in-app message button text, override the Appboy.InAppMessage.Button
style and reference your custom font family. The attribute value should point to a font family in your res/font
directory.
Here is a truncated example with a custom font family, my_custom_font_family
, referenced on the last line:
1
2
3
4
5
6
7
<style name="Appboy.InAppMessage.Button">
<item name="android:layout_height">wrap_content</item>
...
<item name="android:paddingBottom">15.0dp</item>
<item name="android:fontFamily">@font/my_custom_font_family</item>
<item name="fontFamily">@font/my_custom_font_family</item>
</style>
Aside from the Appboy.InAppMessage.Button
style for button text, the style for message text is Appboy.InAppMessage.Message
and the style for message headers is Appboy.InAppMessage.Header
. If you want to use your custom font family across all possible in-app message text, you can set your font family on the Appboy.InAppMessage
style, which is the parent style for all in-app messages.
As with other custom styles, the entire style must be copied over to your local
styles.xml
file for all attributes to be correctly set.
Setting Custom Listeners
Before customizing in-app messages with custom listeners, it’s important to understand the AppboyInAppMessageManager
, which handles the majority of in-app message handling. As described in Step 1, it must be registered for in-app messages to function appropriately.
AppboyInAppMessageManager
manages in-app message display on Android. It contains helper class instances that help it manage the lifecycle and display of in-app messages. All of these classes have standard implementations and defining custom classes is completely optional. However, doing so can add another level of control over the display and behavior of in-app messages. These customizable classes include:
IInAppMessageManagerListener
- Implement to custom manage in-app message display and behavior.IInAppMessageViewFactory
- Implement to build custom in-app message views.IInAppMessageAnimationFactory
- Implement to define custom in-app message animations.
Setting a Custom Manager Listener
The AppboyInAppMessageManager
automatically handles the display and lifecycle of in-app messages. If you require more control over the lifecycle of a message, setting a custom manager listener will enable you to receive the in-app message object at various points in the in-app message lifecycle, allowing you to handle its display yourself, perform further processing, react to user behavior, process the object’s Extras, and much more.
Step 1: Implement an In-App Message Manager Listener
Create a class that implements IInAppMessageManagerListener
The callbacks in your IInAppMessageManagerListener
will be called at various points in the in-app message lifecycle.
For example, if you set a custom manager listener, when an in-app message is received from Braze, the beforeInAppMessageDisplayed()
method will be called. If your implementation of this method returns InAppMessageOperation.DISCARD
, that signals to Braze that the in-app message will be handled by the host app and should not be displayed by Braze. If InAppMessageOperation.DISPLAY_NOW
is returned, Braze will attempt to display the in-app message. This method should be used if you choose to display the in-app message in a customized manner.
IInAppMessageManagerListener
also includes delegate methods for clicks on the message itself or one of the buttons. A common use case would be intercepting a message when a button or message is clicked for further processing.
- See
CustomInAppMessageManagerListener.java
in our Droidboy sample app for an implementation example.
Step 2: Instruct Braze to use your In-App Message Manager Listener
Once your IInAppMessageManagerListener
is created, call AppboyInAppMessageManager.getInstance().setCustomInAppMessageManagerListener()
to instruct AppboyInAppMessageManager
to use your custom IInAppMessageManagerListener
instead of the default listener.
We recommend setting your
IInAppMessageManagerListener
in yourApplication.onCreate()
before any other calls to Braze. This will ensure that the custom listener is set before any in-app message is displayed.
See InAppMessageTesterFragment.java
in the DroidBoy sample app for an example implementation.
In-Depth: Altering In-App Messages Before Display
When a new in-app message is received, and there is already an in-app message being displayed, the new message will be put onto the top of the stack and can be displayed at a later time.
However, if there is no in-app message being displayed, the following delegate method in IInAppMessageManagerListener
will be called:
1
2
3
4
@Override
public InAppMessageOperation beforeInAppMessageDisplayed(IInAppMessage inAppMessageBase) {
return InAppMessageOperation.DISPLAY_NOW;
}
The InAppMessageOperation()
return value can be used to control when the message should be displayed. The suggested usage of this method would be to delay messages in certain parts of the app by returning DISPLAY_LATER
when in-app messages would be distracting to the user’s app experience.
InAppMessageOperation return value |
Behavior |
---|---|
DISPLAY_NOW |
The message will be displayed |
DISPLAY_LATER |
The message will be returned to the stack and displayed at the next available opportunity |
DISCARD |
The message will be discarded |
null |
The message will be ignored. This method should NOT return null |
See InAppMessageOperation.java
for more details.
If you choose to
DISCARD
the in-app message and replace it with your own in-app message view, you will need to manually log in-app message clicks and impressions.
On Android, this is done by calling logClick
and logImpression
on in-app messages, and logButtonClick
on immersive in-app messages.
Once an in-app message has been placed on the stack, you can request for it to be retrieved and displayed at any time by calling
AppboyInAppMessageManager.getInstance().requestDisplayInAppMessage()
. Calling this method requests Braze to display the next available in-app message from the stack.
Setting a Custom View Factory
Braze’s suite of in-app messages types are versatile enough to cover the vast majority of custom use cases. However, if you would like to fully define the visual appearance of your in-app messages instead of using a default type, Braze makes this possible via setting a custom view factory.
Step 1: Implement an In-App Message View Factory
Create a class that implements IInAppMessageViewFactory
- See
CustomInAppMessageViewFactory.java
in our Droidboy sample app for an implementation example.
Step 2: Instruct Braze to use your In-App Message View Factory
Once your IInAppMessageViewFactory
is created, call AppboyInAppMessageManager.getInstance().setCustomInAppMessageViewFactory()
to instruct AppboyInAppMessageManager
to use your custom IInAppMessageViewFactory
instead of the default view factory.
We recommend setting your
IInAppMessageViewFactory
in yourApplication.onCreate()
before any other calls to Braze. This will ensure that the custom view factory is set before any in-app message is displayed.
See InAppMessageTesterFragment.java
in the DroidBoy sample app for an example implementation.
In-Depth: Implementing a Braze View Interface
Braze’s slideup
in-app message view implements IInAppMessageView
. Braze’s full
and modal
type message views implement IInAppMessageImmersiveView
. Implementing one of these classes will allow Braze to add click listeners to your custom view where appropriate. All Braze view classes extend Android’s View class.
Implementing IInAppMessageView
allows you to define a certain portion of your custom view as clickable. Implementing IInAppMessageImmersiveView
allows you to define message button views and a close button view.
- See
CustomInAppMessageView.java
in our Droidboy sample app for an implementation example.
Setting a Custom Animation Factory
In-app messages have preset animation behavior. Slideup
type messages slide into the screen; full
and modal
messages fade in and out. If you would like to define custom animation behaviors for your in-app messages, Braze makes this possible via setting a custom animation factory.
Step 1: Implement an In-App Message Animation Factory
Create a class that implements IInAppMessageAnimationFactory
- See
CustomInAppMessageAnimationFactory.java
in our Droidboy sample app for an implementation example.
Step 2: Instruct Braze to use your In-App Message View Factory
Once your IInAppMessageAnimationFactory
is created, call AppboyInAppMessageManager.getInstance().setCustomInAppMessageAnimationFactory()
to instruct AppboyInAppMessageManager
to use your custom IInAppMessageAnimationFactory
instead of the default animation factory.
We recommend setting your
IInAppMessageAnimationFactory
in yourApplication.onCreate()
before any other calls to Braze. This will ensure that the custom animation factory is set before any in-app message is displayed.
See InAppMessageTesterFragment.java
in the DroidBoy sample app for an example implementation.
Setting Fixed Orientation
To set a fixed orientation for an in-app message, first set a custom in-app message manager listener. Then, call setOrientation()
on the IInAppMessage
object in the beforeInAppMessageDisplayed()
delegate method.
1
2
3
4
5
public InAppMessageOperation beforeInAppMessageDisplayed(IInAppMessage inAppMessage) {
// Set the orientation to portrait
inAppMessage.setOrientation(Orientation.PORTRAIT);
return InAppMessageOperation.DISPLAY_NOW;
}
GIFs
Braze requires an external image library to display animated GIFs with in-app messages.
Custom Image Library Integration
Braze offers the ability to use a custom image library to display animated GIFs with in-app messages.
Note: 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 GlideAppboyImageLoader implements IAppboyImageLoader {
private static final String TAG = GlideAppboyImageLoader.class.getName();
private RequestOptions mRequestOptions = new RequestOptions();
@Override
public void renderUrlIntoCardView(Context context, Card card, String imageUrl, ImageView imageView, AppboyViewBounds viewBounds) {
renderUrlIntoView(context, imageUrl, imageView, viewBounds);
}
@Override
public void renderUrlIntoInAppMessageView(Context context, IInAppMessage inAppMessage, String imageUrl, ImageView imageView, AppboyViewBounds viewBounds) {
renderUrlIntoView(context, imageUrl, imageView, viewBounds);
}
@Override
public Bitmap getPushBitmapFromUrl(Context context, Bundle extras, String imageUrl, AppboyViewBounds viewBounds) {
return getBitmapFromUrl(context, imageUrl, viewBounds);
}
@Override
public Bitmap getInAppMessageBitmapFromUrl(Context context, IInAppMessage inAppMessage, String imageUrl, AppboyViewBounds viewBounds) {
return getBitmapFromUrl(context, imageUrl, viewBounds);
}
private void renderUrlIntoView(Context context, String imageUrl, ImageView imageView, AppboyViewBounds viewBounds) {
Glide.with(context)
.load(imageUrl)
.apply(mRequestOptions)
.into(imageView);
}
private Bitmap getBitmapFromUrl(Context context, String imageUrl, AppboyViewBounds 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 GlideAppboyImageLoader : IAppboyImageLoader {
companion object {
private val TAG = GlideAppboyImageLoader::class.qualifiedName
}
private var mRequestOptions = RequestOptions()
override fun renderUrlIntoCardView(context: Context, card: Card, imageUrl: String, imageView: ImageView, viewBounds: AppboyViewBounds) {
renderUrlIntoView(context, imageUrl, imageView, viewBounds)
}
override fun renderUrlIntoInAppMessageView(context: Context, inAppMessage: IInAppMessage, imageUrl: String, imageView: ImageView, viewBounds: AppboyViewBounds) {
renderUrlIntoView(context, imageUrl, imageView, viewBounds)
}
override fun getPushBitmapFromUrl(context: Context, extras: Bundle, imageUrl: String, viewBounds: AppboyViewBounds): Bitmap? {
return getBitmapFromUrl(context, imageUrl, viewBounds)
}
override fun getInAppMessageBitmapFromUrl(context: Context, inAppMessage: IInAppMessage, imageUrl: String, viewBounds: AppboyViewBounds): Bitmap? {
return getBitmapFromUrl(context, imageUrl, viewBounds)
}
private fun renderUrlIntoView(context: Context, imageUrl: String, imageView: ImageView, viewBounds: AppboyViewBounds) {
Glide.with(context)
.load(imageUrl)
.apply(mRequestOptions)
.into(imageView)
}
private fun getBitmapFromUrl(context: Context, imageUrl: String, viewBounds: AppboyViewBounds): 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 setAppboyImageLoader. Note that 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();
Appboy.getInstance(context).setAppboyImageLoader(new GlideAppboyImageLoader());
}
}
1
2
3
4
5
6
class GlideIntegrationApplication : Application() {
override fun onCreate() {
super.onCreate()
Appboy.getInstance(context).appboyImageLoader = GlideAppboyImageLoader()
}
}
Advanced Notes
Android Dialogs
Braze doesn’t support displaying in-app messages in Android Dialogs at this time.
Button Text Capitalization
Android Material Design specifies that Button text should be upper case by default. Braze’s in-app message buttons follow this convention as well.
Youtube in HTML in-app messages
Starting in Braze Android SDK version 2.0.1, Youtube and other HTML5 content can play in HTML in-app messages. This requires hardware acceleration to be enabled in the Activity where the in-app message is being displayed, please see the Android developer guide for more details. Also, that hardware acceleration is only available on API versions 11 and above.