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);
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.xml
layout 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") } } }
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") } }
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) { return message.getCustomPayload().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.xml
layout file fromiterableapi-ui
. Give it a new name, such ascustom_inbox_item.xml
.-
Subclass
IterableInboxFragment
and set the adapter extension. The methods are similar to the ones in aRecyclerView
adapter, but instead of aRecyclerView.ViewHolder
class, the extension is a plain Java class. SeeIterableInboxAdapterExtension
for 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
findViewById
to 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
,subtitle
and 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.xml
layout file fromiterableapi-ui
. Give it a new name, such ascustom_inbox_item.xml
.Subclass
IterableInboxFragment
and set the adapter extension. SeeIterableInboxAdapterExtension
for more details.Create integer constants for every type of cell you’re planning to have in your custom inbox.
Return those constants in
getItemViewType
by 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")) { 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) { if (message.getCustomPayload().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; saleViewHolder.price.setText(message.getCustomPayload().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
- API Overview and Sample Payloads
- API Overview