Mobile Landings allow you to easily introduce continuity between your app, and your pushes: A user opening a push will be greeted by a rich message related to what they opened, rather than just ending up on your app's main menu.
Mobile landings visual example
Displaying the message
Fully automatic mode
There's no code required to make mobile landings work in automatic mode once you satisfy the requirements: Just attach a landing to your push campaign, and Batch will display it.
You might want to go further into this documentation, setup your analytics listener or add some Custom Actions.
The automatic mode is only guaranteed to work properly if Batch is well integrated into ALL of your activities. Activities with special launchModes (such as singleTask, ...) might require a more manual integration due to platform limitations.
Automatic mode, with a custom receiver
If you're using a custom push receiver, Batch will automatically display landings when a notification is opened. However, landings will not appear when the app is in the foreground as opposed to the fully automatic mode.
For that, you'll need to implement foreground push yourself. Here's how Batch does it, which will probably work for you:
Track if your app is in the foreground or background. That's done by counting onPause and onResume events, using a ActivityLifecycleCallbacks implementation
In the push service, check this value. If the app is in the foreground and the push contains a landing, start an activity that will display the message. You can also use a LocalBroadcastManager to communicate with your foreground app.
The SDK comes with com.batch.android.MessagingActivity, which is a public api: feel free to use it. MessagingActivity.startActivityForMessage() is the static method used to correctly configure this activity and show it for a given landing message.
Once that's handled, simply make sure you call Batch.Push.displayNotification() or Batch.Push.appendBatchData() depending on how custom your receiver is, and that Batch is started in the activity opened by the notification.
Manual mode
You may want to be in control of if, when and how the landings will be loaded and displayed. Batch allows you to disable automatic displaying, and handle loading and displaying of the fragment itself.
Most integrations do not need to use this mode unless the app you're integrating into has some specificities, such as: games, non native-frameworks, activities with special launchMode, etc. The Automatic mode, with a custom receiver section might be what you're looking for, rather than doing a fully manual integration.
First, you'll need to disable the automatic mode, in your Application class:
class SampleApplication : Application(), Messaging.LifecycleListener {
override fun onCreate() {
super.onCreate()
// [...]
Batch.Messaging.setAutomaticMode(false)
}
}
public class SampleApplication extends Application implements Messaging.LifecycleListener {
@Override
public void onCreate() {
super.onCreate();
// [...]
Batch.Messaging.setAutomaticMode(false);
}
}
Then, you need to ask Batch to load the right fragment or view for the push payload (if applicable), and display it:
class MainActivity : FragmentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// [...]
val extras = intent.extras
if (extras != null) {
try {
val pushPayload = BatchPushPayload.payloadFromBundle(extras)
if (pushPayload.hasLandingMessage()) {
val message = pushPayload.landingMessage
try {
val messagingView = Batch.Messaging.loadMessagingView(this, message)
when (messagingView.kind) {
BatchMessagingView.Kind.Fragment -> messagingView.showFragment(supportFragmentManager, "batch-landing")
BatchMessagingView.Kind.View -> messagingView.showView(this)
}
return
} catch (e: BatchMessagingException) {
e.printStackTrace()
}
}
} catch (e: BatchPushPayload.ParsingException) {
e.printStackTrace()
}
}
}
}
public class MainActivity extends FragmentActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// [...]
final Bundle extras = getIntent().getExtras();
if (extras != null) {
try {
BatchPushPayload pushPayload = BatchPushPayload.payloadFromBundle(extras);
if (pushPayload.hasLandingMessage()) {
try {
BatchMessage message = pushPayload.getLandingMessage();
Batch.Messaging.show(this, message);
// Alternatively, you can load and show the message yourself
BatchMessagingView messagingView = Batch.Messaging.loadMessagingView(this, message);
switch (messagingView.getKind()) {
case Fragment:
messagingView.showFragment(getSupportFragmentManager(), "batch-landing");
break;
case View:
messagingView.showView(this);
break;
}
}
catch (BatchMessagingException e) {
e.printStackTrace();
}
}
} catch (BatchPushPayload.ParsingException e) {
e.printStackTrace();
}
}
}
}
You'll need to use the Support Fragment Manager to display the message fragment, rather than the standard one.
Controlling the display using "Do Not Disturb mode"
Batch 1.10 adds a "Do Not Disturb" (DnD) feature: It allows you to tell Batch to hold on a mobile landing for you, rather than display it without using the fully manual mode.
For example, if launching your app results in a splash screen or a fullscreen ad, you might find it undesirable to have Batch display something on top of it.
Turning on "Do Not Disturb" mode will make Batch enqueue the latest mobile landing, rather than display it.
Toggling DnD
Now, when you don't want Batch to automatically display, turn on Do Not Disturb:
Batch.Messaging.setDoNotDisturbEnabled(true)
Batch.Messaging.setDoNotDisturbEnabled(true);
Once you want to start showing landings automatically, call the method with false to turn it off.
Disabling Do Not Disturb mode does NOT make Batch show the enqueued message
Displaying pending mobile landings
After coming back from DnD mode, you might want to show the enqueued message, as Batch will not do that automatically. Batch exposes two methods for managing the queue:
Batch.Messaging.hasPendingMessage() , allowing you to peek into the queue.
Batch.Messaging.popPendingMessage() , allowing you to fetch the pending message (if any). Since calling this makes Batch delete its reference to it to save memory, further calls might return null.
Here is a quick example of how they can be used:
fun splashScreenDismissed(context: Context) {
Batch.Messaging.setDoNotDisturbEnabled(false)
val message: BatchMessage? = Batch.Messaging.popPendingMessage()
message?.let {
Batch.Messaging.show(context, it)
}
}
public void splashScreenDismissed(Context context) {
Batch.Messaging.setDoNotDisturbEnabled(false);
BatchMessage msg = Batch.Messaging.popPendingMessage();
if (msg != null) {
Batch.Messaging.show(context, msg);
}
}
Only the latest message is queued: if a mobile landing arrives while one is still pending, it will overwrite the previous one.
Excluding activities
It is possible to mark activites as excluded from Batch's lifecycle handling to prevent them from displaying mobile landings temporarily: the landing will then be enqueued and displayed over the next activity.
For example, if launching your app results in a splash screen or a fullscreen ad, you might find it undesirable to have Batch display something on top of it.
To do so, add the following meta-data your AndroidManifest.xml :
Listening to lifecycle events and button callbacks
Setting up a listener
Batch's messaging module supports setting up a listener, which can be used for analytics purposes.
It can be any object that implements the Batch.Messaging.LifecycleListener interface, such as your application instance.
class SampleApplication : Application(), Batch.Messaging.LifecycleListener {
override fun onCreate() {
super.onCreate()
// [...]
Batch.Messaging.setLifecycleListener(this)
}
}
public class SampleApplication extends Application implements Batch.Messaging.LifecycleListener {
@Override
public void onCreate() {
super.onCreate();
// [...]
Batch.Messaging.setLifecycleListener(this);
}
}
Analytics listener
Batch can notify your listener of lifecycle events of the in-app messages:
class SampleApplication : Application(), Batch.Messaging.LifecycleListener {
override fun onBatchMessageShown(messageIdentifier: String?) {
Log.i("App", "Message shown $messageIdentifier")
}
override fun onBatchMessageActionTriggered(
messageIdentifier: String?,
ctaIdentifier: String?,
action: BatchMessageAction
) {
Log.i("App","Message action triggered " +messageIdentifier +", id: " + ctaIdentifier + ", action " + action.action)
// If the action is a CTA (button), you can fetch the label
if (action is BatchMessageCTA) {
val label = action.label
}
// CTAs are allowed to simply be a "dismiss" action, in which case they
// will not have any associated action name and arguments
val isDismissAction = action.isDismissAction
if (!isDismissAction) {
val actionName = action.action // not null
}
}
override fun onBatchMessageClosed(
messageIdentifier: String?,
reason: Batch.Messaging.LifecycleListener.MessagingCloseReason?
) {
// The reason will explain to you why the message has been closed, this can be either:
// - Message automatically closed after a delay.
// - User tapping the close button.
// - User swiping to dismiss.
// - User pressing the system's close button.
// - User clicking on a CTA.
// - An error while loading the message.
Log.i("App", "Message closed: $messageIdentifier with reason $reason")
}
}
public class SampleApplication extends Application implements Batch.Messaging.LifecycleListener {
@Override
public void onBatchMessageShown(@Nullable String messageIdentifier) {
Log.i("App", "Message shown: " + messageIdentifier);
}
@Override
public void onBatchMessageActionTriggered(@Nullable String messageIdentifier, @Nullable String ctaIdentifier, @NonNull BatchMessageAction action) {
Log.i("App", "Message action triggered: " + messageIdentifier + " - " + ctaIdentifier + " - " + action);
// If the action is a CTA (button), you can fetch the label
if (action instanceof BatchMessageCTA) {
String label = ((BatchMessageCTA) action).getLabel();
}
// CTAs are allowed to simply be a "dismiss" action, in which case they
// will not have any associated action name and arguments
boolean isDismissAction = action.isDismissAction();
if (!isDismissAction) {
String actionName = action.getAction(); // not null
}
}
@Override
public void onBatchMessageClosed(@Nullable String messageIdentifier, MessagingCloseReason reason) {
// The reason will explain to you why the message has been closed, this can be either:
// - Message automatically closed after a delay.
// - User tapping the close button.
// - User swiping to dismiss.
// - User pressing the system's close button.
// - User clicking on a CTA.
// - An error while loading the message.
Log.i("App", "Message closed: " + messageIdentifier + " - " + reason);
}
}
Customizing the landing
Setting a custom typeface
If you'd like to use a custom typeface (aka font) instead of the system's, Batch allows you to override the fonts it will use:
// Set a custom typeface
val normalTypeface: Typeface = Typeface.createFromAsset(assets, "MyFont.ttf")
val boldTypeface: Typeface = Typeface.createFromAsset(assets, "MyFont-Bold.ttf")
Batch.Messaging.setTypefaceOverride(normalTypeface, boldTypeface)
// Clear the custom typeface, and use the system one
Batch.Messaging.setTypefaceOverride(normalTypeface, boldTypeface)
// Set a custom typeface
Typeface normalTypeface = Typeface.createFromAsset(getAssets(), "MyFont.ttf");
Typeface boldTypeface = Typeface.createFromAsset(getAssets(), "MyFont-Bold.ttf");
Batch.Messaging.setTypefaceOverride(normalTypeface, boldTypeface);
// Clear the custom typeface, and use the system one
Batch.Messaging.setTypefaceOverride(null, null);
Make sure you provide both a normal and a bold font, even if they are the same.
This assumes you've already know how to make custom Typefaces. It usually simply involves putting fonts in your assets folder.
Troubleshooting
Landings are not displayed, even after opening the notification
First, check your logcat to see if Batch is saying anything. Then, if you're using a custom receiver, you might want to double check that you're calling required Batch methods. Activities with custom launchModes can also interfere: make sure onNewIntent is implemented correctly if you're using a special launchMode.
If you still don't get landings, don't hesitate to hit us up at [email protected], or by using the support button on the lower right corner of this page.
Nothing happens when I press an actionable button
Take a look at your application logs in logcat (either in your Android Studio tab, or by running "adb logcat" in a terminal): the SDK might try to warn you about an issue.
If your action isn't a deeplink but a custom action, please continue to the Custom Actions documentation.