To learn more about creating and managing Mobile Apps, read our Mobile App Profiles page.
Note: The minimum version for Maropost's Mobile SDK for Android is Ice Cream Sandwich (Android 15).
In this Article
Install the Maropost Mobile SDK for Android
You can download the mobile SDK configuration and setup files from MMC and install these files in your Android staging apps.
- From the Maropost Marketing Cloud Account, click on your profile and select Settings.

- Visit the Mobile Apps Profile section.

- Choose a mobile SDK from the index page whose setup files you want to download.

- Now, download the .Zip and configuration files for your Android SDK. Finally, add these files to your Android app as a library.

Adding the Framework
- Extract MPPushSDK.zip to reveal the MPPushSDK.aar and the MPFcmRegistrationListener.java files. Right-click in your app and then select Open Module Settings.

- On the top-left corner of the screen, click the New Module button. Select the "Add .JAR/.AAR Package option.

- Navigate to where you had extracted the MPPushSDK.zip file and select the MPPushSDK.aar file.

- For the app module, click the Dependencies tab and add MPPushSDK as a module dependency.

- In the same Dependencies tab, add com.google.android.gms:play-services:version as a Library Dependency (if not already present). In your root package, add MPFcmListenerService.java and set the values according to your project for package, YourMainActivity, R.drawable.your_app_icon_file, and your_app_name. Use your Maropost app key, app ID, and account ID within AndroidManifest.xml.
The app key, app ID, and account ID are found in the following places:

- Finally, you'll need to create a new API authentication token for use by the mobile SDK. This is an additional layer of required security. Read more about creating API authentication tokens. When you create the authentication token, please ensure that you've given it privileges for Push Notifications.
Modify AndroidManifest.xml
Add the following items to your AndroidManifest.xml file:
//if in production use string(“Production”)
The FCM_SERVER_KEY and FCM_SENDER_ID are located in the API management section of your Google Developer's Console. This is what your Google Developer's Console will show after enabling Cloud Messaging:

