Skip to main content

Custom Message Types

Important

This document applies to SDK versions 5.6.7 and later. If your SDK version is < 5.6.7, please use Custom Messages (Legacy).

tip

Starting from SDK version 5.6.7, MessageContent.java encapsulates the encoding/decoding and serialization methods for the user (user information), mentionedInfo (@mention information), and extra (additional information) fields. When implementing custom messages, you can directly call the parent class's parsing methods without needing to parse them yourself. The new and legacy versions of custom messages are compatible and can interoperate. For customers already using legacy custom messages who need to add new custom message types, we recommend using the new version of custom messages.

In addition to using the built-in message types in the IMLib SDK, you can also create custom message types.

You need to choose the message base class to inherit from based on your business requirements:

  • [MessageContent]: This is the base class for ordinary message content. For example, text messages and location messages in the SDK's built-in message types.
  • [MediaMessageContent]: This is the base class for media-type messages. Media-type message content inherits from [MessageContent] and adds processing logic for multimedia files. When sending and receiving messages, the SDK will check whether the message type is a media-type message. If it is, the upload or download process for multimedia files will be triggered.

For more information about message entity classes and message content, see Message Introduction.

tip

The type and structure of custom messages must be consistent across all clients; otherwise, interoperability issues may occur.

Creating Custom Messages

The SDK does not handle the definition and parsing of custom message content; you need to implement this yourself.

The @MessageTag ([MessageTag]) of a custom message type determines its unique identifier (objectname) and properties such as whether it is stored, displayed, or counted in the unread message count.

Example Code for Custom Ordinary Messages

Below is the complete example code for a custom ordinary message.

// 1. Custom message implementing the MessageTag annotation
@MessageTag(value = "app:txtcontent", flag = MessageTag.ISCOUNTED)
public class MyTextContent extends MessageContent {
// <editor-fold desc="* 2. Internal variables, can have multiple">
private static final String TAG = "appTextContent";
private String content;
// </editor-fold>

// <editor-fold desc="* 3. Public constructors">
private MyTextContent() {}

// Quick method to construct a message object
public static MyTextContent obtain(String content) {
MyTextContent msg = new MyTextContent();
msg.content = content;
return msg;
}
// </editor-fold>

// <editor-fold desc="* 4. Binary Encode & decode methods">

/**
* Serializes the local message object into message data.
*
* @return The message data.
*/
@Override
public byte[] encode() {
// Here, we take the example of needing to carry "user information" or "@mention" information
JSONObject jsonObj = super.getBaseJsonObject();
try {
// Serialize all custom message content into the JSON object
jsonObj.put("content", this.content);
} catch (JSONException e) {
Log.e(TAG, "JSONException " + e.getMessage());
}

try {
return jsonObj.toString().getBytes("UTF-8");
} catch (UnsupportedEncodingException e) {
Log.e(TAG, "UnsupportedEncodingException ", e);
}
return null;
}

/** Creates a MyTextContent(byte[] data) constructor with byte[] for parsing message content. */
public MyTextContent(byte[] data) {
if (data == null) {
return;
}
String jsonStr = null;
try {
jsonStr = new String(data, "UTF-8");
} catch (UnsupportedEncodingException e) {
Log.e(TAG, "UnsupportedEncodingException ", e);
}
if (jsonStr == null) {
Log.e(TAG, "jsonStr is null ");
return;
}

try {
JSONObject jsonObj = new JSONObject(jsonStr);
// Here, we take the example of needing to parse "user information" or "@mention" information
super.parseBaseJsonObject(jsonObj);
// Parse and assign all custom variables from the received JSON
if (jsonObj.has("content")) {
content = jsonObj.optString("content");
}
} catch (JSONException e) {
Log.e(TAG, "JSONException " + e.getMessage());
}
}

// </editor-fold>

// <editor-fold desc="* 5. Parcel serialization methods">

@Override
public void writeToParcel(Parcel dest, int i) {
// Serialize message properties, writing the class's data into the externally provided Parcel
// Here, we take the example of needing to serialize "user information" and "@mention information"
super.writeToBaseInfoParcel(dest);
ParcelUtils.writeToParcel(dest, content);
}
/**
* Constructor.
*
* @param in The Parcel passed in during initialization.
*/
public MyTextContent(Parcel in) {
// Here, we take the example of needing to deserialize "user information" and "@mention information"
super.readFromBaseInfoParcel(in);
setContent(ParcelUtils.readFromParcel(in));
}

public static final Creator<MyTextContent> CREATOR =
new Creator<MyTextContent>() {
public MyTextContent createFromParcel(Parcel source) {
return new MyTextContent(source);
}

public MyTextContent[] newArray(int size) {
return new MyTextContent[size];
}
};
@Override
public int describeContents() {
return 0;
}
// </editor-fold>

// <editor-fold desc="* 6. get & set methods">
/**
* Sets the content of the text message.
*
* @param content The content of the text message.
*/
public void setContent(String content) {
this.content = content;
}

public String getContent() {
return content;
}
// </editor-fold>

}

