Messaging

Messages are simply JSON content containers, you can store up to 100KB of JSON data in the message itself, and it will be synchronized with all the users in its channel in real-time. If a message requires larger binary data (sending files, for example), we recomment you to upload the data to another cloud storage service, such as AWS S3, and store the URL to the content in the message data.

In addition to supporting the raw JSON message type, the SDK also provides support for common text and image message types. These additional types are built on top of the standard JSON message layer. In case of image messages, the SDK freely provides a cloud storage service that will process and store all images uploaded, so you don't need to setup and manage a seperate cloud storage service for this common case.

All messaging methods are contained in a EkoMessageRepository class. To get an instance of EkoMessageRepository:

val messageRepository = EkoClient.newMessageRepository();

Sending Messages

All message sending methods are designed to be robust enough to work under any network conditions. When you send any message, that message will automatically be put into a queue in case of unstable network conditions. Once the SDK reconnects to the server, it will automatically resend all queued messages.

Additionally, send messages are always returned in message queries, even before it has delivered to the server. This is to provide the user with a fluid messaging behavior: when a user send a message, that sent message would appear in the message stream right away, instead of waiting until it has been confirmed by the server. To check or display the current status of message delivery, use EkoMessage.getState() method in the EkoMessage object.

To send a message, use EkoMessageRepository.createMessage() method to initiate message creation chain,

messageRepository.createMessage(channelId)

Text Message

Sending a standard text message is simple:

messageRepository.createMessage(channelId)
.with()
.text("Hello Eko!")
.build()
.send()
.subscribe()

Image Message

To send an image message, you must pass in a valid image uri. The SDK will automatically send it to the server. You can also pass in an optional caption as part of the message.

To send an image in original size, set optional isFullImage() to true. Note: File size is limited to 1 GB

messageRepository.createMessage(channelId)
.with()
.image(imageUri)
.caption("It's a beautiful day")
.isFullImage(true)
.build()
.send()
.subscribe()

For more information on Image sizes, please have a look at our documentation on:

File Message

To send a file message, you must pass in a valid file uri. The SDK will automatically send it to the server. You can also pass in an optional caption as part of the message.

Note: File size is limited to 1 GB

messageRepository.createMessage(channelId)
.with()
.file(fileUri)
.caption("It's a beautiful day")
.build()
.send()
.subscribe()

Custom Message

With a custom message, you can send a JsonObect of your choice as part of the message.

val customObject = JsonObject()
customObject.addProperty("url", "https://docs.ekomedia.technology");
messageRepository.createMessage(channelId)
.with()
.custom(customObject)
.build()
.send()
.subscribe()

Message Comment

To comment on a message, specify the messageId with parentId() method

messageRepository.createMessage(channelId)
.parentId(getMessageId())
.with()
.text("Hello Eko!")
.build()
.send()
.subscribe()

Message Query

To query for a list of all messages in a channel:

messageRepository.getMessageCollection(channelId)
.build()
.query()
.subscribe( { adapter.submitList(it) } )

This method will return all messages in the specified channel as Flowable<PagedList<EkoMessage>>

Stack From End

While the SDK will always return messages in chronological order, developers can ask for the messages to be returned starting from the oldest first, or the newest first.

On a typical messaging application, the messages are fetched from the latest (newest) first, and then more older messages are explored. (When stack from end is set to true, the list fills its content starting from the bottom of the view)

messageRepository.getMessageCollection(channelId)
.stackFromEnd(true)
.build()
.query()
.subscribe( { adapter.submitList(it) } )

There are other situations where fetching the oldest messages is preferred, for example for archiving purposes or in community forums. (When stack from end is set to false, the list fills its content starting from the top of the view)

messageRepository.getMessageCollection(channelId)
.stackFromEnd(false)
.build()
.query()
.subscribe( { adapter.submitList(it) } )

Note: Please make sure that stackFromEnd using for obtain MessageCollection is the same as stackFromEnd of RecyclerView's LayoutManager. Otherwise it may cause jumping issue.

