Android: Rich Notifications

Marigold supports Android Rich Notifications out of the box for image payloads.

Extend or change default behavior

You can extend or change this default behavior by adding a NotificationCompat.Extender and implementing your own notification style. This is useful if you want to add media controls, or you'd like to render the notification body with an expanded view.

import android.support.v4.app.NotificationCompat;
public class RichPushNotificationExtender implements NotificationCompat.Extender {
  protected static String TAG = "RichPushNotificationExtender";
  protected static String KEY_PAYLOAD_ALERT = "alert";
  protected static String KEY_PAYLOAD_TITLE = "title";
  protected static String KEY_PAYLOAD_IMAGE_URL = "image_url";
  protected static String KEY_PAYLOAD_ST = "_st";
  
  @Override
  public NotificationCompat.Builder extend(NotificationCompat.Builder builder) {
    Bundle bundle = builder.getExtras();
    Context context = builder.mContext;
    
    // For Image or Video in-app messages, we will send the media URL in the
    // _st payload
    String imageUrl = null;
    String stString = bundle.getString(KEY_PAYLOAD_ST);
    if(stString != null && !stString.isEmpty()) {
      try {
        JSONObject stPayload = new JSONObject(stString);
        if (!stPayload.isNull(KEY_PAYLOAD_IMAGE_URL)) {
          imageUrl = stPayload.getString(KEY_PAYLOAD_IMAGE_URL);
        }
      } catch (JSONException e) {
        Log.e(TAG, "Unable to get " + KEY_PAYLOAD_IMAGE_URL + " from '_st' payload: " + stString + ".\n" + e.getLocalizedMessage());
      }
    }
    
    builder.setContentTitle(context.getResources().getString(R.string.app_name))
      .setSmallIcon(R.drawable.st_bg_notification)
      .setContentText(bundle.getString(KEY_PAYLOAD_ALERT));

    if(imageUrl != null && !imageUrl.isEmpty()) {
      Bitmap image = fetchImageForMessage(imageUrl);

      NotificationCompat.BigPictureStyle largeFormatStyle = new NotificationCompat.BigPictureStyle()
        .bigPicture(image)
        .setBigContentTitle(context.getResources().getString(R.string.app_name))
        .setSummaryText(bundle.getString(KEY_PAYLOAD_TITLE))
        .bigLargeIcon(image);
      builder.setLargeIcon(image);
      builder.setStyle(largeFormatStyle);
    }

    return builder;
  }
  
  // code used to load image from url
  private @Nullable Bitmap fetchImageForMessage(@NonNull String imageUrl) {
      URL url;
      try {
          url = new URL(imageUrl);
      } catch (MalformedURLException e) {
          Log.e(TAG, "Malformed image URL in Push Payload: " + e.getLocalizedMessage());
          return null;
      }

      AsyncTask<URL, Void, Bitmap> task = new AsyncTask<URL, Void, Bitmap>() {

          @Override
          protected Bitmap doInBackground(URL... params) {
              try {
                  URL url = params[0];
                  HttpURLConnection connection = (HttpURLConnection) url.openConnection();
                  connection.setDoInput(true);
                  connection.connect();
                  InputStream input = connection.getInputStream();
                  return BitmapFactory.decodeStream(input);
              } catch (IOException e) {
                  Log.e(TAG, "IO Error loading Message image:" + e.getLocalizedMessage());
                  return Bitmap.createBitmap(1, 1, Bitmap.Config.ALPHA_8);
              }
          }

      }.execute(url);
      try {
          return task.get(5, TimeUnit.SECONDS);
      } catch (Exception e) {
          Log.e(TAG, "Failed to wait for Message Image in RichPushNotificationExtender: " + e.getMessage());
          return null;
      }
  }
   
}
import androidx.core.app.NotificationCompat