Detailed Explanation of Example Code

The following explains the example code for custom ordinary messages. Create a subclass of MessageContent.

  1. For custom message types, you must use the @MessageTag annotation.

    The @MessageTag annotation requires the following parameters:

    ParameterTypeDescription
    valueString(Required) The unique identifier for the message type, e.g., app:txtcontent. To minimize the impact on message size, we recommend keeping it under 16 characters. Note: Do not start with RC: (reserved prefix for official messages), as this will conflict with RC's built-in messages.
    flagint(Required) The storage and counting properties of the message. For possible values, see Flag Explanation below. Note: Both client and server storage behaviors are affected by this property.
    messageHandlerClass<? extends MessageHandler>(Optional) By default, the SDK's default messageHandler is used. For custom media message types, if the size after encode exceeds 128 KB, you need to use a custom messageHandler.

    The flag parameter affects client and server storage behaviors, as explained below:

Flag ValueUse Case
MessageTag.NONEMessages of this type are neither saved to the client's local message database nor counted as unread. Supports offline messages?. Typically used for command messages that don't require display, such as operational platform instructions to client devices.
MessageTag.ISPERSISTEDMessages of this type are saved to the client's local message database but not counted as unread. Supports offline messages? and stored in server history. Commonly used for gray bar notifications that require UI display without incrementing unread counts.
MessageTag.ISCOUNTEDMessages of this type are saved to the client's local message database and increment unread counts. Supports offline messages? and stored in server history. Examples include text and image messages.
MessageTag.STATUSStatus messages are neither stored on client nor server, nor counted as unread. Used exclusively for real-time status updates (e.g., typing indicators). Recipients online will receive these messages; offline recipients will have them discarded by the server without push. Thus, offline recipients cannot receive status messages later, nor will they be re-delivered after app reinstall.
  1. Add internal variables as needed (multiple allowed). The content field in the sample code serves as an internal variable for storing message content.

    private String content;
  2. Implement public constructors for external class invocation.

    // Quick message object construction method
    public static MyTextContent obtain(String content) {
    MyTextContent msg = new MyTextContent();
    msg.content = content;
    return msg;
    }
  3. Implement binary encoding/decoding methods for converting between binary data and message objects (two methods: Encode and Decode).

    tip

    The parent class MessageContent.java encapsulates encoding/decoding and serialization methods for user (user info), mentionedInfo (mention info), and extra (additional info) fields. For custom messages needing these fields, reference the sample code comments to invoke parent class methods.

    1. Override the parent class's encode method to convert message objects to byte[].

      Process: Message object => JSONObject => JSON String => byte[]

      /**
      * Serializes local message object into message data.
      *
      * @return Message data.
      */
      @Override
      public byte[] encode() {

      // Invoke parent method to convert base fields to JSONObject
      JSONObject jsonObj = super.getBaseJsonObject();
      try {
      // Serialize all custom message variables into JSON object
      jsonObj.put("content", this.content);
      } catch (JSONException e) {
      Log.e(TAG, "JSONException " + e.getMessage());
      }

      try {
      // Convert jsonObj to binary
      return jsonObj.toString().getBytes("UTF-8");
      } catch (UnsupportedEncodingException e) {
      Log.e(TAG, "UnsupportedEncodingException ", e);
      }
      return null;
      }
    2. Override the parent class's MessageContent(byte[] data) constructor to parse custom message content.

      Decoding process is the inverse of encoding: byte[] => JSON String => JSONObject => Message object

      /** Constructor MyMyTextContent(byte[] data) for parsing message content from byte[]. */
      public MyTextContent(byte[] data) {
      if (data == null) {
      return;
      }
      String jsonStr = null;
      try {
      jsonStr = new String(data, "UTF-8");
      } catch (UnsupportedEncodingException e) {
      Log.e(TAG, "UnsupportedEncodingException ", e);
      }
      if (jsonStr == null) {
      Log.e(TAG, "jsonStr is null ");
      return;
      }

      try {
      JSONObject jsonObj = new JSONObject(jsonStr);

      // Must invoke parent's parseBaseJsonObjec() to parse base fields
      super.parseBaseJsonObject(jsonObj);

      // Parse and assign all custom variables from JSON
      if (jsonObj.has("content")) {
      content = jsonObj.optString("content");
      }
      } catch (JSONException e) {
      Log.e(TAG, "JSONException " + e.getMessage());
      }
      }
  4. Implement Parcel serialization methods (four methods total) for cross-process message object transmission.

    Use SDK's ParcelUtils to implement MyTextContent serialization/deserialization.

    Important

    • The parent class MessageContent.java encapsulates serialization methods for user, mentionedInfo, and extra fields. Reference sample code comments to invoke parent methods if needed.
    • Parcel read/write operations must maintain strict order and quantity matching.

    /**
    * Writes class data to external Parcel.
    *
    * @param dest Parcel receiving object data.
    * @param flags Additional flags for writing (0 or PARCELABLE_WRITE_RETURN_VALUE).
    */
    @Override
    public void writeToParcel(Parcel dest, int flags) {
    // Write parent variables
    super.writeToBaseInfoParcel(dest);
    // Write internal variables
    ParcelUtils.writeToParcel(dest, content);
    }

    /**
    * Constructor.
    *
    * @param in Initialization Parcel.
    */
    public MyTextContent(Parcel in) {
    // Read parent variables
    super.readFromBaseInfoParcel(in);
    // Read internal variables
    setContent(ParcelUtils.readFromParcel(in));
    }

    /**
    * Describes special object types in Parcelable's object representation.
    *
    * @return Bitmask indicating special object type set.
    */
    public int describeContents() {
    return 0;
    }

    /** Interface for constructing Parcelable class instances from Parcel. */
    public static final Creator<MyTextContent> CREATOR =
    new Creator<MyTextContent>() {

    @Override
    public TextMessage createFromParcel(Parcel source) {
    return new MyTextContent(source);
    }

    @Override
    public TextMessage[] newArray(int size) {
    return new MyTextContent[size];
    }
    };
  5. Implement Getter and Setter methods for custom internal variables.

        /**
* Sets the content of the text message.
*
* @param content The content of the text message.
*/
public void setContent(String content) {
this.content = content;
}