Message Filtering

Like channels, we also have various ways to obtain messages that only match specific criteria:

  • the includingTags and excludingTags parameters let you filter messages based on the tags set (or not set) in each message

  • the parentId parameter lets you filter messages by their relationship:

    • when no parentId is passed, any message will match.

    • when null parentId is passed, query for all messages without a parent.

    • when non-null parentId is passed: query for all messages with the parentId as parent.

val includingTags = koTags()
includingTags.add("games")
val excludingTags = EkoTags()
excludingTags.add("staff-only")
// queries for messages
messageRepository.getMessageCollection(channelId)
.stackFromEnd(stackFromEnd)
.build()
.query()
// queries for messages tagged as "games"
messageRepository.getMessageCollection(channelId)
.stackFromEnd(stackFromEnd)
.includingTags(includingTags)
.build()
.query()
// queries for messages tagged as "games" and not tagged as "staff-only"
messageRepository.getMessageCollection(channelId)
.stackFromEnd(stackFromEnd)
.includingTags(includingTags)
.excludingTags(excludingTags)
.build()
.query()
val parentId = "999";
// queries for children messages of a message with id "999"
messageRepository.getMessageCollection(channelId)
.parentId(parentId)
.stackFromEnd(stackFromEnd)
.build()
.query()
// queries for children messages of a message with id "999" and tagged as "games"
messageRepository.getMessageCollection(channelId)
.parentId(parentId)
.stackFromEnd(stackFromEnd)
.includingTags(includingTags)
.build()
.query()
// queries for children messages of a message with id "999", tagged as "games" and not tagged as "staff-only"
messageRepository.getMessageCollection(channelId)
.parentId(parentId)
.stackFromEnd(stackFromEnd)
.includingTags(includingTags)
.excludingTags(excludingTags)
.build()
.query()

In the case where you only want to fetch an individual message from a channel, you can use the getMessage() method:

