A mobile inbox provides an app-specific place for users to save in-app messages to read later.
Iterable's Android SDK includes a default user interface for a mobile inbox, and you can customize it to match your organization's branding and styles, and to display any necessary fields.
This document describes different ways to customize the mobile inbox provided by Iterable's Android SDK.
# In this article
# Setting up the mobile inbox
Before customizing your app's mobile inbox, read Setting up Mobile Inbox on Android to learn how to set it up and display it.
# Sample app
To better undersand how to customize your app's mobile inbox, take a look at the code in the Inbox Customization sample project (found in the same GitHub repository as Iterable's Android SDK).
# Customizing the mobile inbox
This section describes how to customize the user interface of the mobile inbox embedded in your Android mobile app.
NOTE
Some customizations require you to create a subclass of IterableInboxFragment,
and others do not.
# Empty state
In an empty mobile inbox, you can display custom text (title and body) to help orient your users. These values are blank by default, and they'll wrap to multiple lines if needed. For example:
Use this code to set this text when using the mobile inbox fragment:
Kotlin
var bundle = Bundle() bundle.putString(IterableConstants.NO_MESSAGES_TITLE,"No saved messages") bundle.putString(IterableConstants.NO_MESSAGES_BODY, "Check again later!") val fragment: Fragment = Fragment.instantiate(this, IterableInboxFragment::class.java.name, bundle))
Use this code to set the text when using the mobile inbox activity:
Kotlin
var intent = Intent(this.context,IterableInboxActivity::class.java) intent.putExtra(IterableConstants.NO_MESSAGES_TITLE, "No saved messages") intent.putExtra(IterableConstants.NO_MESSAGES_BODY, "Check again later!") startActivity(intent)
Java
startActivity( new Intent(getApplicationContext(),IterableInboxActivity.class) .putExtra(IterableConstants.NO_MESSAGES_TITLE,"No saved messages") .putExtra(IterableConstants.NO_MESSAGES_BODY,"Check again later!") );
# Message display style (popup or navigation)
A mobile inbox can display messages as popups directly in the inbox view (the default) or as standalone activities. To change this setting, either:
-
Set an extra for the activity's intent:
Kotlin
val intent = Intent(context, IterableInboxActivity::class.java) intent.putExtra("inboxMode", InboxMode.ACTIVITY) startActivity(intent)
Java
Intent intent = new Intent(getContext(), IterableInboxActivity.class); intent.putExtra("inboxMode", InboxMode.ACTIVITY); startActivity(intent);
-
Pass constructor parameters to the fragment:
Kotlin
val inboxFragment = IterableInboxFragment.newInstance(InboxMode.ACTIVITY, 0)
Java
IterableInboxFragment inboxFragment = IterableInboxFragment.newInstance(InboxMode.ACTIVITY)
# Activity title
When launching the mobile inbox as an activity, change the title by passing an
activityTitle argument in the intent:
Kotlin
val intent = Intent(context, IterableInboxActivity::class.java) intent.putExtra("activityTitle", "My Inbox") startActivity(intent)
Java
Intent intent = new Intent(getContext(), IterableInboxActivity.class); intent.putExtra("activityTitle", "My Inbox"); startActivity(intent);
# Inbox toolbar (SDK v3.9.0 and above)
Starting with SDK version 3.9.0, you can display an optional toolbar above the
inbox list using IterableInboxToolbarView. The toolbar is off by default,
so the inbox behaves exactly as it did in previous SDK versions unless you opt
in.
Configure the toolbar with the InboxToolbarOption sealed interface, which has
these options:
-
None(default) — No toolbar. -
Default— A title-only toolbar above the inbox list. -
WithBackButton— A title plus a back-navigation icon. By default, the back action callsOnBackPressedDispatcher. To customize it, have your hostActivityor parentFragmentimplementIterableInboxToolbarBackListener. -
Custom(layoutRes)— Inflates your own toolbar layout. To wire your layout to the SDK, tag views with these reserved IDs (both are optional):-
@id/iterable_reserved_inbox_toolbar_action— Automatically wired to the SDK's back handler. -
@id/iterable_reserved_inbox_toolbar_title— Automatically bound to the toolbar title.
-
IMPORTANT
When the toolbar is enabled, the host activity must use a Theme.AppCompat
descendant.
# Configure the toolbar on the fragment
Pass an InboxToolbarOption (and, optionally, a title) to
IterableInboxFragment.newInstance(...):
Kotlin
val inboxFragment = IterableInboxFragment.newInstance( InboxToolbarOption.WithBackButton, "My Inbox" )
Java
IterableInboxFragment inboxFragment = IterableInboxFragment.newInstance( InboxToolbarOption.WithBackButton.INSTANCE, "My Inbox" );
# Configure the toolbar on the activity
When launching the inbox as an activity, set the TOOLBAR_OPTION and
TOOLBAR_TITLE intent extras:
Kotlin
val intent = Intent(context, IterableInboxActivity::class.java) intent.putExtra(IterableInboxFragment.TOOLBAR_OPTION, InboxToolbarOption.WithBackButton) intent.putExtra(IterableInboxFragment.TOOLBAR_TITLE, "My Inbox") startActivity(intent)
Java
Intent intent = new Intent(getContext(), IterableInboxActivity.class); intent.putExtra(IterableInboxFragment.TOOLBAR_OPTION, InboxToolbarOption.WithBackButton.INSTANCE); intent.putExtra(IterableInboxFragment.TOOLBAR_TITLE, "My Inbox"); startActivity(intent);
# Cell layout, colors, and font
TIP
In the sample app, tap Inbox with Custom Cell to see an example of an inbox that uses custom cells.
To modify the font, color or layout of inbox cells:
Copy the
iterable_inbox_item.xmllayout file fromiterableapi-ui. Give it a new name, such ascustom_inbox_item.xml.In the new file, change the layout, colors and fonts to match your app’s styles.
-
Specify this layout ID when launching the activity:
Kotlin
val intent = Intent(context, IterableInboxActivity::class.java) intent.putExtra("itemLayoutId", R.layout.custom_inbox_item) startActivity(intent)
Java
Intent intent = new Intent(getContext(), IterableInboxActivity.class); intent.putExtra("itemLayoutId", R.layout.custom_inbox_item); startActivity(intent);
-
Alternatively, create the fragment with custom parameters:
Kotlin
val inboxFragment = IterableInboxFragment.newInstance(InboxMode.POPUP, R.layout.custom_inbox_item)
Java
IterableInboxFragment inboxFragment = IterableInboxFragment.newInstance(InboxMode.POPUP, R.layout.custom_inbox_item);
# Date format and visibility
TIP
In the sample app, tap Change Date Format to see an example of an inbox that uses custom cells.
To change the format or visibility of the date field for each message cell,
subclass IterableInboxFragment and set a date mapper in onCreate. The date
mapper takes an IterableInAppMessage and returns a string representing the
creation date of the message. If the date field should be blank, return null.
Kotlin
class CustomInboxDateMapperFragment : IterableInboxFragment() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setDateMapper { message -> DateUtils.getRelativeTimeSpanString( message.createdAt.time, Date().time, 0, DateUtils.FORMAT_ABBREV_ALL ) } } }
Java
public class CustomInboxDateMapperJavaFragment extends IterableInboxFragment implements IterableInboxDateMapper { @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setDateMapper(this); } @Nullable @Override public CharSequence mapMessageToDateString(@NonNull IterableInAppMessage message) { return DateUtils.getRelativeTimeSpanString( message.getCreatedAt().getTime(), new Date().getTime(), 0, DateUtils.FORMAT_ABBREV_ALL ); } }
# Filtering messages
TIP
In the sample app, tap Filter by Message Type or Filter by Message Title to see an example of an inbox that uses custom filtering.
To filter which messages are displayed in the mobile inbox, subclass
IterableInboxFragment and call setFilter in the onCreate method. The filter
should take an IterableInAppMessage and return a boolean: true to show
the message, false otherwise.
IterableInboxFilter is an interface that declares a filter method.
Kotlin
class CustomInboxFilterFragment : IterableInboxFragment() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setFilter { message -> message.customPayload?.has("price") == true } } }
Kotlin (alternative implementation):
class CustomInboxFilterFragment : IterableInboxFragment(), IterableInboxFilter { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setFilter(this) } override fun filter(message: IterableInAppMessage): Boolean { return message.customPayload?.has("price") == true } }
Java
public class CustomInboxFilterFragment extends IterableInboxFragment implements IterableInboxFilter { @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setFilter(this); } @Override public boolean filter(@NonNull IterableInAppMessage message) { JSONObject payload = message.getCustomPayload(); return payload != null && payload.has("price"); } }
# Sorting messages
TIP
In the sample app, tap Sort by Title Ascending or Sort by Date Ascending to see an example of an inbox that changes the way messages are sorted.
By default, Mobile Inbox sorts messages descending by date. However, it is possible to sort the message order in other ways.
To sort the messages in the mobile inbox, subclass IterableInboxFragment and
set a comparator in onCreate. IterableInboxComparator is a standard Java
Comparator interface: return a negative integer, zero, or a positive integer
when the first message is less than, equal to, or greater than the second.
Kotlin
class CustomInboxComparatorFragment : IterableInboxFragment() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setComparator { message1, message2 -> message1.createdAt.compareTo(message2.createdAt) // Sort by creation date ascending } } }
Kotlin (alternative implementation):
class CustomInboxComparatorFragment : IterableInboxFragment(), IterableInboxComparator { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setComparator(this) } override fun compare(message1: IterableInAppMessage, message2: IterableInAppMessage): Int { return message1.createdAt.compareTo(message2.createdAt) // Sort by creation date ascending } }
public class CustomInboxComparatorJavaFragment extends IterableInboxFragment implements IterableInboxComparator { @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setComparator(this); } @Override public int compare(@NonNull IterableInAppMessage message1, @NonNull IterableInAppMessage message2) { // Sort by creation date ascending return message1.getCreatedAt().compareTo(message2.getCreatedAt()); } }
# Adding fields to the inbox cell
TIP
In the sample app, tap Additional Fields to see an example of an inbox that has additional fields on each cell.
To add additional fields to the items displayed in the mobile inbox:
Copy the
iterable_inbox_item.xmllayout file fromiterableapi-ui. Give it a new name, such ascustom_inbox_item.xml.-
Subclass
IterableInboxFragmentand set the adapter extension. The methods are similar to the ones in aRecyclerViewadapter, but instead of aRecyclerView.ViewHolderclass, the extension is a plain Java class. SeeIterableInboxAdapterExtensionfor more details.- Return your custom layout in
getLayoutForViewType. - Create a static inner plain Java class for a
ViewHolderExtension. - Add fields referencing the new views in your layout.
- Create a constructor with calls to
findViewByIdto populate those fields. - In
createViewHolderExtension, call your view holder extension’s constructor and return the result. - In
onBindViewHolder, update the UI for the given inbox message using the standard Iterable ViewHolder (holding references to the standard fields, liketitle,subtitleand others) and your extension object (holding references to your custom views).
- Return your custom layout in
For a reference implementation, see the example in the next section.
# Multiple cell layouts
TIP
In the sample app, tap Multiple Cell Types to see an example of an inbox that uses multiple cell types.
To display different inbox items with different interfaces, follow these steps:
Copy the
iterable_inbox_item.xmllayout file fromiterableapi-ui. Give it a new name, such ascustom_inbox_item.xml.Subclass
IterableInboxFragmentand set the adapter extension. SeeIterableInboxAdapterExtensionfor more details.Create integer constants for every type of cell you’re planning to have in your custom inbox.
Return those constants in
getItemViewTypeby checking the inbox message attributes.The same constants will then be passed to
getLayoutForViewType. Use them to return different layouts based on the view type.
Kotlin
class CustomInboxFieldsFragment : IterableInboxFragment(), IterableInboxAdapterExtension<CustomInboxFieldsFragment.ViewHolder> { val ITEM_TYPE_DEFAULT = 1 val ITEM_TYPE_SALE = 2 override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setAdapterExtension(this) } override fun getItemViewType(message: IterableInAppMessage): Int { if (message.customPayload?.has("price") == true) { return ITEM_TYPE_SALE } else { return ITEM_TYPE_DEFAULT } } override fun getLayoutForViewType(viewType: Int): Int { if (viewType == ITEM_TYPE_SALE) { return R.layout.inbox_item_sale } else { return R.layout.inbox_item_default } } override fun createViewHolderExtension(view: View, viewType: Int): ViewHolder? { if (viewType == ITEM_TYPE_SALE) { return SaleViewHolder(view) } else { return null } } override fun onBindViewHolder(viewHolder: IterableInboxAdapter.ViewHolder, holderExtension: ViewHolder?, message: IterableInAppMessage) { if (holderExtension is SaleViewHolder) { holderExtension.price?.text = message.customPayload?.optString("price") } } open class ViewHolder class SaleViewHolder(view: View) : ViewHolder() { var price: TextView? = null init { this.price = view.findViewById(R.id.price) } } }
Java
public class CustomInboxFieldsJavaFragment extends IterableInboxFragment implements IterableInboxAdapterExtension<CustomInboxFieldsJavaFragment.ViewHolder> { private static final int ITEM_TYPE_DEFAULT = 1; private static final int ITEM_TYPE_SALE = 2; @Override public int getItemViewType(@NonNull IterableInAppMessage message) { JSONObject payload = message.getCustomPayload(); if (payload != null && payload.has("price")) { return ITEM_TYPE_SALE; } else { return ITEM_TYPE_DEFAULT; } } @Override public int getLayoutForViewType(int viewType) { if (viewType == ITEM_TYPE_SALE) { return R.layout.inbox_item_sale; } else { return R.layout.inbox_item_default; } } @Nullable @Override public ViewHolder createViewHolderExtension(@NonNull View view, int viewType) { if (viewType == ITEM_TYPE_SALE) { return new SaleViewHolder(view); } else { return null; } } @Override public void onBindViewHolder(@NonNull IterableInboxAdapter.ViewHolder viewHolder, @Nullable ViewHolder holderExtension, @NonNull IterableInAppMessage message) { if (holderExtension instanceof SaleViewHolder) { SaleViewHolder saleViewHolder = (SaleViewHolder) holderExtension; JSONObject payload = message.getCustomPayload(); if (payload != null) { saleViewHolder.price.setText(payload.optString("price")); } } } static class ViewHolder {} static class SaleViewHolder extends ViewHolder { private TextView price; SaleViewHolder(@NonNull View view) { price = view.findViewById(R.id.price); } } }
# Multiple sections
Mobile Inbox on Android does not provide built-in support for multiple sections.
# Further reading
User guides:
- In-App Messages and Mobile Inbox
- Sending In-App Messages
- Events for In-App Messages and Mobile Inbox
Developer documentation:
- Iterable's iOS SDK
- Iterable's Android SDK
- In-App Messages Overview
- In-App Messages on iOS
- In-App Messages on Android
- Setting up Mobile Inbox on iOS
- Setting up Mobile Inbox on Android
- Customizing Mobile Inbox on iOS
- Animating In-App Messages with CSS
- Image Carousels in In-App Messages
- Testing and Troubleshooting In-App Messages
- In-App Messages Without the SDK
- Getting Started with Iterable's API
- API Endpoints and Sample Payloads