public String getContent() {
return content;
}

Custom Message Example Code: Media Message

tip

Unless you're not using the SDK's built-in upload logic, the JSON's localPath property must have a value.

// 1. Custom message implements MessageTag annotation
@MessageTag(value = "app:mediacontent", flag = MessageTag.ISCOUNTED)
public class MyMediaMessageContent extends MediaMessageContent {
// <editor-fold desc="* 2. Internal variables (multiple allowed). Media messages can directly use MediaMessageContent's localPath">

// </editor-fold>
// <editor-fold desc="* 3. Public constructors">
public MyMediaMessageContent(Uri localUri) {
setLocalPath(localUri);
}

/**
* Creates a MyMediaMessageContent object.
*
* @param localUri Media file URI.
* @return MyMediaMessageContent object instance.
*/
public static MyMediaMessageContent obtain(Uri localUri) {
return new MyMediaMessageContent(localUri);
}
// </editor-fold>

// <editor-fold desc="* 4. Binary Encode & decode methods">

@Override
public byte[] encode() {
//Example showing how to carry "user info" or "@ mentions"
JSONObject jsonObj = super.getBaseJsonObject();

try {

if (getLocalUri() != null) {
/** Unless not using SDK's built-in upload logic, JSON's `localPath` property must have a value. */
jsonObj.put("localPath", getLocalUri().toString());
}
} catch (JSONException e) {
Log.e("JSONException", e.getMessage());
}
return jsonObj.toString().getBytes();
}

public MyMediaMessageContent(byte[] data) {
String jsonStr = new String(data);

try {
JSONObject jsonObj = new JSONObject(jsonStr);
//Example showing how to carry "user info" or "@ mentions"
super.parseBaseJsonObject(jsonObj);
if (jsonObj.has("localPath")) {
setLocalPath(Uri.parse(jsonObj.optString("localPath")));
}

} catch (JSONException e) {
Log.e("JSONException", e.getMessage());
}
}
// </editor-fold>

// <editor-fold desc="* 5. Parcel serialization methods">

/**
* Writes the class data to the provided Parcel.
*
* @param dest The Parcel where the object should be written.
* @param flags Additional flags about how the object should be written (0 or PARCELABLE_WRITE_RETURN_VALUE).
*/
@Override
public void writeToParcel(Parcel dest, int flags) {
// Serializes message attributes into the Parcel (example showing "user info" and "@ mentions")
super.writeToBaseInfoParcel(dest);
ParcelUtils.writeToParcel(dest, getLocalPath());
}

/**
* Constructor.
*
* @param in The input Parcel for initialization.
*/
public MyMediaMessageContent(Parcel in) {
// Example showing serialization of "user info" and "@ mentions"
super.readFromBaseInfoParcel(in);
setLocalPath(ParcelUtils.readFromParcel(in, Uri.class));
}

/** Reader interface for constructing Parcelable class instances from Parcel. */
public static final Creator<MyMediaMessageContent> CREATOR =
new Creator<MyMediaMessageContent>() {

@Override
public MyMediaMessageContent createFromParcel(Parcel source) {
return new MyMediaMessageContent(source);
}

@Override
public MyMediaMessageContent[] newArray(int size) {
return new MyMediaMessageContent[size];
}
};

/**
* Describes special object types contained in this Parcelable instance.
*
* @return A bitmask indicating special object types.
*/
@Override
public int describeContents() {
return 0;
}

// </editor-fold>

// <editor-fold desc="* 6. Getter & setter methods">

/**
* Gets the local image URI (file:///).
*
* @return The local image URI (file:///).
*/
public Uri getLocalUri() {
return getLocalPath();
}

/**
* Sets the local image URI (file:///).
*
* @param localUri The local image URI (file:///).
*/
public void setLocalUri(Uri localUri) {
setLocalPath(localUri);
}
// </editor-fold>
}