Dependencies
Update your app level build.gradle script to include the code and add dependencies in the code as follows:
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
exclude group: 'com.android.support', module: 'support-annotations'
})
implementation 'com.android.support:appcompat-v7:27.1.1'
implementation 'com.android.support.constraint:constraint-layout:1.0.2'
testImplementation 'junit:junit:4.12'
// Implement Kotlin Plugin
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
// For SDK Support, mandatory gradle plugins
implementation 'com.google.android.gms:play-services-location:16.0.0'
implementation 'com.google.firebase:firebase-messaging:17.3.4'
implementation 'com.google.firebase:firebase-core:16.0.4'
implementation 'com.squareup.okhttp3:okhttp:3.10.0'
// Get access to the SDK with .aar
implementation project(':MPPushSDK')
}
apply plugin: 'com.google.gms.google-services'
Update your project level build.gradle. Add the Kotlin extension under buildscript and Kotlin gradle plugin under dependencies. Example:
buildscript {
ext.kotlin_version = '1.2.71'
repositories {
mavenCentral()
jcenter()
google()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.2.1'
classpath 'com.google.gms:google-services:4.1.0'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
Use Firebase Messaging Service to Get Device Tokens and Handle Rich Push Messages
MyFirebaseMessagingService
For Kotlin
class MyFirebaseMessagingService : FirebaseMessagingService() {
private var title : String = ""
private var subTitle : String = ""
private var message : String = ""
private var deepLinkUrl : String = ""
private var campaignId : String = ""
private var imageUrl : String = ""
private var customData : String = ""
override fun onNewToken(p0: String?) {
super.onNewToken(p0)
}
override fun onMessageReceived(remoteMessage: RemoteMessage?) {
title = "Maropost"
subTitle = ""
message = "This is a test notification"
deepLinkUrl = ""
campaignId = ""
imageUrl = ""
customData = ""
if (remoteMessage != null && remoteMessage.data.isNotEmpty()) {
if(!TextUtils.isEmpty(remoteMessage.data["title"]))
title = remoteMessage.data["title"]!!
if(!TextUtils.isEmpty(remoteMessage.data["subtitle"]))
title = remoteMessage.data["subtitle"]!!
if(!TextUtils.isEmpty(remoteMessage.data["message"]))
message = remoteMessage.data["message"]!!
if(!TextUtils.isEmpty(remoteMessage.data["deeplinkurl"]))
deepLinkUrl = remoteMessage.data["deeplinkurl"]!!
if(!TextUtils.isEmpty(remoteMessage.data["message_id"]))
campaignId = remoteMessage.data["message_id"]!!
if(!TextUtils.isEmpty(remoteMessage.data["attachment-url"]))
// imageUrl is the image media url for rich notification
imageUrl = remoteMessage.data["attachment-url"]!!
if(!TextUtils.isEmpty(remoteMessage.data["test"]))
customData = remoteMessage.data["test"]!!
// test is the key mentioned in the custom data payload
// The key could be any depending on the requirement.
//For test purpose, the key 'test' is used
// Build notification
if(TextUtils.isEmpty(imageUrl))
createSimpleNotification()
else createRichNotification()
}
}
/**
* Generate a simple notification without any bitmap
*/
private fun createSimpleNotification() {
addNotification(applicationContext,null)
}
/**
* Generate a rich notification with a bitmap to be set as bigPicture
*/
private fun createRichNotification() {
RichPushAsync().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR)
}
/**
* Create custom notification
*/
private fun addNotification(context: Context, bitmap: Bitmap?) {
val mIntent = Intent(context, MainActivity::class.java)
if (!TextUtils.isEmpty(deepLinkUrl))
mIntent.putExtra(getString(R.string.pending_intent_deeplink_key), deepLinkUrl)
if (!TextUtils.isEmpty(campaignId))
mIntent.putExtra(getString(R.string.campaignId), campaignId)
if (!TextUtils.isEmpty(customData))
mIntent.putExtra(getString(R.string.customData), customData)
val pendingIntent =
PendingIntent.getActivity(applicationContext, System.currentTimeMillis().toInt(),
mIntent, PendingIntent.FLAG_UPDATE_CURRENT)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE)
as NotificationManager
val id = "id_product"
val name = "Product"
// The user-visible description of the channel.
val description = "Notifications regarding our product"
val importance = NotificationManager.IMPORTANCE_MAX
val mChannel = NotificationChannel(id, name, importance)
// Configure the notification channel.
mChannel.description = description
notificationManager.createNotificationChannel(mChannel)
val notificationBuilder = NotificationCompat.Builder(context, "id_product")
.setChannelId(id)
.setContentTitle(title) // Title of Notification
.setContentText(message) // Message body of Notification
.setAutoCancel(true)
.setSmallIcon(R.drawable.ic_launcher)
.setContentIntent(pendingIntent)
if(bitmap != null) {
val style = NotificationCompat.BigPictureStyle()
.bigPicture(bitmap)
notificationBuilder.setStyle(style)
}
notificationManager.notify(System.currentTimeMillis().toInt(), notificationBuilder.build())
} else {
// OS version less than Oreo
val mBuilder = NotificationCompat.Builder(context)
.setContentTitle(title) // Title of Notification
.setContentText(message) // Message body of Notification
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setContentIntent(pendingIntent)
.setAutoCancel(true)
.setSmallIcon(R.drawable.ic_launcher)
if(bitmap != null) {
val style = NotificationCompat.BigPictureStyle()
.bigPicture(bitmap)
mBuilder.setStyle(style)
}
val mNotificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE)
as NotificationManager
mNotificationManager.notify(System.currentTimeMillis().toInt(), mBuilder.build())
}
}
inner class RichPushAsync : AsyncTask() {
override fun doInBackground(vararg params: Void): Bitmap? {
return try {
// You may choose your own way to load bitmap from url
// or use a third party app for downloading
BitmapFactory.decodeStream((URL(imageUrl)).content as InputStream)
} catch (e: Exception) {
e.printStackTrace()
null
}
}
override fun onPostExecute(bitmap: Bitmap?) {
super.onPostExecute(bitmap)
addNotification(applicationContext,bitmap)
}
}
}
For Java
public class FirebaseServiceJava extends FirebaseMessagingService {
private String title = "Maropost";
private String subTitle = "";
private String message = "This is a test notification";
private String deepLinkUrl = "";
private String campaignId = "";
private String imageUrl = "";
private String customData = "";
@Override
public void onNewToken(String s) {
super.onNewToken(s);
}
@Override
public void onMessageReceived(RemoteMessage remoteMessage) {
super.onMessageReceived(remoteMessage);
title = "Maropost";
subTitle = "";
message = "This is a test notification";
deepLinkUrl = "";
campaignId = "";
imageUrl = "";
customData = "";
if (remoteMessage != null && !remoteMessage.getData().isEmpty()) {
if (!TextUtils.isEmpty(remoteMessage.getData().get("title")))
title = remoteMessage.getData().get("title");
if (!TextUtils.isEmpty(remoteMessage.getData().get("subtitle")))
subTitle = remoteMessage.getData().get("subtitle");
if (!TextUtils.isEmpty(remoteMessage.getData().get("message")))
message = remoteMessage.getData().get("message");
if (!TextUtils.isEmpty(remoteMessage.getData().get("deeplinkurl")))
deepLinkUrl = remoteMessage.getData().get("deeplinkurl");
if (!TextUtils.isEmpty(remoteMessage.getData().get("message_id")))
campaignId = remoteMessage.getData().get("message_id");
if (!TextUtils.isEmpty(remoteMessage.getData().get("attachment-url")))
imageUrl = remoteMessage.getData().get("attachment-url");
if (!TextUtils.isEmpty(remoteMessage.getData().get("test")))
customData = remoteMessage.getData().get("test");
// test is the key mentioned in the custom data payload
// The key could be any depending on the requirement.
// For test purpose, the key 'test' is used
// Build notification
if(TextUtils.isEmpty(imageUrl))
createSimpleNotification();
else createRichNotification();
}
}
/**
* Generate a simple notification without any bitmap
*/
private void createSimpleNotification() {
addNotification(getApplicationContext(),null);
}
/**
* Generate a rich notification with a bitmap to be set as bigPicture
*/
private void createRichNotification() {
new RichPushAsync().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
/**
* Create custom notification
*/
private void addNotification(Context context, Bitmap bitmap) {
Intent mIntent = new Intent(context, MainActivity.class);
if (!TextUtils.isEmpty(deepLinkUrl))
mIntent.putExtra(getString(R.string.pending_intent_deeplink_key), deepLinkUrl);
if (!TextUtils.isEmpty(campaignId))
mIntent.putExtra(getString(R.string.campaignId), campaignId);
if (!TextUtils.isEmpty(customData))
mIntent.putExtra(getString(R.string.customData), customData);
PendingIntent pendingIntent = PendingIntent.getActivity(getApplicationContext(), 0, mIntent,
PendingIntent.FLAG_UPDATE_CURRENT);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationManager notificationManager =
(NotificationManager)context.getSystemService(Context.NOTIFICATION_SERVICE);
String id = "id_product";
// The user-visible name of the channel.
String name = "Product";
// The user-visible description of the channel.
String description = "Notifications regarding our product";
int importance = NotificationManager.IMPORTANCE_MAX;
NotificationChannel mChannel = new NotificationChannel(id, name, importance);
// Configure the notification channel.
mChannel.setDescription(description);
notificationManager.createNotificationChannel(mChannel);
NotificationCompat.Builder notificationBuilder =
new NotificationCompat.Builder(getApplicationContext(), "id_product")
.setChannelId(id)
.setContentTitle(title) // Title of Notification
.setContentText(message) // Message body of Notification
.setAutoCancel(true)
.setSmallIcon(R.drawable.ic_launcher) // Can place your own icon
.setContentIntent(pendingIntent);
if(bitmap != null) {
NotificationCompat.BigPictureStyle style = new NotificationCompat.BigPictureStyle()
.bigPicture(bitmap);
notificationBuilder.setStyle(style);
}
notificationManager.notify(1, notificationBuilder.build());
} else {
// OS version less than Oreo
NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(this)
.setContentTitle(title) // Title of Notification
.setContentText(message) // Message body of Notification
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setContentIntent(pendingIntent)
.setAutoCancel(true)
.setSmallIcon(R.drawable.ic_launcher); // Can place your own icon
if(bitmap != null) {
NotificationCompat.BigPictureStyle style = new NotificationCompat.BigPictureStyle()
.bigPicture(bitmap);
mBuilder.setStyle(style);
}
NotificationManager mNotificationManager =
(NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE);
mNotificationManager.notify(1, mBuilder.build());
}
}
class RichPushAsync extends AsyncTask {
@Override
protected Bitmap doInBackground(Void... voids) {
try {
// You may choose your own way to load bitmap from url
// or use a third party app for downloading
return BitmapFactory.decodeStream((InputStream) new URL(imageUrl).getContent());
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
@Override
protected void onPostExecute(Bitmap bitmap) {
super.onPostExecute(bitmap);
addNotification(getApplicationContext(),bitmap);
}
}
}
📋 Note: There is no need to include FirebaseInstanceIdService if you have included the latest version of Firebase as mentioned in build.gradle. Similarly, you can avoid registering it in the Manifest as well. onNewToken() is available with FirebaseMessagingService itself.
Start up MPPush Services
In your MainActivity.java, import the SDK class com.maropost.mppush.MPPush. The MPPush SDK requries only a single entry point into the app delegate, which is startup. In your MainActivity.java, locate the onCreate() method. Initialize a shared MPPush instance by calling MPPush.shared().startup(this,”YOUR_API_KEY”).
For Kotlin
class MainActivity : AppCompatActivity() {
private val LOCATION = 1
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
askForPermission(android.Manifest.permission.ACCESS_FINE_LOCATION, LOCATION)
}
private fun askForPermission(permission: String, requestCode: Int?) {
if (ContextCompat.checkSelfPermission(this@MainActivity, permission) != PackageManager.PERMISSION_GRANTED) {
// Should we show an explanation?
if (ActivityCompat.shouldShowRequestPermissionRationale(this@MainActivity, permission)) {
//This is called if user has denied the permission before
//In this case I am just asking the permission again
ActivityCompat.requestPermissions(this@MainActivity, arrayOf(permission), requestCode!!)
} else {
ActivityCompat.requestPermissions(this@MainActivity, arrayOf(permission), requestCode!!)
}
} else {
setUpSDK()
}
}
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (ActivityCompat.checkSelfPermission(this, permissions[0]) == PackageManager.PERMISSION_GRANTED) {
when (requestCode) {
//Location
1 -> setUpSDK()
}
}
else
setUpSDK()
}
fun setUpSDK(){
MPPush.shared().startup(this,"YOUR_API_KEY");
val primaryColorValue = Color.parseColor("#00ff00")
val secondaryColorValue = Color.parseColor("#E91E63")
// Set the desired font to use when displaying in-app messages.
val typeface = Typeface.createFromAsset(assets, resources.getString(R.string.font_name))
MPPush.shared().setInAppFont(typeface as Typeface)
// Toggle whether the SDK tracks users by location (Not done by default):
MPPush.shared().trackLocation(true) //Required for location based segments
// Link device with an email ID (if available) for domain based segmentation
MPPush.shared().setUserEmail("user@example.com");
// Set the device user's full name (if available)
MPPush.shared().setUserFullName("John", "Appleseed");
// Add a tag to current device for segmentation
MPPush.shared().addUserTag("NAME_OF_TAG_TO_ADD")
// Remove a tag for current device
MPPush.shared().removeUserTag("NAME_OF_TAG_TO_REMOVE")
// Set the desired primary color(text color) to use when displaying in-app messages.
MPPush.shared().setPrimaryInAppColor(primaryColorValue)
// Set the desired secondary color(background color) to use when displaying in-app messages.
MPPush.shared().setSecondaryInAppColor(secondaryColorValue)
// Manually invoke when to display in-app messages in case auto-display is turned off
MPPush.shared().displayPendingInAppMessages()
}
}
For Java
public class MainActivity extends AppCompatActivity{
private int LOCATION = 1;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
askForPermission(android.Manifest.permission.ACCESS_FINE_LOCATION, LOCATION);
}
private void askForPermission(String permission, int location) {
if (ContextCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED) {
// Should we show an explanation?
if (ActivityCompat.shouldShowRequestPermissionRationale(this, permission)) {
//This is called if user has denied the permission before
//In this case I am just asking the permission again
ActivityCompat.requestPermissions(this, new String[]{permission}, requestCode);
} else {
ActivityCompat.requestPermissions(this, new String[]{permission}, requestCode);
}
}else{
setUpSDK();
}
}
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if(ActivityCompat.checkSelfPermission(this, permissions[0]) == PackageManager.PERMISSION_GRANTED){
switch (requestCode) {
//Location
case 1:
setUpSDK();
break;
}
}
else setUpSDK();
}
/**
* Initialise your SDK
*/
private void setUpSDK(){
// Sandbox
MPPush.shared().startup(this,"YOUR_API_KEY");
int primaryColorValue = Color.parseColor("#00ff00");
int secondaryColorValue = Color.parseColor("#E91E63");
// Set the desired font to use when displaying in-app messages.
Typeface typeface = Typeface.createFromAsset(getAssets(), getResources().getString(R.string.font_name));
MPPush.shared().setInAppFont(typeface);
// Toggle whether the SDK tracks users by location (Not done by default):
MPPush.shared().trackLocation(true); //Required for location based segments
// Link device with an email ID (if available) for domain based segmentation
MPPush.shared().setUserEmail("user@example.com");
// Set the device user's full name (if available)
MPPush.shared().setUserFullName("John", "Appleseed");
// Add a tag to current device for segmentation
MPPush.shared().addUserTag("NAME_OF_TAG_TO_ADD");
// Remove a tag for current device
MPPush.shared().removeUserTag("NAME_OF_TAG_TO_REMOVE");
// Set the desired primary color(text color) to use when displaying in-app messages.
MPPush.shared().setPrimaryInAppColor(primaryColorValue);
// Set the desired secondary color(background color) to use when displaying in-app messages.
MPPush.shared().setSecondaryInAppColor(secondaryColorValue);
// Manually invoke when to display in-app messages in case auto-display is turned off
MPPush.shared().displayPendingInAppMessages();
}
}
Finally, build your project. If there are no errors, then you are all set.
📋 Note: In order to access the location, permission for ‘ACCESS_FINE_LOCATION’ is mandatory to support ‘High Accuracy’ priority for the location requests.
Deep Linking and Rich Push Messaging
From a push message, use deep linking to guide the user to a specific screen in the mobile app. For example: If you have a sports app that reports on game scores, you can employ push messaging to send users final scores when their favorite teams have finished a game. Your deep linking here shouldn’t direct the user to your app home screen; it should take him or her directly to the results screen. When the push notification is tapped, consume the data from the Pending intent in MainActivity inside the overridden method onNewIntent().
For Kotlin
override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
if(intent != null && !TextUtils.isEmpty(intent.getStringExtra(getString(R.string.pending_intent_deeplink_key)))) // Same Key as used while adding extras to the pending intent
// Consume the deeplink from intent and redirect it to the browser
}
For Java
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
if(intent != null && !TextUtils.isEmpty(intent.getStringExtra(getString(R.string.pending_intent_deeplink_key)))) // Same Key as used while adding extras to the pending intent
// Consume the deeplink from intent and redirect it to the browser
}
📋 Note: onNewIntent() will be called if the app is running. If the app is killed and then the notification is clicked, then the data can be consumed from the intent in onCreate().
For Kotlin
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
askForPermission(android.Manifest.permission.ACCESS_FINE_LOCATION, LOCATION)
val deeplinkUrl = intent.getStringExtra(getString(R.string.pending_intent_deeplink_key))
if(!TextUtils.isEmpty(deeplinkUrl)){
// Consume the deeplink from intent and redirect it to the browser
}
}
For Java
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
askForPermission(android.Manifest.permission.ACCESS_FINE_LOCATION, LOCATION)
val deeplinkUrl = intent.getStringExtra(getString(R.string.pending_intent_deeplink_key))
if(!TextUtils.isEmpty(deeplinkUrl)){
// Consume the deeplink from intent and redirect it to the browser
}