messageRepository.getMessage(messageId)
.doOnNext { message -> {
}
.subscribe()

Viewing Text Message

val data = message.getData()
when (data) {
is EkoMessage.Data.TEXT -> {
val text = data.getText()
}
}

Viewing Image Message

To get image url from EkoMessage:

val data = message.getData()
when (data) {
is EkoMessage.Data.IMAGE -> {
val imageUrl = data.getImage().getUrl()
}
}

Calling getUrl() without specifying image size returns a url of a medium-size image.

Please use the appropriate image size depending on your usecase in order to save bandwidth.

  • SMALL is used for image thumbnails, with a maximum image size of 160 pixels per dimension. For example, this should be used for small previews in an image gallery that displays a large amount of images in a grid.

  • MEDIUM is used for standard image display, with a maximum image size of 600 pixels per dimension.

  • LARGE is used for full screen image display, with a maximum image size of 1500 pixels per dimension.

  • FULL is used to get the original image. This size is only valid if the image is uploaded with the method isFullImage() set to true. If a FULL sized image is not available, a LARGE sized image will be returned instead.

The image is protected by access token. In order to view an image, the http request for that image needs to be authenticated.

To integrate with Glide and OkHttp3:

  1. Ensure that you have the following gradle dependencies where glideVersion is the latest Glide version:

    implementation "com.github.bumptech.glide:glide:$glideVersion",
    implementation "com.github.bumptech.glide:okhttp3-integration:$glideVersion",
    annotationProcessor "com.github.bumptech.glide:compiler:$glideVersion",
  2. Integrate Glide with Eko's authentication

    i. If you don't have custom AppGlideModule defined, just add the following class and that's it:

    @GlideModule
    open class EkoGlideModule : AppGlideModule()

    ii. If you already have Glide OkHttp3 integration add OkHttp Interceptor using OkHttpClient.Builder.addNetworkInterceptor() method:

    builder.addNetworkInterceptor(EkoOkHttp.getInterceptor())

To render image with Glide

val data = message.getData()
when (data) {
is EkoMessage.Data.IMAGE -> {
val imageUrl = data.getUrl()
Glide.with(context)
.load(imageUrl)
.into(holder.imageView)
}
}

To integrate with Picasso and OkHttp3:

  1. Ensure that you have the following gradle dependencies where picassoVersion is 2.7 or above. For picassoVersionbelow, you may need a Downloader extension.

    implementation 'com.squareup.picasso:picasso:2.$picassoVersion'
  2. Integrate Picasso with Eko's authentication

    i. Create an instance of OkHttpClient with the implemenation provided by the SDK and pass it to Picasso's builder.

    val ekoOkHttpClient = EkoOkHttp.newBuilder().build()
    val picasso = Picasso.Builder(context)
    .downloader(OkHttp3Downloader(ekoOkHttpClient))
    .build()

    ii. If you already have your own implementation of OkHttpClient, add OkHttp Interceptor using OkHttpClient.Builder.addNetworkInterceptor() method:

builder.addNetworkInterceptor(EkoOkHttp.getInterceptor())

To render image with Picasso

val data = message.getData()
when (data) {
is EkoMessage.Data.IMAGE -> {
val imageUrl = data.getUrl()
val picasso = Picasso.Builder(context)
.downloader(OkHttp3Downloader(ekoOkHttpClient))
.build()
picasso.load(imageUrl).into(imageView);
}
}

Viewing File Message

To get file url and file name from EkoMessage:

val data = message.getData()
when (data) {
is EkoMessage.Data.FILE -> {
val fileName = data.getFile().getFileName()
val fileUrl = data.getFile().getUrl()
}
}

The file is protected by access token. In order to retrieve a file, the http request for that file needs to be authenticated. To integrate with OkHttp3:

  1. Create an instance of OkHttpClient with the implemenation provided by the SDK.

    OkHttpClient ekoOkHttpClient = EkoOkHttp.newBuilder().build()
  2. If you already have your own implementation of OkHttpClient, add OkHttp Interceptor using OkHttpClient.Builder.addNetworkInterceptor() method:

    builder.addNetworkInterceptor(EkoOkHttp.getInterceptor())

Viewing Custom Message

To get customedJsonObject from EkoMessage

val data = message.getData()
when (data) {
is EkoMessage.Data.CUSTOM -> {
val json = data.raw()
}
}

Edit and Delete Messages

You can only perform edit and delete operations on messages you've sent. Once the operation is complete, the message's editedAt will be set to the current time. This allows you to provide UI to the user to inform the user of specific messages that has been edited, if needed.

Currently, SDK has only 2 editable data types, TEXT and CUSTOM

val data : EkoMessage.Data.TEXT = ...
data.edit()
.text(text)
.build()
.apply()
.subscribe()
val data : EkoMessage.Data.CUSTOM = ...
data.edit()
.data(jsonObject)
.build()
.apply()
.subscribe()

To delete a message, simply call delete()on EkoMessage

message.delete().subscribe()

Message Reaction

Reaction Data

There are 3 Reaction related methods on EkoMessage:

1. getReactionCount() returns Int, the total reaction count on the message.

2. getMyReactions() returns List<String>, a collection of reactions that has been added by the active user.

3. getReactions() returns EkoReactionMap, an extension of Map<String, Int> of reaction name and its count.

// To get reaction count of the message
val reactionCount = message.getReactionCount()
// To get a list of my reactions on the message
val myReactions = message.getMyReactions()
// To get a reaction map of the messsage
val reactionMap = message.getReactionMap()
// Count of a specific reactionName
val likeCount = reactionMap.getCount("like")

Add/Remove Reaction

You can choose to add/remove a reaction to/from a message using calling react() method on EkoMessage

// Add a reaction
message.react()
.addReaction("like")
.subscribe()
// Remove a reaction
message.react()
.removeReaction("like")
.subscribe()

Reaction Query

To query for a list of all reactions on a message:

message.getReactionCollection()
.build()
.query()
.subscribe( { adapter.submitList(it) } )

This method will return a Flowable<PagedList<EkoReaction>> of all reactions in the specified message.