Android In-App Automation Customization

In-app messages are fully customizable. Minor modifications can be accomplished by overriding the styles, but more advanced customizations can employ an adapter for the given message type.

Excluding Activities

Activities can be excluded from auto-displaying an in-app message. This is useful for preventing in-app message displays on screens where it would be detrimental to the user experience, such as splash screens, settings screens, or landing pages.

Exclude activity from displaying in-app messages
<activity
    android:name=".MyActivity">
    <meta-data
        android:name="com.urbanairship.push.iam.EXCLUDE_FROM_AUTO_SHOW"
        android:value="true" />
</activity>

Listening for Events

A listener can be added to the in-app messaging manager to listen for when a message is displayed and finished displaying. This is useful for adding analytics events outside of Airship as well as for further processing of the in-app message.

Example

InAppAutomation.shared().getInAppMessaging().setDisplayDelegate(new InAppMessageDisplayDelegate() {
    @Override
    public boolean isMessageReadyToDisplay(@NonNull InAppMessage message, @NonNull String scheduleId) {
        // Called to check if the message is ready to be displayed
        return true;
    }

    @Override
    public void messageWillDisplay(@NonNull InAppMessage message, @NonNull String scheduleId) {
        // Message displayed
    }

    @Override
    public void messageFinishedDisplaying(@NonNull InAppMessage message, @NonNull String scheduleId) {
        // Message finished
    }
});
InAppAutomation.shared().inAppMessaging.displayDelegate = object : InAppMessageDisplayDelegate {
    override fun isMessageReadyToDisplay(message: InAppMessage, scheduleId: String): Boolean {
        // Called to check if the message is ready to be displayed
        return true
    }

    override fun messageWillDisplay(message: InAppMessage, scheduleId: String) {
        // Message displayed
    }

    override fun messageFinishedDisplaying(message: InAppMessage, scheduleId: String) {
        // Message finished
    }
}

Fonts

Fonts that are added in XML are available for use with in-app messaging, including downloadable fonts. To add fonts, please read the Fonts In XML guide.

After adding fonts to your app, create a Font Stack in the Airship dashboard by following the steps in our Custom fonts guide.

Then you can select the stack when setting in-app message defaults and creating in-app messages.

Styles

The Android resource merging feature can be used to override any of the message styles that the SDK provides. Copy any of the styles that need to be overridden into the application’s resource directory, then change any of the styles.

Styles:

Custom Adapters

Providing an adapter allows full customization for any message type. The adapter will be created by the in-app messaging manager when a message’s schedule is triggered. Once created, the adapter will be called to first prepare the in-app message, giving the adapter time to download any resources such as images. After the adapter prepares the message, the adapter will be called to display the message. Be sure to update the adapter factory for the message type you are trying to override. In this example, the adapter is overriding modal messages, therefore the custom display adapter type is CustomDisplayAdapterType.MODAL.

After the message is displayed, the provided display handler must be notified that the message is finished displaying by returning a CustomDisplayResolution via the suspending method or callback. This will allow other in-app messages to be displayed.

Example adapter

class MyCustomDisplayAdapter implements CustomDisplayAdapter.CallbackAdapter {

    private final InAppMessage message;

    public MyCustomDisplayAdapter(
        Context context,
        InAppMessage message,
        AirshipCachedAssets assets
    ) {
        this.message = message;
    }

    @Override
    public void display(@NonNull Context context, DisplayFinishedCallback callback) {

        // Display the message...

        callback.finished(CustomDisplayResolution.UserDismissed.INSTANCE);
    }

    static void register() {
        InAppAutomation.shared().getInAppMessaging().setAdapterFactoryBlock(
            CustomDisplayAdapterType.MODAL,
            (context, message, assets) -> new MyCustomDisplayAdapter(context, message, assets)
        );
    }
}
class MyCustomDisplayAdapter(
    private val context: Context,
    private val message: InAppMessage,
    private val assets: AirshipCachedAssets,
    private val scope: CoroutineScope
) : CustomDisplayAdapter.SuspendingAdapter {

    // Implement the isReady property, used to signal when the adapter is ready to display the message.
    // If this adapter does not need to wait for anything before displaying the message, you can return 
    // an initial value of true to indicate that it is always ready. Otherwise, set the initial value
    // to false, and emit true once the adapter is ready to display the message.
    override val isReady: StateFlow<Boolean> = MutableStateFlow(true)

    override fun display(context: Context): CustomDisplayResolution {
        // Display the message...

        return CustomDisplayResolution.UserDismissed
    }

    companion object {
        fun register(scope: CoroutineScope) {
            InAppAutomation.shared().inAppMessaging.setAdapterFactoryBlock(
                type = CustomDisplayAdapterType.MODAL,
                factoryBlock = { context, message, assets ->
                    MyCustomDisplayAdapter(context, message, assets, scope)
                }
            )
        }
    }
}

Override the default modal factory to provide the new adapter

MyCustomDisplayAdapter.register();
MyCustomDisplayAdapter.register()

Standard In-App Messages

Standard in-app messages delivered through push messages are managed by the legacy in-app message manager. The manager converts the standard in-app message into a new in-app message schedule. The conversion can be customized by setting a builder extender to extend either the schedule builder or the message builder.

Setting a schedule info builder extender

InAppAutomation.shared().getLegacyInAppMessaging()
    .setScheduleExtender((schedule) ->
        // Return an updated schedule
        schedule.newBuilder()
            .setPriority(10)
            .setLimit(3)
            .build()
    );
InAppAutomation.shared().legacyInAppMessaging
    .scheduleExtender = { schedule ->
        // Return an updated schedule
        schedule.newBuilder()
            .setPriority(10)
            .setLimit(3)
            .build()
    }

Setting a message builder extender

InAppAutomation.shared().getLegacyInAppMessaging()
    .setMessageExtender(message -> {
        JsonMap extras = JsonMap.newBuilder()
            .putAll(message.getExtras())
            .put("custom_key", "custom_value")
            .build();

        // Return an updated message
        return message.newBuilder()
            .setExtras(extras)
            .build();
    });
InAppAutomation.shared().legacyInAppMessaging
    .messageExtender = { message ->
        val extras = JsonMap.newBuilder()
            .putAll(message.getExtras())
            .put("custom_key", "custom_value")
            .build()
    
        // Return an updated message
        message.newBuilder()
            .setExtras(extras)
            .build()
    }

Customizing HTML In-App Messages

 Note

In order for the Airship JavaScript interface to be loaded into the webview, the URL must be specified in the UrlAllowList.

HTML in-app messages provide a way to display custom content inside a native web view. These types of in-app messages display with a dismiss button built in, but can also be customized to provide their own buttons capable of dismissing the view. Dismissing a view requires calling the dismiss function on the UAirship JavaScript interface with a button resolution object passed in as a parameter. The button resolution object is a JSON object containing information about the interaction type and the button performing the dismissal. It should match the following format:

{
    "type" : "button_click",
    "button_info" : {
        "id" : "button identifier",
        "label" : {"text" : "foo"}
    }
}

The button resolution requires each of the key fields shown above. These include:

  • type — The type key with the value of resolution type button_click
  • button_info — The button info object containing required id and label fields
    • id — The button identifier
    • label — Label object containing the required text key
      • text — The text key with a string value representing the label text

Providing a basic dismiss button in HTML:

<button onclick="UAirship.dismiss({
    'type' : 'button_click',
    'button_info' : {
    'id' : 'button identifier',
    'label' : {'text' : 'foo'}
    }
}
);">Dismiss with resolution</button>