Registering Custom Messages

You need to call [registerMessageType] to register custom message types before establishing an IM connection, so the SDK can recognize these message types. Otherwise, messages will be treated as UnknownMessage.

tip

Custom messages must be registered before connection. Recommended to call during the application lifecycle.

Example for registering custom messages MyTextContent.class and MyMessage.class:

ArrayList<Class<? extends MessageContent>> myMessages = new ArrayList<>();
myMessages.add(MyTextContent.class);
myMessages.add(MyMessage.class);
RongIMClient.registerMessageType(myMessages);
ParameterTypeDescription
messageContentClassListList<Class<? extends MessageContent>>List of custom message classes.

Sending Custom Messages

Custom message types can be sent using the same methods as built-in message types. Please select the appropriate core class and method based on your SDK version, business needs, and message type:

  • If the custom message type inherits MessageContent, use the interface for sending regular messages.

  • If the custom message type inherits MediaMessageContent, use the interface for sending media messages.

If the custom message type needs to support push notifications, you must specify push content (pushContent) when sending the message. This content will be displayed in the notification bar when the recipient receives the push.

  • You can directly specify push content through the pushContent parameter when sending messages.

  • Alternatively, you can configure personalized push settings by setting pushContent and other fields in [Message]'s MessagePushConfig. The MessagePushConfig settings take precedence.

For specific methods and configurations for sending messages, please refer to the following documentation:

  • App with IMLib SDK only: [Send Messages][imlib-发送消息] (one-to-one chat, group chat, chatroom), [Send and Receive Messages] (ultra group)

  • App with IMKit SDK: [Send Messages][imkit-发送消息] (one-to-one chat, group chat, chatroom)

tip
  • If RC server cannot obtain the custom message's pushContent, message push cannot be triggered. For example, the recipient will not receive push notifications when offline.
  • If the custom message type is a status message, push notifications are not supported and no additional push content needs to be specified.