class RichPushNotificationExtender : NotificationCompat.Extender {
    override fun extend(builder: NotificationCompat.Builder): NotificationCompat.Builder {
        val bundle: Bundle = builder.getExtras()
        val context: Context = builder.mContext

        // For Image or Video in-app messages, we will send the media URL in the
        // _st payload
        var imageUrl: String? = null
        val stString = bundle.getString(KEY_PAYLOAD_ST)
        if (stString != null && !stString.isEmpty()) {
            try {
                val stPayload = JSONObject(stString)
                if (!stPayload.isNull(KEY_PAYLOAD_IMAGE_URL)) {
                    imageUrl = stPayload.getString(KEY_PAYLOAD_IMAGE_URL)
                }
            } catch (e: JSONException) {
                Log.e(TAG, """Unable to get $KEY_PAYLOAD_IMAGE_URL from '_st' payload: $stString.
${e.getLocalizedMessage()}""")
            }
        }
        builder.setContentTitle(context.resources.getString(R.string.app_name))
                .setSmallIcon(R.drawable.st_bg_notification)
                .setContentText(bundle.getString(KEY_PAYLOAD_ALERT))
        if (imageUrl != null && !imageUrl.isEmpty()) {
            val image = fetchImageForMessage(imageUrl)
            val largeFormatStyle: NotificationCompat.BigPictureStyle = NotificationCompat.BigPictureStyle()
                    .bigPicture(image)
                    .setBigContentTitle(context.resources.getString(R.string.app_name))
                    .setSummaryText(bundle.getString(KEY_PAYLOAD_TITLE))
                    .bigLargeIcon(image)
            builder.setLargeIcon(image)
            builder.setStyle(largeFormatStyle)
        }
        return builder
    }

    // code used to load image from url
    private fun fetchImageForMessage(imageUrl: String): Bitmap? {
        val url: URL
        try {
            url = URL(imageUrl)
        } catch (e: MalformedURLException) {
            Log.e(TAG, "Malformed image URL in Push Payload: " + e.getLocalizedMessage())
            return null
        }
        val task: AsyncTask<URL?, Void?, Bitmap?> = object : AsyncTask<URL?, Void?, Bitmap>() {
            override fun doInBackground(vararg params: URL): Bitmap {
                return try {
                    val url: URL = params[0]
                    val connection: HttpURLConnection = url.openConnection() as HttpURLConnection
                    connection.setDoInput(true)
                    connection.connect()
                    val input: InputStream = connection.getInputStream()
                    BitmapFactory.decodeStream(input)
                } catch (e: IOException) {
                    Log.e(TAG, "IO Error loading Message image:" + e.getLocalizedMessage())
                    Bitmap.createBitmap(1, 1, Bitmap.Config.ALPHA_8)
                }
            }
        }.execute(url)
        return try {
            task[5, TimeUnit.SECONDS]
        } catch (e: Exception) {
            Log.e(TAG, "Failed to wait for Message Image in RichPushNotificationExtender: " + e.message)
            null
        }
    }

    companion object {
        protected var TAG = "RichPushNotificationExtender"
        protected var KEY_PAYLOAD_ALERT = "alert"
        protected var KEY_PAYLOAD_TITLE = "title"
        protected var KEY_PAYLOAD_IMAGE_URL = "image_url"
        protected var KEY_PAYLOAD_ST = "_st"
    }
}

Then add your extender to NotificationConfig

public class MyApplication extends Application {
  @Override
  public void onCreate() {
    super.onCreate();

    Marigold marigold = new Marigold();
    marigold.startEngine(getApplicationContext(), "your sdk key");

    NotificationConfig notificationConfig = new NotificationConfig();
    notificationConfig.addNotificationExtender(new RichPushNotificationExtender());
    marigold.setNotificationConfig(notificationConfig);
  }
}
class MyApplication : Application() {
    fun onCreate() {
        super.onCreate()
        val marigold = Marigold()
        marigold.startEngine(applicationContext, "your sdk key")
        val notificationConfig = NotificationConfig()
        notificationConfig.addNotificationExtender(RichPushNotificationExtender())
        marigold.setNotificationConfig(notificationConfig)
    }
}