In-App Messages Migration

In SDK 9.0 we introduced a revamped In-App Messaging solution, In-App Automation, which adds extensive templating, server-backed guaranteed delivery, and automation capabilities. However, existing customers may want to continue sending In-App Message banners in push notifications. To support this, the SDK transparently converts these into the new automated message format and schedules them. In most cases this will work out of the box, requiring little to no migration. However there are a few edge cases where a custom approach might be helpful, and the SDK provides a mechanism for overriding or supplementing the message conversion process. This guide will cover a few of these cases, with example code for both iOS and Android.

Note

Note:

For the sake of brevity and to avoid confusion, we will be referring to In-App Message banners sent in push notifications as legacy banners.

To Migrate or Not to Migrate

Prior to SDK 9.0, the standard behavior for legacy banners was to store an incoming message as pending, and display it on the subsequent app foreground. In cases where this behavior was undesirable, the developer had the option of setting flags on the In-App Messaging manager class, such as displayASAPEnabled or autoDisplayEnabled. Enabling the former would instead cause messages to be displayed immediately on delivery, and disabling the latter would block the SDK from automatically displaying the message, so that the app could choose the right time and place, using the displayPendingMessage method.

In SDK 9.0, legacy banners default to immediate display, and the flags and methods mentioned above have been removed, along with properties for setting default fonts, colors and so on. We believe the new defaults will cover the majority of use cases, but in the event this behavior needs customization, we've provided a way to intercept these banners as they are processed by the SDK, so that the app can either tweak or override their behavior.

Legacy In-App Message Banner Conversion: An Overview

When a legacy banner is received by the SDK, it's converted into the new, automated message format and scheduled. These messages can be triggered in many different ways, ranging from simple app state transitions, to screen tracking events. Hooking into this flow is just a few lines of code. On iOS, it involves registering a class implementing the UALegacyInAppMessageFactoryDelegate protocol. The easiest way to do this is to create a new class and assign the delegate in its init method:

@implementation MessageFactoryDelegate

+(instancetype)messageFactoryDelegate {
    return [[MessageFactoryDelegate alloc] init];
}

-(instancetype)init {
    self = [super init];

    if (self) {
        // Initialize delegate
        [UAirship legacyInAppMessaging].factoryDelegate = self;
    }

    return self;
}

-(UAInAppMessageScheduleInfo *)scheduleInfoForMessage:(UALegacyInAppMessage *)message {
    // Convert the legacy banner into an automated message here
}

@end

On Android it looks rather similar, but uses the extender pattern instead of a delegate callback:

UAirship.shared()
        .getLegacyInAppMessageManager()
        .setScheduleBuilderExtender(new LegacyInAppMessageManager.ScheduleInfoBuilderExtender() {
            @NonNull
            @Override
            public InAppMessageScheduleInfo.Builder extend(Context context, InAppMessageScheduleInfo.Builder builder, LegacyInAppMessage legacyMessage) {
                // Provide any changes to the builder here
                return builder;
            }
        });

In both cases the basic idea is the same, which is that the application has the opportunity to decide how legacy banners are converted into automated message schedules, at any level of granularity. This could include scheduling new message types, such as modal and fullscreen, or adding additional media content. However for our purposes, we will be focusing on banners.

Postponing Message Display Until Next Foreground

Key to automated in-app messages are trigger types. Because these messages are automated, the SDK is mostly responsible for deciding when the message will be displayed, but the trigger type defines how that decision will be made. The default type is active session, which will fire immediately if the app is in the foreground, or wait until the next foreground if not. This directly corresponds to enabling the displayAsap flag in older SDKs. Reverting to the old default behavior of always waiting until the next foreground is easy, and simply involves swapping out the trigger for the foreground type.

On iOS, using our message factory delegate, we can call back into the UALegacyInAppMessaging class to convert the legacy banner into its default automated message schedule, and with a few extra lines of code, reconfigure it with the desired trigger:

