Skip to main content

Custom Message Types

tip

Starting from SDK version 5.6.7, the MessageContent.java class 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 these fields yourself. Custom messages from both old and new versions are compatible and can interoperate. For customers already using the legacy custom message system, if you need to add new custom message types, it is recommended to use the new version of custom messages.

The client SDK uses the [Message] object to represent sent and received messages. The [Message] class encapsulates the [MessageContent] object, which represents the specific content of the message.

The concept of message types is implemented through subclasses of [MessageContent]. The SDK provides default implementations for basic message types such as text, image, voice, video, and file (see [Built-in Message Types]). If the built-in message types do not meet your requirements, you can define custom message types.

To implement a custom message, you must inherit from one of the following abstract classes:

  • [MessageContent]: Represents regular message content. For example, text messages and location messages in the SDK's built-in message types.
  • [MediaMessageContent]: Represents media-type messages. Media-type message content inherits from [MessageContent] and adds logic for handling multimedia files. When sending and receiving messages, the SDK checks if the message type is a media type. If it is, the SDK triggers the upload or download process for the multimedia file.
tip

The type and structure of custom messages must be consistent across different platforms to avoid interoperability issues.

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 Messages: Regular Message

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

// 1. Custom message implementation with 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 constructor">
private MyTextContent() {}

// Quick method to build 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() {
// Example of carrying "user info" or "@ mention" info
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;
}

/** Create 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);
// Example of carrying "user info" or "@ mention" info
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 attributes, writing the class data into the provided Parcel
super.writeToBaseInfoParcel(dest);
ParcelUtils.writeToParcel(dest, content);
}
/**
* Constructor.
*
* @param in The Parcel passed in for initialization.
*/
public MyTextContent(Parcel in) {
// Example of serializing "user info", "@ mention" info, etc.
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">
/**
* Set 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 a custom regular message. Create a subclass of MessageContent.

  1. Custom message types 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, it is recommended to keep it within 16 characters. Note: Do not start with RC: (reserved prefix for RC), as it will conflict with RC's built-in messages.
    flagint(Required) The storage and counting properties of the message. See Flag Description below for details. 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.

    flag Parameter affects client and server storage behaviors. The details are as follows:

    flag ValueApplicable Scenarios
    MessageTag.NONESuch messages are not saved to the client's local message database and do not increment the unread count. Supports offline messages?. Typically used for command messages that do not need to be displayed. For example, a command message from an operations platform to a terminal, notifying the terminal to perform an action.
    MessageTag.ISPERSISTEDSuch messages are saved to the client's local message database but do not increment the unread count. Supports offline messages? and are stored in the server's historical messages. Typically used for gray bar messages that need to be displayed in the UI but do not increment the unread count.
    MessageTag.ISCOUNTEDSuch messages are saved to the client's local message database and increment the unread count. Supports offline messages? and are stored in the server's historical messages. Examples include text and image messages.
    MessageTag.STATUSStatus messages are not stored on either the client or server and do not increment the unread count. They are only used to convey instant status, such as typing status. The recipient can receive the message if online; if offline, the server discards the message and does not push it. Therefore, if the recipient is offline, they cannot receive the status message again; it will not be resent after reinstallation.
  2. Add internal variables as needed. The content field in the example code is an internal variable of the custom message, used to store the message content.

    private String content;
  3. Implement the public constructor for other classes to call.

    // Quick method to build a message object
    public static MyTextContent obtain(String content) {
    MyTextContent msg = new MyTextContent();
    msg.content = content;
    return msg;
    }
  4. Implement binary encoding and decoding methods for converting between binary data and message objects. There are two methods (Encode and Decode).

    tip

    The parent class MessageContent.java encapsulates the encoding, decoding, and serialization methods for the user (user information), mentionedInfo (mention information), and extra (additional information) fields. If your custom message needs to carry this information, you can refer to the example code comments to call the parent class methods for parsing.

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

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

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

      // Call the parent class method to convert basic fields into JSONObject
      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 {
      // 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 the custom message content.

      The parsing process (Decode) is the reverse of the encoding process: byte[] => JSON String => JSONObject => Message object

      /** Create MyMyTextContent(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);

      // Call the parent class's parseBaseJsonObjec() method to parse basic fields
      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());
      }
      }
  5. Implement Parcel serialization methods. This set of methods is used for cross-process transmission of message objects. There are four methods in total.

    Use the SDK's ParcelUtils utility class to implement the serialization and deserialization of MyTextContent.

    Important

    • The parent class MessageContent.java encapsulates the encoding, decoding, and serialization methods for the user (user information), mentionedInfo (mention information), and extra (additional information) fields. If your custom message needs to carry this information, you can refer to the example code comments to call the parent class methods for serialization.
    • The number and order of Parcel reads and writes in serialization must correspond exactly.

    /**
    * Writes the class data into the provided Parcel.
    *
    * @param dest The Parcel the object is being written into.
    * @param flags Additional flags about how the object should be written, may be 0 or PARCELABLE_WRITE_RETURN_VALUE.
    */
    @Override
    public void writeToParcel(Parcel dest, int flags) {
    // Write parent class variables
    super.writeToBaseInfoParcel(dest);
    // Write internal variables
    ParcelUtils.writeToParcel(dest, content);
    }

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

    /**
    * Describes the special objects contained in this Parcelable instance's marshaled representation.
    *
    * @return A bitmask indicating the set of special object types marshaled by this Parcelable object instance.
    */
    public int describeContents() {
    return 0;
    }

    /** Interface for reading, to construct an instance of a Parcelable class from a 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];
    }
    };
  6. Implement Getter and Setter methods for custom internal variables.

        /**
    * Set 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;
    }

Example Code for Custom Messages: Media Message

tip

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

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

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

/**
* Generates a MyMediaMessageContent object.
*
* @param localUri The media file address.
* @return An instance of MyMediaMessageContent.
*/
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 of carrying "user info" or "@ mention" info
JSONObject jsonObj = super.getBaseJsonObject();

try {

if (getLocalUri() != null) {
/** Unless you do not use the SDK's built-in upload logic, the `localPath` property in the JSON 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 of carrying "user info" or "@ mention" info
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 into the provided Parcel.
*
* @param dest The Parcel the object is being written into.
* @param flags Additional flags about how the object should be written, may be 0 or PARCELABLE_WRITE_RETURN_VALUE.
*/
@Override
public void writeToParcel(Parcel dest, int flags) {
// Serialize message attributes, writing the class data into the provided Parcel
super.writeToBaseInfoParcel(dest);
ParcelUtils.writeToParcel(dest, getLocalPath());
}

/**
* Constructor.
*
* @param in The Parcel passed in for initialization.
*/
public MyMediaMessageContent(Parcel in) {
// Example of serializing "user info", "@