- (UAInAppMessageScheduleInfo *)scheduleInfoForMessage:(UALegacyInAppMessage *)message {
    UAInAppMessageScheduleInfo *defaultScheduleInfo = [[UAirship legacyInAppMessaging] scheduleInfoForMessage:message];

    UAScheduleTrigger *trigger = [UAScheduleTrigger foregroundTriggerWithCount:1];

    UAInAppMessageScheduleInfo *myScheduleInfo = [UAInAppMessageScheduleInfo scheduleInfoWithBuilderBlock:^(UAInAppMessageScheduleInfoBuilder * _Nonnull builder) {
        builder.triggers = @[trigger];
        builder.end = message.expiry;
        builder.message = defaultScheduleInfo.message;
    }];

    return myScheduleInfo;
}

On Android the process is similar, but as outlined in the previous section we'll do this using an extender:

UAirship.shared()
    .getLegacyInAppMessageManager()
    .setScheduleBuilderExtender(new LegacyInAppMessageManager.ScheduleInfoBuilderExtender() {
        @NonNull
        @Override
        public InAppMessageScheduleInfo.Builder extend(Context context, InAppMessageScheduleInfo.Builder builder, LegacyInAppMessage legacyMessage) {
            Trigger foregroundTrigger = Triggers.newForegroundTriggerBuilder()
                                                .setGoal(1)
                                                .build();
            builder.setTriggers(Arrays.asList(foregroundTrigger));
            return builder;
        }
    });

Manually Displaying Pending Messages

Because In-App Messages are automated in SDK 9, and because the automation pipeline supports multiple pending messages, the old displayPendingMessage method no longer makes sense. However we can still achieve the same result by tying messages to custom event triggers, and firing the associated custom event when we want them to be displayed. This is very similar to approach shown above.

On iOS we can assign a custom event trigger as below:

- (UAInAppMessageScheduleInfo *)scheduleInfoForMessage:(UALegacyInAppMessage *)message {
    UAInAppMessageScheduleInfo *defaultScheduleInfo = [[UAirship legacyInAppMessaging] scheduleInfoForMessage:message];

    UAJSONValueMatcher *valueMatcher = [UAJSONValueMatcher matcherWhereStringEquals:@"displayLegacyBanner"];
    UAJSONMatcher *jsonMatcher = [UAJSONMatcher matcherWithValueMatcher:valueMatcher key:UACustomEventNameKey];
    UAJSONPredicate *predicate = [UAJSONPredicate predicateWithJSONMatcher:jsonMatcher];
    UAScheduleTrigger *trigger = [UAScheduleTrigger customEventTriggerWithPredicate:predicate count:1];

    UAInAppMessageScheduleInfo *myScheduleInfo = [UAInAppMessageScheduleInfo scheduleInfoWithBuilderBlock:^(UAInAppMessageScheduleInfoBuilder * _Nonnull builder) {
        builder.triggers = @[trigger];
        builder.end = message.expiry;
        builder.message = defaultScheduleInfo.message;
    }];

    return myScheduleInfo;
}

And later, when it is time to fire the event:

UACustomEvent *event = [UACustomEvent eventWithName:@"displayLegacyBanner"];
[[UAirship analytics] addEvent:event];

On Android, as above we provide a custom event trigger in the extender:

UAirship.shared()
    .getLegacyInAppMessageManager()
    .setScheduleBuilderExtender(new LegacyInAppMessageManager.ScheduleInfoBuilderExtender() {
        @NonNull
        @Override
        public InAppMessageScheduleInfo.Builder extend(Context context, InAppMessageScheduleInfo.Builder builder, LegacyInAppMessage legacyMessage) {
            Trigger customEventTrigger = Triggers.newCustomEventTriggerBuilder()
                                                 .setEventName("displayLegacyBanner")
                                                 .setCountGoal(1)
                                                 .build();
            builder.setTriggers(Arrays.asList(customEventTrigger));
            return builder;
        }
    });

Likewise, when it is time to fire the event:

CustomEvent event = new CustomEvent.Builder("displayLegacyBanner").create();
event.track();

Of course, since these are custom events the developer has quite a bit of flexibility in how to approach this. For instance, by using key/value pairs in the legacy banner extras, different banners could be tied to different custom events, and triggered individually. But the above approach is enough to duplicate the behavior of displayPendingMessage as found on older SDKs.

Tutorials