Quick Start

1. Create a new SendBird application in the Dashboard

The first thing you need to do is to log in to the SendBird Dashboard and create a SendBird application. If you do not yet have an account, you can log in with Google, GitHub, or create a new account.

You should create one application per service, regardless of the platform. For example, an app released in both Android and iOS would require only one application to be created in the Dashboard.

All users within the same SendBird application are able to communicate with each other, across all platforms. This means users using iOS, Android, web clients, etc. can all chat with one another. However, users in different SendBird applications cannot talk to each other.

2-1. When building a new app

If you are developing an application from scratch, a convenient way to begin is by building your app on top of our Sample UI project.

You can access the sample in GitHub from the link below.

Build and run the Sample UI project to play around with Open Chat or Group Chat.

The sample project is shipped with a Testing App ID.
You must replace this with your own App ID, which can be found in the SendBird Dashboard, for a production app.

2-2. When integrating SendBird with an existing app

Install the SendBird SDK using Gradle

Add the following dependencies to your app-level build.gradle file.

build.gradle
repositories {
    maven { url "https://raw.githubusercontent.com/smilefam/SendBird-SDK-Android/master/" }
}
dependencies {
    compile 'com.sendbird.sdk:sendbird-android-sdk:3.0.28'
}

Or, you can directly download the jar file from the link below.

Add Android permissions

The SendBird SDK requires system permissions. These permissions allow the SDK to communicate with the SendBird servers and read/write files to a device's external storage.

Add the following permissions to your AndroidManifest.xml file.

AndroidManifest.xml
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

Authentication

Initializing with APP_ID

To use its chat features, you must initialize SendBird using the APP_ID assigned to your SendBird application.
Typically, initialization would be implemented in the user login Activity.

SendBird.init(APP_ID, context);

Connecting with UserID

By default, SendBird requires only a UserID to join a channel. Upon requesting connection, SendBird will query its user database for a matching UserID. If it finds that the UserID has not been registered yet, a new user account will be created. The UserID can be any unique string id, such as an email address or a UID from your database.

This simple authentication procedure might be useful when you are in development or if your service does not require additional security.

Explanation on SendBird's usage of Handlers and callbacks can be found under the Event Handler section.

SendBird.connect(USER_ID, new ConnectHandler() {
    @Override
    public void onConnected(User user, SendBirdException e) {
        if (e != null) {
            // Error.
            return;
        }
    }
});

You must connect to SendBird before calling any methods through the SDK (apart from init()). If you attempt to call a method without connecting, you may receive an API-TOKEN is missing error.

Connecting with UserID and Access Token

With the SendBird Platform API, you can create a user with an access token, or you can issue an access token for an existing user. Once an access token is issued, you are required to provide the user's token in the login method.

  1. Create a SendBird user account via the Platform API when your user signs up on your service.
  2. Save the access token to your secured persistent store.
  3. Load the access token in your client and pass it to the SendBird login method.
  4. For security reasons, we recommend that you periodically update your access token by issuing a new token to replace the previous one.

You can set restrictions for users without access tokens in your Dashboard settings.
These settings can be found under Security - Access Token Policy.

SendBird.connect(USER_ID, ACCESS_TOKEN, new ConnectHandler() {
    @Override
    public void onConnected(User user, SendBirdException e) {
        if (e != null) {
            // Error.
            return;
        }
    }
});

Disconnecting

You should disconnect from SendBird when your user no longer needs to receive messages from an online state.

Users will still be able to receive Group Channel messages through Push Notifications.

Disconnecting removes all registered handlers and callbacks. That is, it removes all Event Handlers added through SendBird.addChannelHandler() or SendBird.addConnectionHandler(). It also flushes all internally cached data, such as the channels that are cached when OpenChannel.getChannel() or GroupChannel.getChannel() is called.

SendBird.disconnect(new SendBird.DisconnectHandler() {
    @Override
    public void onDisconnected() {
        // You are disconnected from SendBird.
    }
});

Updating a User Profile and Profile Image

You can update a user's nickname and profile image.

Call updateCurrentUserInfo() to update a user's nickname, as well as their profile picture with a URL.

SendBird.connect(USER_ID, new ConnectHandler() {
    @Override
    public void onConnected(User user, SendBirdException e) {
        if (e != null) {
            // Error.
            return;
        }

        SendBird.updateCurrentUserInfo(NICKNAME, PROFILE_URL, new UserInfoUpdateHandler() {
            @Override
            public void onUpdated(SendBirdException e) {
                if (e != null) {
                    // Error.
                    return;
                }
            }
        });
    }
});

Or, you can upload an image directly using updateCurrentUserInfoWithProfileImage().

SendBird.connect(USER_ID, new ConnectHandler() {
    @Override
    public void onConnected(User user, SendBirdException e) {
        if (e != null) {
            // Error.
            return;
        }

        SendBird.updateCurrentUserInfoWithProfileImage(NICKNAME, FILE, new UserInfoUpdateHandler() {
            @Override
            public void onUpdated(SendBirdException e) {
                if (e != null) {
                    // Error.
                    return;
                }
            }
        });
    }
});

Channel Types

You should understand the following terminology before proceeding with the rest of this guide.

Open Channel

An Open Channel is a public chat. In this channel type, anyone can enter and participate in the chat without permission. A single channel can handle thousands of simultaneous users. i.e.) a Twitch-style public chat

Group Channel

A Group Channel is a private chat. A user may join the chat only through an invitation by another user who is already a member of the chatroom.

  • Distinct property : A channel with the Distinct property enabled will always be reused for the same members. If a new member is invited, or if a member leaves the channel, then the Distinct property is disabled automatically.

  • 1-on-1 messaging : 1-on-1 messaging is a private channel between two users. You can enable the Distinct property for the channel in order to reuse a channel for the same members. i.e.) Twitter Direct Messages-style 1-on-1 chatting

  • Group messaging : Group messaging is a private channel among multiple users. You can invite up to hundreds of members into a group channel. i.e.) a WhatsApp-style closed group chat

Open vs. Group Channels

Type Open Channel Group Channel
Access Control Public Invitation required
Class Name OpenChannel GroupChannel
Number of Members Over a few thousand Less than a few hundred
How to Create SendBird Dashboard / Platform API / Client SDK Client SDK / Platform API
Operators Supported N/A
User Ban Supported N/A
User Mute Supported N/A
Freeze Channel Supported N/A
Push Notifications N/A Supported
Unread Counts N/A Supported
Read Receipts N/A Supported
Typing Indicators N/A Supported

Open Channel

An Open Channel is a public chat. In this channel type, anyone can enter and participate in the chat without permission. A single channel can handle thousands of simultaneous users. i.e.) a Twitch-style public chat

Creating an Open Channel

You can create an Open Channel from the SendBird Dashboard.

To create a channel, you must specify a Channel URL, which is a unique identifier. Additionally, you can set a Channel Topic, the name of the channel.

An open channel is ideal for use cases that require a small and static number of channels - for example, when you have a central "Lobby Chat" within a game.

create_channel_dashboard

You can also create a channel via the SDK or the SendBird Platform API. You should do so when your channel needs to be created on demand or dynamically.

OpenChannel.createChannel(new OpenChannel.OpenChannelCreateHandler() {
    @Override
    public void onResult(OpenChannel openChannel, SendBirdException e) {
        if (e != null) {
            // Error.
            return;
        }
    }
});

You can also append information by passing additional arguments.

OpenChannel.createChannel(NAME, COVER_IMAGE_OR_URL, DATA, CUSTOM_TYPE, HANDLER)
  • NAME : the name of the channel, or the Channel Topic.
  • COVER_IMAGE_OR_URL : the file or URL of the cover image, which you can fetch to render into the UI.
  • DATA : a String field to store structured information, such as a JSON String.
  • CUSTOM_TYPE : a String field that allows you to subclassify your channel.

See the Advanced section for more information on cover images and Custom Types.

Getting a list of Open Channels

You can obtain a list of all Open Channels by creating a OpenChannelListQuery.
The next() method returns a list of OpenChannel objects.

You must be connected to SendBird before requesting an Open Channel List.

OpenChannelListQuery channelListQuery = OpenChannel.createOpenChannelListQuery();
channelListQuery.next(new OpenChannelListQuery.OpenChannelListQueryResultHandler() {
    @Override
    public void onResult(List<OpenChannel> channels, SendBirdException e) {
        if (e != null) {
            // Error.
            return;
        }
    }
});

Getting an Open Channel instance with a URL

Since a channel URL is a unique identifier of an Open Channel, you can use a URL to retrieve a channel instance. It is important to remember that a user must enter the channel before being able to send or receive messages within it.

Store channel URLs to handle lifecycle or state changes in your app. For example, if a user disconnects from SendBird by temporarily switching to another app, you can provide a smooth restoration of the user's state using a stored URL to fetch the appropriate channel instance, then re-entering the user into the channel.

OpenChannel.getChannel(channelUrl, new OpenChannel.OpenChannelGetHandler() {
    @Override
    public void onResult(OpenChannel openChannel, SendBirdException e) {
        if (e != null) {
            // Error!
            return;
        }

        // Successfully fetched the channel.
        // Do something with openChannel.
    }
});

Entering an Open Channel

A user must enter an Open Channel in order to receive messages.

You can enter up to 10 Open Channels at once.

Entered Open Channels are valid only within the current connection. If you disconnect or reconnect to SendBird, you must re-enter channels in order to continue receiving messages.

OpenChannel.getChannel(CHANNEL_URL, new OpenChannel.OpenChannelGetHandler() {
    @Override
    public void onResult(OpenChannel openChannel, SendBirdException e) {
        if (e != null) {
            // Error.
            return;
        }

        openChannel.enter(new OpenChannel.OpenChannelEnterHandler() {
            @Override
            public void onResult(SendBirdException e) {
                if (e != null) {
                    // Error.
                    return;
                }
            }
        });
    }
});

Exiting an Open Channel

To stop receiving messages from an Open Channel, you must exit the channel.

OpenChannel.getChannel(CHANNEL_URL, new OpenChannel.OpenChannelGetHandler() {
    @Override
    public void onResult(OpenChannel openChannel, SendBirdException e) {
        if (e != null) {
            // Error.
            return;
        }

        openChannel.exit(new OpenChannel.OpenChannelExitHandler() {
            @Override
            public void onResult(SendBirdException e) {
                if (e != null) {
                    // Error.
                    return;
                }
            }
        });
    }
});

Sending messages

Upon entering a channel, a user will be able to send messages of the following types:

  • UserMessage : a User text message.
  • FileMessage : a User binary message.

You can additionally specify a CUSTOM_TYPE to further subclassify a message.

When you send a text message, you can additionally attach arbitrary strings via a data field. You can utilize this field to send structured data such as font sizes, font types, or custom JSON objects.

Delivery failures (e.g., due to network issues) will return an exception. By overriding the onSent()callback within an implementation of sendUserMessage(), it is possible to display only the messages that are successfully sent.

channel.sendUserMessage(MESSAGE, DATA, CUSTOM_TYPE, new BaseChannel.SendUserMessageHandler() {
    @Override
    public void onSent(UserMessage userMessage, SendBirdException e) {
        if (e != null) {
            // Error.
            return;
        }
    }
});

A user can also send any binary file through SendBird. There are two ways in which you can send a binary file: by sending the file itself, or by sending a URL.

By sending a raw file, you are uploading it to the SendBird servers. Alternatively, you can choose to send a file hosted in your own servers by passing in a URL that points to the file. In this case, your file will not be hosted in the SendBird servers, and downloads of the file will occur through your own servers instead.

Note that if you upload your file directly, a size limit is imposed per file. This limit depends on your plan, and can be viewed from your Dashboard.

No file size limit is imposed if you send a File Message via a URL. Your file will not be uploaded to the SendBird servers.

// Sending File Message with raw file

channel.sendFileMessage(FILE, NAME, TYPE, SIZE, DATA, CUSTOM_TYPE, new BaseChannel.SendFileMessageHandler() {
    @Override
    public void onSent(FileMessage fileMessage, SendBirdException e) {
        if (e != null) {
            // Error.
            return;
        }
    }
});
// Sending File Message with file URL

channel.sendFileMessage(FILE_URL, NAME, TYPE, SIZE, DATA, CUSTOM_TYPE, new BaseChannel.SendFileMessageHandler() {
    @Override
    public void onSent(FileMessage fileMessage, SendBirdException e) {
        if (e != null) {
            // Error.
            return;
        }
    }
});

To send metadata along with a file, you can populate the DATA field.

Receiving messages

Messages can be received by adding a ChannelHandler.
A received BaseMessage object can be of one of three different types of messages.

  • UserMessage : a User text message.
  • FileMessage : a User binary message.
  • AdminMessage : an Admin message which can be sent by an admin through the Platform API.

UNIQUE_HANDLER_ID is a unique identifier to register multiple concurrent handlers.

SendBird.addChannelHandler(UNIQUE_HANDLER_ID, new SendBird.ChannelHandler() {
    @Override
    public void onMessageReceived(BaseChannel baseChannel, BaseMessage baseMessage) {
        if (baseMessage instanceof UserMessage) {
            // message is a UserMessage
        } else if (baseMessage instanceof FileMessage) {
            // message is a FileMessage
        } else if (baseMessage instanceof AdminMessage) {
            // message is an AdminMessage
        }
    }
});

Loading previous messages

You can load previous messages by creating a PreviousMessageListQuery instance and calling load().
You will be able to display past messages in your UI once they have loaded.

PreviousMessageListQuery prevMessageListQuery = openChannel.createPreviousMessageListQuery();
prevMessageListQuery.load(30, true, new PreviousMessageListQuery.MessageListQueryResult() {
    @Override
    public void onResult(List<BaseMessage> messages, SendBirdException e) {
        if (e != null) {
            // Error.
            return;
        }
    }
});

Past messages are queried in fixed numbers (30 in the above code). A new PreviousMessageListQuery instance will load the most recent n messages. Calling load() on the same query instance will load n messages before that. Therefore, you should store your query instance as a member variable in order to traverse through your entire message history.

An important note is that you must receive your first onResult() callback before invoking load() again.

Loading messages by timestamp

You can retrieve a set number of messages starting from a specific timestamp.

To load messages sent prior to a specifed timestamp, use channel.getPreviousMessagesByTimestamp().

channel.getPreviousMessagesByTimestamp(timestamp, isInclusive,
        prevResultSize, reverse, messageType, customType,
        new BaseChannel.GetMessagesHandler() {
    @Override
    public void onResult(List<BaseMessage> list, SendBirdException e) {
        if (e != null) {
            return;
        }

        // Successfully fetched list of messages sent before timestamp.
    }
});
  • timestamp : The reference timestamp.
  • isInclusive : Whether to include messages sent exactly at timestamp.
  • prevResultSize : The number of messages to load. Note that the actual number of results may be larger than the set value when there are multiple messages with the same timestamp as the earliest message.
  • reverse : Whether to reverse the results.
  • messageType : A BaseChannel.MessageTypeFilter object. Should be one of MessageTypeFilter.USER, MessageTypeFilter.FILE, MessageTypeFilter.ADMIN, or MessageTypeFilter.ALL.
  • customType : The Custom Type of the messages to be returned.

To load messages sent after a specified timestamp, call channel.getNextMessagesByTimestamp() in a similar fashion. To load results on either side of the reference timestamp, use channel.getPreviousAndNextMessagesByTimestamp().

Getting a list of participants in a channel

Participants are online users who are currently receiving all messages from the Open Channel.

UserListQuery userListQuery = openChannel.createParticipantListQuery();
userListQuery.next(new UserListQuery.UserListQueryResultHandler() {
    @Override
    public void onResult(List<User> list, SendBirdException e) {
        if (e != null) {
            // Error.
            return;
        }
    }
});

Getting participants' online statuses

To stay updated on each participant's connection status, you must obtain a new UserListQuery, which contains the latest information on each user. To get a UserListQuery for a specific channel, call channel.createParticipantListQuery(). If you wish to get the list of all users of your service (application), call SendBird.createUserListQuery().

You can then check each of the users' connection statuses by making calls to user.getConnectionStatus().

If your application needs to keep track of users' connection statuses in real time, we recommend that you receive a new UserListQuery periodically, perhaps in intervals of one minute or more.

getConnectionStatus() can return one of three values:

  • User.ConnectionStatus.NON_AVAILABLE : User's status information cannot be reached.
  • User.ConnectionStatus.OFFLINE : User is disconnected from SendBird.
  • User.ConnectionStatus.ONLINE : User is connected to SendBird.

Getting a list of banned or muted users in a channel

You can also create a query to get a list of muted or banned users in an Open Channel

This query is only available for users who are registered as operators of the Open Channel.

UserListQuery bannedUserListQuery = openChannel.createBannedUserListQuery(); //or createMutedUserListQuery()
bannedUserListQuery.next(new UserListQuery.UserListQueryResultHandler() {
    @Override
    public void onResult(List<User> list, SendBirdException e) {
        if (e != null) {
            // Error.
            return;
        }
    }
});

Deleting messages

Users are able to delete messages. An error is returned if a user tries to delete messages sent by someone else.
Channel Operators are able to delete any message in the channel, including those by other users.

Deleting a message fires a MessageDeleted event to all other users in the channel.

channel.deleteMessage(BASE_MESSAGE, new BaseChannel.DeleteMessageHandler() {
    @Override
    public void onResult(SendBirdException e) {
        if (e != null) {
            // Error.
            return;
        }
    }
});

You can receive a MessageDeleted event using a ChannelHandler.

SendBird.addChannelHandler(UNIQUE_HANDLER_ID, new SendBird.ChannelHandler() {
    @Override
    public void onMessageDeleted(BaseChannel baseChannel, long messageId) {
    }
});

Open Channel - Advanced

Admin messages

You can send Admin messages to users in a channel using the SendBird Dashboard or the Platform API.

To do so using the Dashboard, navigate to the Open Channels tab. Inside the message box, you should see an option to send an Admin message. Admin messages should not be longer than 1000 characters.

If you are currently developing under the Free Plan and therefore cannot access the Moderation Tools from the Dashboard, you must send Admin messages through the Platform API.

Channel cover images

When creating a channel, you can add a cover image by specifying an image URL or file.

OpenChannel.createChannel(NAME, COVER_URL, DATA, new OpenChannel.OpenChannelCreateHandler() {
    @Override
    public void onResult(OpenChannel openChannel, SendBirdException e) {
        if (e != null) {
            // Error.
            return;
        }
    }
});

You can get the cover image URL using getCoverUrl(). You can also update a channel's cover image by calling updateChannel().

Custom channel types

When creating a channel, you can additionally specify a Custom Type to further subclassify your channels. This custom type takes on the form of a String, and can be handy in searching or filtering channels.

DATA and CUSTOM_TYPE are both String fields that allow you to append information to your channels. The intended use case is for CUSTOM_TYPE to contain information that can subclassify the channel (e.g., distinguishing "School" and "Work" channels). However, both these fields can be flexibly utilized.

channel.createChannel(NAME, COVER_URL, DATA, CUSTOM_TYPE new OpenChannel.OpenChannelCreateHandler() {
    @Override
    public void onResult(OpenChannel openChannel, SendBirdException e) {
        if (e != null) {
            // Error.
            return;
        }
    }
});

To get a channel's Custom Type, call channel.getCustomType().

Custom message types

Likewise, you can specify a Custom Type for messages in order to categorize them into more specific groups. This custom type takes on the form of a String, and can be useful in searching or filtering messages.

DATA and CUSTOM_TYPE are both String fields that allow you to append information to your messages. The intended use case is for CUSTOM_TYPE to contain information that can subclassify the message (e.g., distinguishing "FILE_IMAGE" and "FILE_AUDIO" type messages). However, both these fields can be flexibly utilized.

To embed a Custom Type into a message, simply pass a String parameter to channel.sendUserMessage() or channel.sendFileMessage().

channel.sendUserMessage(MESSAGE, DATA, CUSTOM_TYPE, new BaseChannel.SendUserMessageHandler() {
    @Override
    public void onSent(UserMessage userMessage, SendBirdException e) {
        if (e != null) {
            // Error.
            return;
        }
    }
});

To get a message's Custom Type, call message.getCustomType().

Message auto-translation

This feature is not available under the Free plan. Contact sales@sendbird.com if you wish to implement this functionality.

SendBird makes it possible for messages to be sent in different languages through its auto-translation feature.
Pass in a List of language codes to sendUserMessage() to request translated messages in the corresponding languages.

ArrayList targetLangList = new ArrayList<String>();
targetLangList.add("es");
targetLangList.add("ko");

channel.sendUserMessage(MESSAGE, DATA, CUSTOM_TYPE, targetLangList, new BaseChannel.SendUserMessageHandler() {
    @Override
    public void onSent(UserMessage userMessage, SendBirdException e) {
        if (e != null) {
            // Error.
            return;
        }
    }
});

You can obtain translations of a message using userMessage.getTranslations(). This method returns a Map containing the language codes and translations.

SendBird.addChannelHandler(UNIQUE_CHANNEL_ID, new SendBird.ChannelHandler() {
    @Override
    public void onMessageReceived(BaseChannel baseChannel, BaseMessage baseMessage) {
        Map<String, String> map = ((UserMessage) baseMessage).getTranslations();
        String esTranslation = map.get("es");
        ...
        // Display translation in UI.
    }
}

Reference the table below for supported languages and their language codes.

Language Code English Name Language Code English Name
af Afrikaans tlh-Qaak Klingon (pIqaD)
ar Arabic ko Korean
bs-Latn Bosnian (Latin) lv Latvian
bg Bulgarian lt Lithuanian
ca Catalan ms Malay
zh-CHS Chinese Simplified mt Maltese
zh-CHT Chinese Traditional no Norwegian
hr Croatian fa Persian
cs Czech pl Polish
da Danish pt Portuguese
nl Dutch otq Querétaro Otomi
en English ro Romanian
et Estonian ru Russian
fi Finnish sr-Cyrl Serbian (Cyrillic)
fr French sr-Latn Serbian (Latin)
de German sk Slovak
el Greek sl Slovenian
ht Haitian Creole es Spanish
he Hebrew sv Swedish
hi Hindi th Thai
mww Hmong Daw tr Turkish
hu Hungarian uk Ukrainian
id Indonesian ur Urdu
it Italian vi Vietnamese
ja Japanese cy Welsh
sw Kiswahili yua Yucatec Maya
tlh Klingon - -

You can search for specific channels by adding a keyword to your OpenChannelListQuery.
There are two types of keywords: a Name Keyword and a URL Keyword.

Adding a Name Keyword to a query will return the list of Open Channels that have the keyword included in their names.

channelListQuery = OpenChannel.createOpenChannelListQuery();
channelListQuery.setNameKeyword("NameKeyword");

channelListQuery.next(new OpenChannelListQuery.OpenChannelListQueryResultHandler() {
    @Override
    public void onResult(List<OpenChannel> channels, SendBirdException e) {
        if (e != null) {
            // Error!
            return;
        }
        // Returns a List of channels that have "NameKeyword" in their names.
    }
});

Adding a URL Keyword to a query will return the Open Channel whose URL exactly matches the given keyword.

channelListQuery = OpenChannel.createOpenChannelListQuery();
channelListQuery.setUrlKeyword("UrlKeyword");

channelListQuery.next(new OpenChannelListQuery.OpenChannelListQueryResultHandler() {
    @Override
    public void onResult(List<OpenChannel> channels, SendBirdException e) {
        if (e != null) {
            // Error!
            return;
        }
        // Returns a List containing a single channel with the URL that matches the URL Keyword.
    }
});

File Message thumbnails

This feature is not available under the Free plan. Contact sales@sendbird.com if you wish to implement this functionality.

When sending an image file, you can choose to create thumbnails of the image, which you can fetch and render into your UI. You can specify up to 3 different dimensions to generate thumbnail images in, which can be convenient for supporting various display densities.

Supported file types are files whose MIME type is image/* or video/*.

The SDK does not support creating thumbnails when sending a File Message via a file URL.

Create a List of FileMessage.ThumbnailSize objects to pass to channel.sendFileMessage(). A ThumbnailSize can be created with the constructor ThumbnailSize(int maxWidth, int maxHeight), where the values specify pixels. The onSent() callback will subsequently return a List of Thumbnail objects that each contain the URL of the generated thumbnail image file.

List<FileMessage.ThumbnailSize> thumbnailSizes = new ArrayList<>();

// Create and add ThumbnailSizes (Max: 3 ThumbnailSizes).
thumbnailSizes.add(new ThumbnailSize(100, 100));
thumbnailSizes.add(new ThumbnailSize(200, 200)); 

channel.sendFileMessage(file, name, type, size, data, customtype, thumbnailSizes, new BaseChannel.SendFileMessageHandler() {
    public void onSent(FileMessage fileMessage, SendBirdException e) {
        if (e != null) {
            // Error!
        }

        Thumbnail first = fileMessage.getThumbnails.get(0);
        Thumbnail second = fileMessage.getThumbnails.get(1);

        int maxHeightFirst = first.getMaxHeight(); // 100
        int maxHeightSecond = second.getMaxHeight(); // 200

        String urlFirst = first.getUrl(); // URL of first thumbnail file.
        String urlSecond = second.getUrl(); // URL of second thumbnail file.
    }
}

maxWidth and maxHeight specify the maximum dimensions of the thumbnail. Your image will be scaled down evenly to fit within the bounds of (maxWidth, maxHeight). Note that if the original image is smaller than the specified dimensions, the thumbnail will not be scaled. getUrl() returns the location of the generated thumbnail file within the SendBird servers.

Group Channel

A Group Channel is a private chat. A user may join the chat only through an invitation by another user who is already a member of the chatroom. A Group Channel can consist of one to hundreds of members. Creating a channel with two members allows 1-to-1 messaging.

A user will automatically receive all messages from the group channels that they are a member of.

Creating a Group Channel

A Group Channel can be created on demand by a user through the SendBird SDK.

Distinct property : A channel with the Distinct property enabled will always be reused for the same members. If a new member is invited, or if a member leaves the channel, then the distinct property is disabled automatically. For example, in the case that a Group Channel with 3 members, A, B, and C, already exists, attempting to create a new channel with the same members will just return a reference to the existing channel.

Consequently, we recommend that you enable the Distinct property in 1-to-1 messaging channels in order to reuse the same channel when a user chooses to directly message a friend. If the property is disabled, the user will create a new channel even if they have had previous conversations with the friend, and therefore will not be able to see or access previously sent messages or data.

GroupChannel.createChannelWithUserIds(USER_IDS, IS_DISTINCT, new GroupChannel.GroupChannelCreateHandler() {
    @Override
    public void onResult(GroupChannel groupChannel, SendBirdException e) {
        if (e != null) {
            // Error.
            return;
        }
    }
});

You can also append information by passing additional arguments.

GroupChannel.createChannel(USER_IDS, IS_DISTINCT, NAME, COVER_IMAGE_OR_URL, DATA, CUSTOM_TYPE, HANDLER)
  • NAME : the name of the channel, or the Channel Topic.
  • COVER_IMAGE_OR_URL : the file or URL of the cover image, which you can fetch to render into the UI.
  • DATA : a String field to store structured information, such as a JSON String.
  • CUSTOM_TYPE : a String field that allows you to subclassify your channel.

See the Advanced section for more information on cover images and Custom Types.

You can also create channels via the SendBird Platform API.
You should utilize the Platform API when you wish to control channel creations and member invitations on the server-side.

Getting a Group Channel instance with a URL

Since a channel URL is a unique identifier of a Group Channel, you can use a URL to retrieve a channel instance.

Store channel URLs to handle lifecycle or state changes in your app. For example, if a user disconnects from SendBird by temporarily switching to another app, you can provide a smooth restoration of the user's state using a stored URL to fetch the appropriate channel instance, then re-entering the user into the channel.

GroupChannel.getChannel(channelUrl, new GroupChannel.GroupChannelGetHandler() {
    @Override
    public void onResult(GroupChannel groupChannel, SendBirdException e) {
        if (e != null) {
            // Error!
            return;
        }

        // Successfully fetched the channel.
        // Do something with groupChannel.
    }
});

Inviting users to an existing channel

Only members of the channel are able to invite new users into the channel.

You can choose whether the newly invited user is able to access past messages in the channel. In your Dashboard Settings - Messages section, there is an option to show channel history. If this option is enabled, new users will be able to view all messages sent before they have joined the channel. If not, new users will only be able to see messages sent after they had been invited.

Show channel history is enabled by default.

groupChannel.inviteWithUserIds(userIds, new GroupChannel.GroupChannelInviteHandler() {
    @Override
    public void onResult(SendBirdException e) {
        if (e != null) {
            // Error.
            return;
        }
    }
});

Leaving a Group Channel

Users will no longer receive messages from channels they have left.

groupChannel.leave(new GroupChannel.GroupChannelLeaveHandler() {
    @Override
    public void onResult(SendBirdException e) {
        if (e != null) {
            // Error.
            return;
        }
    }
});

Getting a list of my Group Channels

You can obtain a list of all Group Channels by creating a GroupChannelListQuery.
The next() method returns a list of GroupChannel objects.

You can also set an option to include empty channels with setIncludeEmpty(boolean). Empty channels are channels that have been created but contain no sent messages. By default, empty channels are not shown.

GroupChannelListQuery channelListQuery = GroupChannel.createMyGroupChannelListQuery();
channelListQuery.setIncludeEmpty(true);
channelListQuery.next(new GroupChannelListQuery.GroupChannelListQueryResultHandler() {
    @Override
    public void onResult(List<GroupChannel> list, SendBirdException e) {
        if (e != null) {
            // Error.
            return;
        }
    }
});

Querying Group Channels by User IDs

It is possible to filter a channel search by user IDs. This can be done by calling setUserIdsExactFilter(List<String> userIds) or setUserIdsIncludeFilter(List<String> userIds, QueryType queryType).

Given an example where a user (with the ID "User") is part of two Group Channels:

  • channelA: [ "User", "John", "Jay" ]
  • channelB: [ "User", "John", "Jay", "Jin" ]

An ExactFilter returns the list of channels containing exactly the queried userIDs.

GroupChannelListQuery filteredQuery = GroupChannel.createMyGroupChannelListQuery();
List<String> userIds = new ArrayList<>();
userIds.add("John");
userIds.add("Jay");

filteredQuery.setUserIdsExactFilter(userIds);
filteredQuery.next(
...
    // returns channelA only.
)

An IncludeFilter returns channels where the userIDs are included. This method can return one of two different results, based on the parameter queryType.

GroupChannelListQuery filteredQuery = GroupChannel.createMyGroupChannelListQuery();
List<String> userIds = new ArrayList<>();
userIds.add("John");
userIds.add("Jay");
userIds.add("Jin");

filteredQuery.setUserIdsIncludeFilter(userIds, GroupChannelListQuery.QueryType.AND);
filteredQuery.next(
...
    // returns channels that include the ids { John, Jay, Jin } as a subset.

    // i.e. returns channelB only.
)

filteredQuery.setUserIdsIncludeFilter(userIds, GroupChannelListQuery.QueryType.OR);
filteredQuery.next(
...
    // returns channels that include { John }, plus channels that include { Jay }, 
    // plus channels that include { Jin }.

    // i.e. returns both channelA, channelB.
)

Sending messages

Upon entering a channel, a user will be able to send messages of the following types:

  • UserMessage : a User text message.
  • FileMessage : a User binary message.

You can additionally specify a CUSTOM_TYPE to further subclassify a message.

When you send a text message, you can additionally attach arbitrary strings via a data field. You can utilize this field to send structured data such as font size, font type, or a custom JSON object.

Delivery failures (e.g., due to network issues) will return an exception. By overriding the onSent() callback within an implementation of sendUserMessage(), it is possible to display only the messages that are successfully sent.

channel.sendUserMessage(MESSAGE, DATA, CUSTOM_TYPE, new BaseChannel.SendUserMessageHandler() {
    @Override
    public void onSent(UserMessage userMessage, SendBirdException e) {
        if (e != null) {
            // Error.
            return;
        }
    }
});

A user can also send any binary file through SendBird. There are two ways in which you can send a binary file: by sending the file itself, or by sending a URL.

By sending a raw file, you are uploading it to the SendBird servers. Alternatively, you can choose to send a file hosted in your own servers by passing in a URL that points to the file. In this case, your file will not be hosted in the SendBird servers, and downloads of the file will occur through your own servers instead.

Note that if you upload your file directly, a size limit is imposed per file. This limit depends on your plan, and can be viewed from your Dashboard.

No file size limit is imposed if you send a File Message via a URL. Your file will not be uploaded to the SendBird servers.

// Sending File Message with raw file

channel.sendFileMessage(FILE, NAME, TYPE, SIZE, DATA, CUSTOM_TYPE, new BaseChannel.SendFileMessageHandler() {
    @Override
    public void onSent(FileMessage fileMessage, SendBirdException e) {
        if (e != null) {
            // Error.
            return;
        }
    }
});
// Sending File Message with file URL

channel.sendFileMessage(FILE_URL, NAME, TYPE, SIZE, DATA, CUSTOM_TYPE, new BaseChannel.SendFileMessageHandler() {
    @Override
    public void onSent(FileMessage fileMessage, SendBirdException e) {
        if (e != null) {
            // Error.
            return;
        }
    }
});

To send metadata along with a file, you can populate the DATA field.

Receiving messages

Messages can be received by adding a ChannelHandler.
A received BaseMessage object can be of one of three different types of messages.

  • UserMessage : a User text message.
  • FileMessage : a User binary message.
  • AdminMessage : an Admin message which can be sent by an admin through the Platform API.

UNIQUE_HANDLER_ID is a unique identifier to register multiple concurrent handlers.

SendBird.addChannelHandler(UNIQUE_HANDLER_ID, new SendBird.ChannelHandler() {
    @Override
    public void onMessageReceived(BaseChannel baseChannel, BaseMessage baseMessage) {
        if (baseMessage instanceof UserMessage) {
            // message is a UserMessage
        } else if (baseMessage instanceof FileMessage) {
            // message is a FileMessage
        } else if (baseMessage instanceof AdminMessage) {
            // message is an AdminMessage
        }
    }
});

You should remove the channel handler when the UI is no longer valid.

SendBird.removeChannelHandler(UNIQUE_HANDLER_ID);

Loading previous messages

You can load previous messages by creating a PreviousMessageListQuery instance and calling load(). You will be able to display these messages in your UI once they have loaded.

Whether a user can load messages prior to joining the channel depends on your settings. In your Dashboard Settings - Messages section, there is an option to show channel history. If this option is enabled, new users will be able to view all messages sent before they have joined the channel. If not, new users will only be able to see messages sent after they had been invited.

PreviousMessageListQuery prevMessageListQuery = groupChannel.createPreviousMessageListQuery();
prevMessageListQuery.load(30, true, new PreviousMessageListQuery.MessageListQueryResult() {
    @Override
    public void onResult(List<BaseMessage> messages, SendBirdException e) {
        if (e != null) {
            // Error.
            return;
        }
    }
});

Past messages are queried in fixed numbers (30 in the above code). A new PreviousMessageListQuery instance will load the most recent n messages. Calling load() on the same query instance will load n messages before that. Therefore, you should store your query instance as a member variable in order to traverse through your entire message history.

An important note is that you must receive your first onResult() callback before invoking load() again.

Loading messages by timestamp

You can retrieve a set number of messages starting from a specific timestamp.

To load messages sent prior to a specifed timestamp, use channel.getPreviousMessagesByTimestamp().

channel.getPreviousMessagesByTimestamp(timestamp, isInclusive,
        prevResultSize, reverse, messageType, customType,
        new BaseChannel.GetMessagesHandler() {
    @Override
    public void onResult(List<BaseMessage> list, SendBirdException e) {
        if (e != null) {
            return;
        }

        // Successfully fetched list of messages sent before timestamp.
    }
});
  • timestamp : The reference timestamp.
  • isInclusive : Whether to include messages sent exactly at timestamp.
  • prevResultSize : The number of messages to load. Note that the actual number of results may be larger than the set value when there are multiple messages with the same timestamp as the earliest message.
  • reverse : Whether to reverse the results.
  • messageType : A BaseChannel.MessageTypeFilter object. Should be one of MessageTypeFilter.USER, MessageTypeFilter.FILE, MessageTypeFilter.ADMIN, or MessageTypeFilter.ALL.
  • customType : The Custom Type of the messages to be returned.

To load messages sent after a specified timestamp, call channel.getNextMessagesByTimestamp() in a similar fashion. To load results on either side of the reference timestamp, use channel.getPreviousAndNextMessagesByTimestamp().

Deleting messages

Users are able to delete messages. An error is returned if a user tries to delete messages sent by someone else.
Channel Operators are able to delete any message in the channel, including those by other users.

Deleting a message fires a MessageDeleted event to all other users in the channel.

channel.deleteMessage(BASE_MESSAGE, new BaseChannel.DeleteMessageHandler() {
    @Override
    public void onResult(SendBirdException e) {
        if (e != null) {
            // Error.
            return;
        }
    }
});

You can receive a MessageDeleted event using a ChannelHandler.

SendBird.addChannelHandler(UNIQUE_HANDLER_ID, new SendBird.ChannelHandler() {
    @Override
    public void onMessageDeleted(BaseChannel baseChannel, long messageId) {
    }
});

1-to-1 Chat

A 1-to-1 chat is just a Group Channel with two members.

Creating a 1-to-1 chat

A Group Channel can be created on demand by a user through the client SDK. Pass in two user IDs to create a 1-to-1 chat between two users.

You would typically want a 1-to-1 chat to be Distinct. If the Distinct property is not enabled, the user will be able to create a new channel with the same opponent, even if they have had previous conversations. In this case, multiple 1-to-1 chats between the same two users might exist, each with its own chat history and data.

GroupChannel.createChannelWithUserIds(USER_IDS, IS_DISTINCT, new GroupChannel.GroupChannelCreateHandler() {
    @Override
    public void onResult(GroupChannel groupChannel, SendBirdException e) {
        if (e != null) {
            // Error.
            return;
        }
    }
});

You can also append information within the channel by passing additional arguments.

GroupChannel.createChannel(USER_IDS, IS_DISTINCT, NAME, COVER_IMAGE_OR_URL, DATA, CUSTOM_TYPE, HANDLER)
  • NAME : the name of the channel, or the Channel Topic.
  • COVER_IMAGE_OR_URL : the file or URL of the cover image, which you can fetch to render into the UI.
  • DATA : a String field to store structured information, such as a JSON String.
  • CUSTOM_TYPE : a String field that allows you to subclassify your channel.

See the Advanced section for more information on cover images and Custom Types.

You can also create channels via the SendBird Platform API.
You should utilize the Platform API when you wish to control channel creations and member invitations on the server-side.

Group Channel - Advanced

Getting a list of all channel members

You can obtain a list of members in a Group Channel using getMembers().

List<User> members = groupChannel.getMembers();

Members are automatically updated when you are online. If you disconnect from SendBird and reconnect, you should refresh() the channel to be updated with the latest information.

groupChannel.refresh(new GroupChannel.GroupChannelRefreshHandler() {
    @Override
    public void onResult(SendBirdException e) {
        if (e != null) {
            //Error.
            return;
        }
    }
});

Getting members' online statuses

To stay updated on each member's connection status, call channel.refresh() before calling channel.getMembers().
You can then check each of the users' connection statuses by making calls to user.getConnectionStatus().

If your application needs to keep track of users' connection statuses in real time, we recommend that you refresh() your channel instance periodically, perhaps in intervals of one minute or more.

getConnectionStatus() can return one of three values:

  • User.ConnectionStatus.NON_AVAILABLE : User's status information cannot be reached.
  • User.ConnectionStatus.OFFLINE : User is disconnected from SendBird.
  • User.ConnectionStatus.ONLINE : User is connected to SendBird.

Typing indicators

You can send typing events by invoking startTyping and endTyping.

groupChannel.startTyping();
groupChannel.endTyping();

You can receive a TypingStatusUpdate event in the channel handler.

SendBird.addChannelHandler(UNIQUE_HANDLER_ID, new SendBird.ChannelHandler() {
    @Override
    public void onTypingStatusUpdated(GroupChannel groupChannel) {
        if (currentGroupChannel.getUrl().equals(groupChannel.getUrl())) {
            List<User> members = groupChannel.getTypingMembers();
            // Refresh typing status of members within channel.
        }
    }
});

Read Receipts

A user can indicate that they have read a message by calling markAsRead().

groupChannel.markAsRead();

This broadcasts a ReadReceiptUpdate event, which can be handled with a ChannelHandler.

SendBird.addChannelHandler(UNIQUE_HANDLER_ID, new SendBird.ChannelHandler() {
    @Override
    public void onReadReceiptUpdated(GroupChannel groupChannel) {
        if (currentGroupChannel.getUrl().equals(groupChannel.getUrl())) {
            for (BaseMessage msg : yourMessages) {
                int unreadCount = groupChannel.getReadReceipt(msg);
                if (unreadCount <= 0) {
                    // All members have read the message.
                } else {
                    // Some members haven't read the message.
                }
            }
        }
    }
});

getReadReceipt(BaseMessage) returns the number of members in the channel who have not read the message.

  int unreadCount = groupChannel.getReadReceipt(message);

Viewing who has read a message

You can view who has read a message with channel.getReadMembers(message). This list is updated when the message's read receipt is updated. Therefore, you should replace your previous message instance with the newly received message in onReadReceiptUpdate() for real-time updates.

List<User> readMembers = channel.getReadMembers(message);

Similarly, you can also view who has not read the message with channel.getUnreadMembers(message).

Admin messages

You can send Admin messages to users in a channel using the SendBird Dashboard or the Platform API.

To do so using the Dashboard, navigate to the Group Channels tab. Inside the message box, you should see an option to send an Admin message. Admin messages should not be longer than 1000 characters.

If you are currently developing under the Free Plan and therefore cannot access the Moderation Tools from the Dashboard, you must send Admin messages through the Platform API.

Channel cover images

When creating a channel, you can add a cover image by specifying an image URL or file.

GroupChannel.createChannel(NAME, COVER_URL, DATA, new GroupChannel.GroupChannelCreateHandler() {
    @Override
    public void onResult(GroupChannel groupChannel, SendBirdException e) {
        if (e != null) {
            // Error.
            return;
        }
    }
});

You can get the cover image URL using getCoverUrl(). You can also update a channel's cover image by calling updateChannel().

Custom channel types

When creating a channel, you can additionally specify a Custom Type to further subclassify your channels. This custom type takes on the form of a String, and can be handy in searching or filtering channels.

DATA and CUSTOM_TYPE are both String fields that allow you to append information to your channels. The intended use case is for CUSTOM_TYPE to contain information that can subclassify the channel (e.g., distinguishing "School" and "Work" channels). However, both these fields can be flexibly utilized.

GroupChannel.createChannel(NAME, COVER_URL, DATA, CUSTOM_TYPE new OpenChannel.OpenChannelCreateHandler() {
    @Override
    public void onResult(GroupChannel groupChannel, SendBirdException e) {
        if (e != null) {
            // Error.
            return;
        }
    }
});

To get a channel's Custom Type, call channel.getCustomType().

Custom message types

Likewise, you can specify a Custom Type for messages in order to categorize them into more specific groups. This custom type takes on the form of a String, and can be useful in searching or filtering messages.

DATA and CUSTOM_TYPE are both String fields that allow you to append information to your messages. The intended use case is for CUSTOM_TYPE to contain information that can subclassify the message (e.g., distinguishing "FILE_IMAGE" and "FILE_AUDIO" type messages). However, both these fields can be flexibly utilized.

To embed a Custom Type into a message, simply pass a String parameter to channel.sendUserMessage() or channel.sendFileMessage().

channel.sendUserMessage(MESSAGE, DATA, CUSTOM_TYPE, new BaseChannel.SendUserMessageHandler() {
    @Override
    public void onSent(UserMessage userMessage, SendBirdException e) {
        if (e != null) {
            // Error.
            return;
        }
    }
});

To get a message's Custom Type, call message.getCustomType().

Message auto-translation

This feature is not available under the Free plan. Contact sales@sendbird.com if you wish to implement this functionality.

SendBird makes it possible for messages to be sent in different languages through its auto-translation feature.
Pass in a List of language codes to sendUserMessage() to request translated messages in the corresponding languages.

ArrayList targetLangList = new ArrayList<String>();
targetLangList.add("es");
targetLangList.add("ko");

channel.sendUserMessage(MESSAGE, DATA, CUSTOM_TYPE, targetLangList, new BaseChannel.SendUserMessageHandler() {
    @Override
    public void onSent(UserMessage userMessage, SendBirdException e) {
        if (e != null) {
            // Error.
            return;
        }
    }
});

You can obtain translations of a message using userMessage.getTranslations(). This method returns a Map containing the language codes and translations.

SendBird.addChannelHandler(UNIQUE_CHANNEL_ID, new SendBird.ChannelHandler() {
    @Override
    public void onMessageReceived(BaseChannel baseChannel, BaseMessage baseMessage) {
        Map<String, String> map = ((UserMessage) baseMessage).getTranslations();
        String esTranslation = map.get("es");
        ...
        // Display translation in UI.
    }
}

Reference the table below for supported languages and their language codes.

Language Code English Name Language Code English Name
af Afrikaans tlh-Qaak Klingon (pIqaD)
ar Arabic ko Korean
bs-Latn Bosnian (Latin) lv Latvian
bg Bulgarian lt Lithuanian
ca Catalan ms Malay
zh-CHS Chinese Simplified mt Maltese
zh-CHT Chinese Traditional no Norwegian
hr Croatian fa Persian
cs Czech pl Polish
da Danish pt Portuguese
nl Dutch otq Querétaro Otomi
en English ro Romanian
et Estonian ru Russian
fi Finnish sr-Cyrl Serbian (Cyrillic)
fr French sr-Latn Serbian (Latin)
de German sk Slovak
el Greek sl Slovenian
ht Haitian Creole es Spanish
he Hebrew sv Swedish
hi Hindi th Thai
mww Hmong Daw tr Turkish
hu Hungarian uk Ukrainian
id Indonesian ur Urdu
it Italian vi Vietnamese
ja Japanese cy Welsh
sw Kiswahili yua Yucatec Maya
tlh Klingon - -

File Message thumbnails

This feature is not available under the Free plan. Contact sales@sendbird.com if you wish to implement this functionality.

When sending an image file, you can choose to create thumbnails of the image, which you can fetch and render into your UI. You can specify up to 3 different dimensions to generate thumbnail images in, which can be convenient for supporting various display densities.

Supported file types are files whose MIME type is image/* or video/*.

The SDK does not support creating thumbnails when sending a File Message via a file URL.

Create a List of FileMessage.ThumbnailSize objects to pass to channel.sendFileMessage(). A ThumbnailSize can be created with the constructor ThumbnailSize(int maxWidth, int maxHeight), where the values specify pixels. The onSent() callback will subsequently return a List of Thumbnail objects that each contain the URL of the generated thumbnail image file.

List<FileMessage.ThumbnailSize> thumbnailSizes = new ArrayList<>();

// Create and add ThumbnailSizes (Max: 3 ThumbnailSizes).
thumbnailSizes.add(new ThumbnailSize(100, 100));
thumbnailSizes.add(new ThumbnailSize(200, 200)); 

channel.sendFileMessage(file, name, type, size, data, customtype, thumbnailSizes, new BaseChannel.SendFileMessageHandler() {
    public void onSent(FileMessage fileMessage, SendBirdException e) {
        if (e != null) {
            // Error!
        }

        Thumbnail first = fileMessage.getThumbnails.get(0);
        Thumbnail second = fileMessage.getThumbnails.get(1);

        int maxHeightFirst = first.getMaxHeight(); // 100
        int maxHeightSecond = second.getMaxHeight(); // 200

        String urlFirst = first.getUrl(); // URL of first thumbnail file.
        String urlSecond = second.getUrl(); // URL of second thumbnail file.
    }
}

maxWidth and maxHeight specify the maximum dimensions of the thumbnail. Your image will be scaled down evenly to fit within the bounds of (maxWidth, maxHeight). Note that if the original image is smaller than the specified dimensions, the thumbnail will not be scaled. getUrl() returns the location of the generated thumbnail file within the SendBird servers.

Channel Metadata

With MetaData and MetaCounter, you can store additional information within a channel.

MetaData allows you to store a Map of String key-value pairs in a channel instance.
If your aim is to store an integer with atomic increasing/decreasing operations, you should use a MetaCounter instead.

Use cases for MetaData/Counters could include tracking the number of likes, the background color, or a long description of the channel, which can each be fetched and rendered into the UI.

MetaData

MetaData is a map that is stored within a channel. Its uses are very flexible, allowing you to customize a channel to fit you and your users' needs.

Create

Storing MetaData into a channel simply requires creation of a Map, then passing it as an argument when calling createMetaData(). You can store multiple key-value pairs in the map.

HashMap<String, String> data = new HashMap<String, String>();
data.put("key1", "value1");
data.put("key2", "value2");
channel.createMetaData(data, new BaseChannel.MetaDataHandler() {
    @Override
    public void onResult(Map<String, String> map, SendBirdException e) {
        if (e != null) {
            // Error.
            return;
        }
    }
});

Update

The process for updating MetaData is identical to creation. Values will be updated for existing keys, while new key-value pairs will be added.

HashMap<String, String> data = new HashMap<String, String>();
data.put("key1", "valueToUpdate");
data.put("key2", "valueToUpdate");
channel.updateMetaData(data, new BaseChannel.MetaDataHandler() {
    @Override
    public void onResult(Map<String, String> map, SendBirdException e) {
        if (e != null) {
            // Error.
            return;
        }
    }
});

Get

Getting stored MetaData requires creating a Collection of keys to pass as an argument to getMetaData(). The callback onResult() returns a Map<String, String> containing the corresponding key-value pairs.

List<String> keys = new ArrayList<String>();
keys.add("key1");
keys.add("key2");
channel.getMetaData(keys, new BaseChannel.MetaDataHandler() {
    @Override
    public void onResult(Map<String, String> map, SendBirdException e) {
        if (e != null) {
            // Error.
            return;
        }
    }
});

MetaCounter

A MetaCounter is a map that is stored within a channel instance. Its primary uses are to track and update discrete indicators within a channel.

Create

Storing a MetaCounter into a channel simply requires creation of a Map, then passing it as an argument when calling createMetaCounter(). You can store multiple key-value pairs in the map.

HashMap<String, Integer> counters = new HashMap<String, Integer>();
counters.put("key1", 1);
counters.put("key2", 2);
channel.createMetaCounters(counters, new BaseChannel.MetaCounterHandler() {
    @Override
    public void onResult(Map<String, Integer> map, SendBirdException e) {
        if (e != null) {
            // Error.
            return;
        }
    }
});

Get

Retrieving stored MetaCounters requires creating a Collection of keys to pass as an argument to getMetaCounters(). The callback onResult() returns a Map<String, Integer> containing the corresponding key-value pairs.

List<String> keys = new ArrayList<String>();
keys.add("key1");
keys.add("key2");
channel.getMetaCounters(keys, new BaseChannel.MetaCounterHandler() {
    @Override
    public void onResult(Map<String, Integer> map, SendBirdException e) {
        if (e != null) {
            // Error.
            return;
        }
    }
});

Increase

The increase and decrease operations work similarly to getting MetaCounters, as described above. Create a Collection of keys to pass to increaseMetaCounters(), which increments the corresponding MetaCounters by 1.

HashMap<String, Integer> counters = new HashMap<String, Integer>();
counters.put("key1", 2); // Increases by 2.
counters.put("key2", 3); // Increases by 3.
channel.increaseMetaCounters(counters, new BaseChannel.MetaCounterHandler() {
    @Override
    public void onResult(Map<String, Integer> map, SendBirdException e) {
        if (e != null) {
            // Error.
            return;
        }
    }
});

Decrease

Likewise, pass a Collection of keys to decreaseMetaCounters(), which decrements the MetaCounters by 1.

HashMap<String, Integer> counters = new HashMap<String, Integer>();
counters.put("key1", 3); // Decreases by 3.
counters.put("key2", 4); // Decreases by 4.
channel.decreaseMetaCounters(counters, new BaseChannel.MetaCounterHandler() {
    @Override
    public void onResult(Map<String, Integer> map, SendBirdException e) {
        if (e != null) {
            // Error.
            return;
        }
    }
});

Event Handler

Event Handlers are crucial components of the SendBird SDK that allow a client to react to server-side events. These handlers contain callback methods that can be overridden to respond to specific chat-related events passed from the server. For example, ChannelHandler.onMessageReceived(BaseChannel, BaseMessage) is triggered whenever a message is received. The specifics of each received message is contained within the BaseChannel and BaseMessage arguments passed in the triggering callback.

By providing its own Event Handlers, the SendBird SDK allows a client to respond to asynchronous events without worrying about the plethora of issues surrounding client-server communication and multithreading. A chat application especially involves rapid exchanges of data that must take place in near real-time across potentially thousands of users. Therefore, the SDK optimizes communication and threading to ensure data integrity between users and servers. Add Event Handlers and implement the necessary callback methods to track events occurring within channels or a user's own device.

Channel Handler

Register a ChannelHandler to receive information whenever events occur within a channel.

You can register multiple Channel Handlers. UNIQUE_HANDLER_ID is a unique identifier that should be given to each handler. Typically, Event Handlers would be registered in each activity in order to stay up to date with changes in the channel, as well as notify the channel of the user's own activity.

SendBird.addChannelHandler(UNIQUE_HANDLER_ID, new SendBird.ChannelHandler() {
    @Override
    public void onMessageReceived(BaseChannel baseChannel, BaseMessage baseMessage) {
        // Received a chat message.
    }

    @Override
    public void onMessageDeleted(BaseChannel baseChannel, long messageId) {
      // When a message has been deleted.
    }

    @Override
    public void onChannelChanged(BaseChannel baseChannel) {
      // When a channel property has been changed.
      // More information on the properties can be found below.
    }

    @Override
    public void onChannelDeleted(String channelUrl, BaseChannel.ChannelType channelType) {
      // When a channel has been deleted.
    }

    @Override
    public void onReadReceiptUpdated(GroupChannel groupChannel) {
        // When read receipt has been updated.
    }

    @Override
    public void onTypingStatusUpdated(GroupChannel groupChannel) {
        // When typing status has been updated.
    }

    @Override
    public void onUserJoined(GroupChannel groupChannel, User user) {
      // When a new member joined the group channel.
    }

    @Override
    public void onUserLeft(GroupChannel groupChannel, User user) {
      // When a member left the group channel.
    }

    @Override
    public void onUserEntered(OpenChannel openChannel, User user) {
      // When a new user entered the open channel.
    }

    @Override
    public void onUserExited(OpenChannel openChannel, User user) {
      // When a new user left the open channel.
    }

    @Override
    public void onUserMuted(OpenChannel openChannel, User user) {
      // When a user is muted on the open channel.
    }

    @Override
    public void onUserUnmuted(OpenChannel openChannel, User user) {
      // When a user is unmuted on the open channel.
    }

    @Override
    public void onUserBanned(OpenChannel openChannel, User user) {
      // When a user is banned on the open channel.
    }

    @Override
    public void onUserUnbanned(OpenChannel openChannel, User user) {
      // When a user is unbanned on the open channel.
    }

    @Override
    public void onChannelFrozen(OpenChannel openChannel) {
      // When the open channel is frozen.
    }

    @Override
    public void onChannelUnfrozen(OpenChannel openChannel) {
      // When the open channel is unfrozen.
    }
});

onChannelChanged() is called whenever a one of the following channel properties have been changed :

  • Push preference
  • Last message (except in cases where the message is a silent Admin message)
  • Unread message count
  • Name, cover image, data, Custom Type
  • Operators (only applicable to Open Channels)
  • Distinct property (only applicable to Group Channels)

You should remove the ChannelHandler where the activity is no longer valid.

SendBird.removeChannelHandler(UNIQUE_HANDLER_ID);

Connection Handler

Register a ConnectionHandler to detect changes in the user's own connection status.

You can register multiple Connection Handlers. UNIQUE_HANDLER_ID is a unique identifier that should be given to each handler. Typically, Connection Handlers would be registered in each activity in order to monitor the state of the user's connection with the SendBird servers.

SendBird.addConnectionHandler(UNIQUE_HANDLER_ID, new SendBird.ConnectionHandler() {
    @Override
    public void onReconnectStarted() {
        // Network has been disconnected. Auto reconnecting starts.
    }

    @Override
    public void onReconnectSucceeded() {
        // Auto reconnecting succeeded.
    }

    @Override
    public void onReconnectFailed() {
        // Auto reconnecting failed. You should call `connect` to reconnect to SendBird.
    }
});

You should remove the Connection Handler when the activity is no longer valid.

SendBird.removeConnectionHandler(UNIQUE_HANDLER_ID);

Push Notifications for Android

You can set up Push Notifications so that users can receive messages even when they are offline.

Typically, you might want users to receive push notifications after their app goes into the background. Our latest SDK automatically detects if your app enters the background and updates the user's connection status to Disconnected. Therefore, in normal cases, you do not have to call disconnect() explicitly.

If you do not wish to use this auto-detection feature, invoke SendBird.setAutoBackgroundDetection(false).

Push notifications are only supported in Group Channels. The SDK does not provide an option for users to receive push notifications from Open Channels.

Follow these 4 steps to enable push notifications for Android.

  1. Generate an FCM Server API Key and FCM Sender ID in the Firebase Developer website.
  2. Register an FCM Server API Key and FCM Sender ID in your SendBird Dashboard.
  3. Set up the FCM client code in your Android application.
  4. Register your FCM registration token in the SendBird SDK and parse SendBird FCM messages.

(1) Generate FCM Server API Key and FCM Sender ID

If you already have an FCM Server API and FCM Sender ID, skip this section and go to (2) Register GCM Server API Key and GCM Sender ID.

  1. Visit the Firebase Console. If do not you have an existing Firebase project for your service, create a new project.

  2. Click the project card to navigate to the Project Overview page.

    Project detail

  3. From the top of left panel, click the gear icon on the right of the project name. From the drop-down menu, select Project Settings.

    Project settings

  4. Select the Cloud Messaging tab under Settings. Under Project Credentials, you should see your Server Key and Sender ID.

    Server key and Sender ID

  5. Navigate back to the General tab. If you have not already done so, add Firebase to your Android app by entering your package name and downloading the google-services.json file into your module root directory.

    Add Firebase to app

(2) Register FCM Server API Key and FCM Sender ID

Go to your SendBird Dashboard. Navigate to the Settings tab. In the Notifications section, you should see a checkbox enabling push notifications. Select the checkbox, then add your FCM Server Key and Sender ID by clicking Add FCM/GCM and pasting in the values.

Set Push Information

Or, you can also register a FCM Server API Key and FCM Sender ID via the Platform API

(3) Set up FCM client code in your Android application

Please follow Firebase's guide on adding configuration code to your Android project to handle FCM messages.

Set Up a Firebase Cloud Messaging Client App on Android

Google's sample project can also be a helpful reference.

Google FCM Sample Project

After completing this step, you should be able to generate a FCM registration token and have skeleton code to handle FCM messages.

(4) Register FCM Registration Token in the SendBird SDK and parse SendBird FCM messages

You should obtain a FCM Registration token from FirebaseInstanceIdService and pass it to the SendBird SDK. onTokenRefresh() in FirebaseInstanceIdService can be the perfect place to do this.

FirebaseInstanceIdService.class
@Override
public void onTokenRefresh() {
    // Get updated InstanceID token.
    String token = FirebaseInstanceId.getInstance().getToken();

    SendBird.registerPushTokenForCurrentUser(token, new SendBird.RegisterPushTokenWithStatusHandler() {
        @Override
        public void onRegistered(SendBird.PushTokenRegistrationStatus ptrs, SendBirdException e) {
            if (e != null) {
                return;
            }

            if (ptrs == SendBird.PushTokenRegistrationStatus.PENDING) {
                // Try registering the token after a connection has been successfully established.
            }
        }
    });
}

Register the token in any Activity that calls SendBird.connect().

SendBird.connect(userId, new SendBird.ConnectHandler() {
    @Override
    public void onConnected(User user, SendBirdException e) {
        if (e != null) {
            // Error.
            return;
        }

        if (FirebaseInstanceId.getInstance().getToken() == null) return;

        SendBird.registerPushTokenForCurrentUser(FirebaseInstanceId.getInstance().getToken(),
                new SendBird.RegisterPushTokenWithStatusHandler() {
            @Override
            public void onRegistered(SendBird.PushTokenRegistrationStatus status, SendBirdException e) {
                if (e != null) {
                    // Error.
                    return;
                }
            }
        });
    }
}

You will now receive FCM messages from SendBird. Within FirebaseMessagingService, parse the messages in order to display them to your user.

FirebaseMessagingService.class
@Override
public void onMessageReceived(RemoteMessage remoteMessage) {
    String message = remoteMessage.getData().get("message");
    JsonElement payload = new JsonParser().parse(remoteMessage.getData().get("sendbird"));
    sendNotification(message, payload);
}

private void sendNotification(String message, JsonElement payload) {  
  // Your own way to show notifications to users.
}

The message property is the string that is the received text message.
The payload property is a JSON string containing full information about the request.

{
  category: "messaging:offline_notification",
  type: string,              // Message Type, User or File or Admin
  message: string,           // User input message
  data: string,              // Custom data field
  app_id : string,           // application_id
  unread_message_count : int // Total unread count of the user
  channel: {
    channel_url: string,     // Group Channel URL
    name: string,            // Group Channel name
  },
  channel_type: string,      // messaging, group_messaging, chat
  sender: {
    id: string,              // Sender's unique ID
    name: string,            // Sender's nickname
    profile_url: string      // Sender's profile image url
  },
  recipient: {
    id: string,              // Recipient's unique ID
    name: string,            // Recipient's nickname
  },
  files: [],  // If a message is a file link, this array represents files
  translations: {} // If a message has translations, this dict has locale:translation.
}

Notification preferences

Push notifications can be turned on or off for a user.

public setPushNotification(boolean enable) {
    if (enable) {
        SendBird.registerPushTokenForCurrentUser(gcmRegToken,
                new SendBird.RegisterPushTokenWithStatusHandler() {
        @Override
        public void onRegistered(SendBird.PushTokenRegistrationStatus status, SendBirdException e) {
            if (e != null) {
                // Error.
                return;
            }
        }
    }
    else {
        // If you want to unregister the current device only, invoke this method.
        SendBird.unregisterPushTokenForCurrentUser(gcmRegToken,
                new SendBird.UnregisterPushTokenHandler() {
            @Override
            public void onUnregistered(SendBirdException e) {
                if (e != null) {
                    // Error.
                    return;
                }
            }
        });

        // If you want to unregister the all devices of the user, invoke this method.
        SendBird.unregisterPushTokenAllForCurrentUser(new SendBird.UnregisterPushTokenHandler() {
            @Override
            public void onUnregistered(SendBirdException e) {
                if (e != null) {
                    // Error.
                    return;
                }
            }
        });
    }        
}

Or, you can set push notification settings for individual channels.

// If you want to turn push notification for this channel on, set this true.
mGroupChannel.setPushPreference(TRUE_OR_FALSE, new GroupChannelSetPushPreferenceHandler() {
    @Override
    public void onResult(SendBirdException e) {
        if (e != null) {
            // Error.
            return;
        }
    }
});

If you want to snooze alarms (notifications) for some periods, invoke SendBird.setDoNotDisturb().

// The current logged-in user doesn't receive push notifications during the specified time.
SendBird.setDoNotDisturb(TRUE_OR_FALSE, START_HOUR, START_MIN, END_HOUR, END_MIN,
        TimeZone.getDefault().getID(),
        new SetDoNotDisturbHandler() {
    @Override
    public void onResult(SendBirdException e) {
        if (e != null) {
            // Error.
            return;
        }
    }
});

Push notification message templates

Message templates define how a message is displayed when a push notification arrives to a user's device. You can choose between the default template and the alternative template, both of which are customizable.

Message templates
Text Message File Message
Default template {sender_name}: {message} (e.g., John: Hello!) {filename} (e.g., squirrel.jpg)
Alternative template New message arrived New file arrived

{sender_name}, {message}, and {filename} are variables that represent the corresponding string values. Use these fields to customize message templates from your Dashboard Settings. The option is under Notifications - Push Notification Message Templates.

To choose whether a user receives messages in the form of the default template or the alternative template, call SendBird.setPushTemplate(templateName, handler). templateName can be one of two values: SendBird.PUSH_TEMPLATE_DEFAULT, or SendBird.PUSH_TEMPLATE_ALTERNATIVE.

SendBird.setPushTemplate(SendBird.PUSH_TEMPLATE_ALTERNATIVE, new SendBird.SetPushTemplateHandler() {
    @Override
    public void onResult(SendBirdException e) {
        if (e != null) {
            // Error!
        }
        // Push template successfully set to SendBird.PUSH_TEMPLATE_ALTERNATIVE.
    }
});

Note that the default configuration is SendBird.PUSH_TEMPLATE_DEFAULT.

You can check your current setting with SendBird.getPushTemplate().

SendBird.getPushTemplate(new SendBird.GetPushTemplateHandler() {
    @Override
    public void onResult(String s, SendBirdException e) {
        if (e != null) {
            // Error!
        }

        if (s.equals(SendBird.PUSH_TEMPLATE_DEFAULT)) {
            // Currently configured to use the default template.
        } else if (s.equals(SendBird.PUSH_TEMPLATE_ALTERNATIVE)) {
            // Currently configured to use the alternative template.
        }
    }
});

Caching Data

Storing a local copy of SendBird data in a device enables users to look through their messages and channels even while offline. It can also prevent the inefficiency of repeating queries upon each connection or device state change, as well as provide a smoother user experience by reducing data loading delays.

In this document, we will guide you on building a local cache using object serialization/deserialization, which is provided through the SDK. In the Basic caching using a file section, we provide instructions on building a simple cache that stores the most recent messages and channels in a file. In the Advanced caching using a database section, you can find instructions on caching data in an internal database, which enables you to store structured and queriable data.

Basic caching using a file

In this section, we will guide you on building a simple cache that stores a user's most recent messages and channels. This cache can be used to load data when a user views their channel list, or enters a channel to view their message history. Implementing even a basic cache such as this can greatly improve user experience, as users will no longer encounter empty lists of channels or messages when their connectivity is unstable.

In the steps described below, we will create a file per channel in the application's cache directory. Next, we will write serialized data into the file to store a set amount of recent messages. Finally, we will configure the app to first load messages from the cache, then replace them when the newest results are successfully fetched from the servers.

1. Object serialization / deserialization

In order to store SendBird objects such as messages, channels, and users in local storage, we provide serialization and deserialization methods through our SDK. Use serialize() to convert a SendBird object to binary data, which can then be natively stored in a file.

byte[] baseMessage.serialize()
BaseMessage BaseMessage.buildFromSerializedData(byte[] data)

byte[] baseChannel.serialize()
BaseChannel BaseChannel.buildFromSerializedData(byte[] data)

2. Saving messages

With serialization, you can store a channel and its most recent messages in a file. In this case, we are encoding the binary serialized data into a Base64 string. then storing each item in a new line. Normally, you would save data when onStop() is called in your user's chat screen.

public void save() {
    try {
        StringBuilder sb = new StringBuilder();
        if (mChannel != null) {
            // Convert current channel instance into a string.
            sb.append(Base64.encodeToString(mChannel.serialize(), Base64.DEFAULT | Base64.NO_WRAP));

            // Converts up to 100 messages within the channel into a string.
            BaseMessage message = null;
            for (int i = 0; i < Math.min(mMessageList.size(), 100); i++) {
                message = mMessageList.get(i);
                sb.append("\n");
                sb.append(Base64.encodeToString(message.serialize(), Base64.DEFAULT | Base64.NO_WRAP));
            }

            String data = sb.toString();
            String md5 = TextUtils.generateMD5(data);

            // Create a file within the app's cache directory.
            File appDir = new File(mContext.getCacheDir(), SendBird.getApplicationId());
            appDir.mkdirs();

            // Create a data file and a hash file within the directory.
            File dataFile = new File(appDir, TextUtils.generateMD5(SendBird.getCurrentUser().getUserId() + mChannel.getUrl()) + ".data");
            File hashFile = new File(appDir, TextUtils.generateMD5(SendBird.getCurrentUser().getUserId() + mChannel.getUrl()) + ".hash");

            try {
                String content = FileUtils.loadFromFile(hashFile);
                // If data has not been changed, do not save.
                if(md5.equals(content)) {
                    return;
                }
            } catch(IOException e) {
                // File not found. Save the data.
            }

            FileUtils.saveToFile(dataFile, data);
            FileUtils.saveToFile(hashFile, md5);
        }
    } catch(Exception e) {
        e.printStackTrace();
    }
}

In this case, MD5 hashing is used to generate a hash file for each stored data file. Using this hash file, you can check if the newly generated data differs from the one already stored in the cache, preventing unnecessary overwriting.

3. Loading messages

When your user enters a chat to view their message history, load saved messages from the cache.

public void load(String channelUrl) {
    try {
        File appDir = new File(mContext.getCacheDir(), SendBird.getApplicationId());
        appDir.mkdirs();

        File dataFile = new File(appDir, TextUtils.generateMD5(SendBird.getCurrentUser().getUserId() + channelUrl) + ".data");

        String content = FileUtils.loadFromFile(dataFile);
        String [] dataArray = content.split("\n");

        // Load the channel instance.
        mChannel = (GroupChannel) GroupChannel.buildFromSerializedData(Base64.decode(dataArray[0], Base64.DEFAULT | Base64.NO_WRAP));

        // Add the loaded messages to the currently displayed message list.
        for(int i = 1; i < dataArray.length; i++) {
            mMessageList.add(BaseMessage.buildFromSerializedData(Base64.decode(dataArray[i], Base64.DEFAULT | Base64.NO_WRAP)));
        }

        notifyDataSetChanged();
    } catch(Exception e) {
        // Nothing to load.
    }
}

After receiving an updated message list from the SendBird servers, clear the current message list and replace it with the updated list. In effect, messages from the cache would be overwritten almost instantly if the user's connection is normal.

4. Saving and loading channels

The process of caching channels is identical to caching messages. For the sake of brevity, an implementation will be provided without additional explanations.

public void save() {
    try {
        StringBuilder sb = new StringBuilder();
        if (mChannelList != null && mChannelList.size() > 0) {
            // Convert current channel into a String.
            GroupChannel channel = null;
            for (int i = 0; i < Math.min(mChannelList.size(), 100); i++) {
                channel = mChannelList.get(i);
                sb.append("\n");
                sb.append(Base64.encodeToString(channel.serialize(), Base64.DEFAULT | Base64.NO_WRAP));
            }
            // Remove first newline.
            sb.delete(0, 1);

            String data = sb.toString();
            String md5 = TextUtils.generateMD5(data);

            // Save the data into file.
            File appDir = new File(mContext.getCacheDir(), SendBird.getApplicationId());
            appDir.mkdirs();

            File hashFile = new File(appDir, TextUtils.generateMD5(SendBird.getCurrentUser().getUserId() + "channel_list") + ".hash");
            File dataFile = new File(appDir, TextUtils.generateMD5(SendBird.getCurrentUser().getUserId() + "channel_list") + ".data");

            try {
                String content = FileUtils.loadFromFile(hashFile);
                // If data has not been changed, do not save.
                if(md5.equals(content)) {
                    return;
                }
            } catch(IOException e) {
                // File not found. Save the data.
            }

            FileUtils.saveToFile(dataFile, data);
            FileUtils.saveToFile(hashFile, md5);
        }
    } catch(Exception e) {
        e.printStackTrace();
    }
}
public void load() {
    try {
        File appDir = new File(mContext.getCacheDir(), SendBird.getApplicationId());
        appDir.mkdirs();

        File dataFile = new File(appDir, TextUtils.generateMD5(SendBird.getCurrentUser().getUserId() + "channel_list") + ".data");

        String content = FileUtils.loadFromFile(dataFile);
        String [] dataArray = content.split("\n");

        // Add the loaded channels to the currently displayed channel list.
        for(int i = 0; i < dataArray.length; i++) {
            mChannelList.add((GroupChannel) BaseChannel.buildFromSerializedData(Base64.decode(dataArray[i], Base64.DEFAULT | Base64.NO_WRAP)));
        }

        notifyDataSetChanged();
    } catch(Exception e) {
        // Nothing to load.
    }
}

Advanced caching using a database

In this section, we will guide you on building your own local cache using a database. This has several advantages to storing raw data in a file, notably enabling queries on stored channels and messages. While here we base our examples on SQLite, it shouldn't be too difficult to follow these steps with any database of your choice, such as Realm.

1. Object serialization / deserialization

In order to store SendBird objects such as messages, channels, and users in local storage, we provide serialization and deserialization methods through our SDK. Use serialize() to convert a SendBird object to binary data, which can then be natively stored in your persistent database.

byte[] baseMessage.serialize()
BaseMessage BaseMessage.buildFromSerializedData(byte[] data)

byte[] baseChannel.serialize()
BaseChannel BaseChannel.buildFromSerializedData(byte[] data)

2. Caching messages

Table structure

A basic table to store messages would contain the following columns:

message_id channel_url message_ts payload
123123 sendbird_channel_234802384 1432039402823 Serialized data
234234 sendbird_channel_234802384 1432039403417 Serialized data

Caching procedure

  1. After fetching new messages using a MessageListQuery, serialize and insert each message into your database. However, we do recommend storing the message ID, timestamp, and channel URL in separate columns (i.e. using message.getMessageId(), message.getCreatedAt(), and message.getChannelUrl(). This will allow you to query the dataset later on.

  2. Before loading messages within a channel, order rows chronologically by message_ts. Then, deserialize each message and display them in your UI.

  3. When loading previous messages that are not currently stored in the local database, obtain the timestamp of the earliest stored message. Then, query for messages created before that value.

  4. Likewise, when loading new messages, query for messages with a later timestamp than the most recent message.

Example 1 - When entering a channel
// Get messages from local database
final SQLiteDatabase database = dbHelper.getWritableDatabase();

String selection = COLUMN_NAME_CHANNEL_URL + " = ?";
String[] selectionArgs = { CURRENT_CHANNEL_URL };

String sortOrder = COLUMN_NAME_TIMESTAMP + " DESC";

Cursor cursor = database.query(
        TABLE_NAME,
        null, // The columns to return; all if null.
        selection, // The columns for the WHERE clause
        selectionArgs, // The values for the WHERE clause
        null, // Don't group the rows
        null, // Don't filter by row groups
        sortOrder, // The sort order
        "30" // The limit
);

// Create a List of BaseMessages by deserializing each item.
List<BaseMessage> prevMessageList = new ArrayList<>();

while (cursor.moveToNext()) {
    byte[] data = cursor.getBlob(cursor.getColumnIndex(COLUMN_NAME_PAYLOAD));
    BaseMessage message = BaseMessage.buildFromSerializedData(data);
    prevMessageList.add(message);
}

cursor.close();

// Pass messages to adapter in order to display them in a RecyclerView, ListView, etc.
mMessageListAdapter.addMessages(prevMessageList);

// Get new messages from the SendBird servers
long latestStoredTs = prevMessageList.get(0).getCreatedAt(); // Get the timestamp of the last stored message.
MessageListQuery query = mChannel.createMessageListQuery();
query.next(latestStoredTs, 30, false, new MessageListQuery.MessageListQueryResult() {
    @Override
    public void onResult(List<BaseMessage> list, SendBirdException e) {
        if (e != null) {
            // Error!
            return;
        }
        // New messages successfully fetched.
        mMessageListAdapter.addMessages(list);

        // Insert each new message in your local database
        for (BaseMessage message : list) {
            // Store each new message into the local database
            ContentValues values = new ContentValues();
            values.put(COLUMN_NAME_ID, message.getMessageId());
            values.put(COLUMN_NAME_CHANNEL_URL, message.getChannelUrl());
            values.put(COLUMN_NAME_TIMESTAMP, message.getCreatedAt());
            values.put(COLUMN_NAME_PAYLOAD, message.serialize());

            database.insert(TABLE_NAME, null, values);
        }
    }
});

database.close();
Example 2 - When receiving new messages
SendBird.addChannelHandler(CHANNEL_HANDLER_ID, new SendBird.ChannelHandler() {
    @Override
    public void onMessageReceived(BaseChannel baseChannel, BaseMessage baseMessage) {
        if (baseChannel.getUrl().equals(mChannelUrl)) {
            // Pass the message to your adapter.
            mMessageListAdapter.addMessage(baseMessage);

            // Store the message in your local database.
            // Having a helper class or method for database transactions would probably be a good idea.
            final SQLiteDatabase database = dbHelper.getWritableDatabase();

            ContentValues values = new ContentValues();
            values.put(COLUMN_NAME_ID, baseMessage.getMessageId());
            values.put(COLUMN_NAME_CHANNEL_URL, baseMessage.getChannelUrl());
            values.put(COLUMN_NAME_TIMESTAMP, baseMessage.getCreatedAt());
            values.put(COLUMN_NAME_PAYLOAD, baseMessage.serialize());

            database.insert(TABLE_NAME, null, values);

            database.close();
        }
    }
});
Example 3 - When sending a message
mChannel.sendUserMessage(messageBody, new BaseChannel.SendUserMessageHandler() {
    @Override
    public void onSent(UserMessage userMessage, SendBirdException e) {
        if (e != null) {
            // Error!
            return;
        }

        // Display sent message to RecyclerView
        mMessageListAdapter.addMessage(userMessage);

        // Store the message in your local database.
        // Having a helper class or method for database transactions would probably be a good idea.
        final SQLiteDatabase database = dbHelper.getWritableDatabase();

        ContentValues values = new ContentValues();
        values.put(COLUMN_NAME_ID, userMessage.getMessageId());
        values.put(COLUMN_NAME_CHANNEL_URL, userMessage.getChannelUrl());
        values.put(COLUMN_NAME_TIMESTAMP, userMessage.getCreatedAt());
        values.put(COLUMN_NAME_PAYLOAD, userMessage.serialize());

        database.insert(TABLE_NAME, null, values);

        database.close();
    }
});

Caveats

Currently, it is difficult to sync deleted or edited messages. We are working to provide this feature in both our SDKs and APIs, and hope to release it soon.

3. Caching channels

Note: the examples in this section are based on Group Channels. To cache Open Channels, you would have to slightly improvise from the directions below (such as changing last_message_ts to channel_created_at).

Table structure

A basic table to store channels would contain the following columns:

channel_url last_message_ts payload
sendbird_channel_234802384 1432039402416 Serialized data
sendbird_channel_234802384 1432039403610 Serialized data

Caching procedure

  1. After fetching new channels using a OpenChannelListQuery or GroupChannelListQuery, serialize and insert each channel into your database. As with messages, we recommend storing the channel URL and timestamp of the last message in separate columns (i.e. using channel.getUrl() and channel.getLastMessage().getCreatedAt(). This will allow you to query the dataset later on.

  2. Before loading a list of channels, order rows chronologically by last_message_ts. Then, deserialize each channel and display them in your UI.

  3. Unlike messages, channels are relatively few in number and go through frequent property changes, such as cover URL changes, name changes, or deletions. Therefore, we recommend updating your cache by completely replacing the dataset when possible.

  4. When real-time changes are made to a channel list, update your cache.

Example 1 - When entering the channel list screen
// Load channels from local database
final SQLiteDatabase database = dbHelper.getWritableDatabase();

String sortOrder = COLUMN_NAME_LAST_MESSAGE_TIMESTAMP + " DESC";

Cursor cursor = database.query(
        TABLE_NAME,
        null, // The columns to return; all if null.
        null, // The columns for the WHERE clause
        null, // The values for the WHERE clause
        null, // Don't group the rows
        null, // Don't filter by row groups
        sortOrder, // The sort order
        "30" // The limit
);

// Create a List of BaseChannels by deserializing each item.
final List<BaseChannel> prevChannelList = new ArrayList<>();

while (cursor.moveToNext()) {
    byte[] data = cursor.getBlob(cursor.getColumnIndex(COLUMN_NAME_PAYLOAD));
    BaseChannel channel = BaseChannel.buildFromSerializedData(data);
    prevChannelList.add(channel);
}

cursor.close();

// Pass channels to adapter in order to display them in a RecyclerView, ListView, etc.
mChannelListAdapter.addChannels(prevChannelList);

// Get new channels from the SendBird servers
GroupChannelListQuery query = GroupChannel.createMyGroupChannelListQuery();
query.next(new GroupChannelListQuery.GroupChannelListQueryResultHandler() {
    @Override
    public void onResult(List<GroupChannel> list, SendBirdException e) {
        if (e != null) {
            // Error!
            return;
        }

        // Replace the current (cached) dataset
        mChannelListAdapter.clear();
        mChannelListAdapter.addChannels(prevChannelList);

        // Clear the current cache
        database.delete(TABLE_NAME, null, null);

        // Insert each new channel in your local database
        for (GroupChannel channel : list) {
            // Store each new channel into the local database
            ContentValues values = new ContentValues();
            values.put(COLUMN_NAME_CHANNEL_URL, channel.getUrl());
            values.put(COLUMN_NAME_LAST_MESSAGE_TIMESTAMP, channel.getLastMessage().getCreatedAt());
            values.put(COLUMN_NAME_PAYLOAD, channel.serialize());

            database.insert(TABLE_NAME, null, values);
        }
    }
});

database.close();
}
Example 2 - On real-time events such as additions or updates
SendBird.addChannelHandler(CHANNEL_HANDLER_ID, new SendBird.ChannelHandler() {
    ...
    @Override
    public void onChannelChanged(BaseChannel channel) {
        final SQLiteDatabase database = dbHelper.getWritableDatabase();

        String selection = COLUMN_NAME_CHANNEL_URL + " = ?";
        String[] selectionArgs = { CURRENT_CHANNEL_URL };

        // Get the changed channel from the local database using its URL.
        Cursor cursor = database.query(
                TABLE_NAME,
                null, // The columns to return; all if null.
                selection, // The columns for the WHERE clause
                selectionArgs, // The values for the WHERE clause
                null, // Don't group the rows
                null, // Don't filter by row groups
                null, // The sort order
                "1" // The limit
        );

        byte[] data = cursor.getBlob(cursor.getColumnIndex(COLUMN_NAME_PAYLOAD));

        ContentValues values = new ContentValues();
        values.put(COLUMN_NAME_CHANNEL_URL, channel.getUrl());
        long lastMessageTs = ((GroupChannel) channel).getLastMessage().getCreatedAt();
        values.put(COLUMN_NAME_LAST_MESSAGE_TIMESTAMP, lastMessageTs);
        values.put(COLUMN_NAME_PAYLOAD, channel.serialize());

        if (data != null) {
            // If the channel is in the current cache, update it.
            database.update(TABLE_NAME, values, selection, selectionArgs);
        } else {
            // If the channel is not currently cached, add it.
            database.insert(TABLE_NAME, null, values);
        }

        database.close();
    }
});

A similar process can be followed for onChannelDeleted(), onUserJoined(), and onUserLeft().

Miscellaneous

Client SDK-generated Error Codes

Errors on SendBird are defined in SendBirdError.java.

Error Value Description
ERR_INVALID_INITIALIZATION 800100 Initialization failed
ERR_CONNECTION_REQUIRED 800101 Connection required
ERR_INVALID_PARAMETER 800110 Invalid parameters
ERR_NETWORK 800120 Network error
ERR_NETWORK_ROUTING_ERROR 800121 Routing error
ERR_MALFORMED_DATA 800130 Malformed data
ERR_MALFORMED_ERROR_DATA 800140 Malformed error data
ERR_WRONG_CHANNEL_TYPE 800150 Wrong channel type
ERR_MARK_AS_READ_RATE_LIMIT_EXCEEDED 800160 Mark as read rate limit exceeded
ERR_QUERY_IN_PROGRESS 800170 Query is in progress
ERR_ACK_TIMEOUT 800180 Command ack timed out
ERR_LOGIN_TIMEOUT 800190 Login timed out
ERR_WEBSOCKET_CONNECTION_CLOSED 800200 Connection closed
ERR_WEBSOCKET_CONNECTION_FAILED 800210 Connection failed
ERR_REQUEST_FAILED 800220 Request failed

Server-generated Error Codes

These errors are not defined in the java file.

Code Description
400100 Parameter Error - String value is required
400101 Parameter Error - Number value is required
400102 Parameter Error - List value is required
400103 Parameter Error - Json value is required
400104 Parameter Error - Boolean value is required
400105 Parameter Error - Not all the required fields are arrived
400106 Parameter Error - Value must be a positive number
400107 Parameter Error - Value must be a negative number
400108 User doesn't have an access to channels or messages
400110 Parameter Error - Length of value is not valid
400111 Parameter Error - Unknown
400112 Parameter Error - Should provide two different values
400151 Parameter Error - Not allowed characters
400201 Object(Channel/User/Message) not found
400202 Unique constraint violation
400300 User Authentication Error - Deactivated user
400301 User Authentication Error - Deleted user or user not found
400302 User Authentication Error - Invalid access token
400303 User Authentication Error - Unexpected error
400304 User Authentication Error - Application not found
400305 User Authentication Error - User id is too long
400306 User Authentication Error - Plan quota exceeded
400307 User Authentication Error - Requests from authorized domain
400601 The push token registration failure
400602 The push token removal failure
400910 Requests are rate-limited
400920 Tried to access non-allowed features under your plan
500901 Unexpected errors
900010 Try to send messages without login
900020 Try to send messages to group channels not belong to the user
900021 Try to send messages after getting deactivated
900030 Try to send messages to the channels when the guest policy is read-only on dashboard
900041 The user is muted on this channel
900050 User cannot send messages to frozen channels
900060 Message is blocked by profanity filter
900070 Try to send messages to deleted channels
900080 You cannot send messages on 1-on-1 group channel when receiver is blocked
900081 You cannot send messages on 1-on-1 group channel when receiver is deactivated
900100 Try to enter the banned channel
900200 You are blocked because you sent too many messages in short period

Migration from v2

SDK 3.0 (v3) is a fully innovated chat solution for mobile apps and websites. The structure is elegant and the performance has increased dramatically.

  • OpenChannel and GroupChannel are added for Open Channel and Group Channel related features respectively.
  • UserMessage, FileMessage and AdminMessage are added to handle messages.
  • Callbacks and queries are neatly arranged.

We strongly encourage you in moving to v3 from the previous version and here are the migration tips.

Installation

If you are using gradle, just change the version of dependencies in build.gradle at app level (not project level).

Before
dependencies {
    compile 'com.sendbird.sdk:sendbird-android-sdk:2.2.16' // 2.2.16 is the latest v2 SDK as of writing this doc.
}
After
dependencies {
    compile 'com.sendbird.sdk:sendbird-android-sdk:3.0.4' // 3.0.4 is the latest SDK as of writing this doc.
}

Authentication

Initialization

You still need to initialize SendBird once when your application begins.

Before
SendBird.init(context, APP_ID);
After
SendBird.init(APP_ID, context);

Login

login is no longer used in v3. Calling connect just once after init is all you have to do.

Before
SendBird.login(LoginOption option);
After
SendBird.connect(USER_ID, ConnectHandler handler); // When you allow guest login.
SendBird.connect(USER_ID, ACCESS_TOKEN, ConnectHandler handler); // When you allow only permitted user login.

If you want to update user information such as nickname, profile image or FCM/GCM push tokens, now you can use updateCurrentUserInfo and registerPushTokenForCurrentUser after connection is established.

Open Channel (Previous Open Chat Channel)

From v3, we call Open Chat Channel as Open Channel. Members having entered an Open Channel are referred to as Participants of the channel.
OpenChannel and OpenChannelListQuery handle Open Channel related features.

Getting a List of Open Channels

Before
ChannelListQuery channelListQuery = SendBird.queryChannelList();
channelListQuery.next(ChannelListQueryResult handler);
After
OpenChannelListQuery channelListQuery = OpenChannel.createOpenChannelListQuery();
channelListQuery.next(OpenChannelListQueryResultHandler handler);

Connecting to an Open Channel

You don't have to do anything to connect to each Open Channel in v3. All required connections are automatically made once you have called connect after init.
Plus, entering an Open Channel is much simpler in v3. In v2, you needed to fetch channel URL and call join, queryMessageList and connect.
Now getChannel and enter is here for your convenience.

Before
SendBird.join(CHANNEL_URL);
SendBird.queryMessageList(CHANNEL_URL).prev(Long.MAX_VALUE, LIMIT, new MessageListQueryResult() {
    @Override
    public void onResult(List<MessageModel> messageModels) {
        // Connect to SendBird with max messages timestamp to receive new messages since last query.
        SendBird.connect(MAX_MESSAGE_TIMESTAMP);
    }

    @Override
    public void onError(Exception e) {
    }
});
After
OpenChannel.getChannel(CHANNEL_URL, new OpenChannelGetHandler() {
    @Override
    public void onResult(OpenChannel openChannel, SendBirdException e) {
        openChannel.enter(OpenChannelEnterHandler handler);
    }
});

Disconnecting an Open Channel

You don't have to do anything to disconnect an Open Channel in v3. All connections are automatically disconnected when you call disconnect on application termination.
If you want a user to leave an Open Channel, just call exit.

Before
SendBird.leave(CHANNEL_URL);
SendBird.disconnect();
After
OpenChannel.getChannel(CHANNEL_URL, new OpenChannelGetHandler() {
    @Override
    public void onResult(OpenChannel openChannel, SendBirdException e) {
        openChannel.exit(OpenChannelExitHandler handler);
    }
});

Sending Messages

Mentioned message is NOT currently supported in v3.

Before
SendBird.send(MESSAGE);
SendBird.sendWithData(MESSAGE, DATA);

SendBird.uploadFile(FILE, TYPE, SIZE, CUSTOM_FIELD, SendBirdFileUploadEventHandler handler);
FileInfo fileInfo = FileInfo.build(URL_FROM_ABOVE, NAME, TYPE, SIZE, CUSTOM_FIELD);
SendBird.sendFile(fileInfo);
After
openChannel.sendUserMessage(MESSAGE, DATA, SendUserMessageHandler handler);
openChannel.sendFileMessage(FILE, FILE_NAME, FILE_TYPE, FILE_SIZE, CUSTOM_DATA, SendFileMessageHandler handler);

Receiving Messages

ChannelHandler replaces SendBirdEventHandler. Multiple ChannelHandlers are allowed.

Before
SendBird.setEventHandler(new SendBirdEventHandler() {
    ...
});
After
SendBird.addChannelHandler(UNIQUE_HANDLER_ID, new SendBird.ChannelHandler() {
    @Override
    public void onMessageReceived(BaseChannel baseChannel, BaseMessage baseMessage) {
    }
});

Loading Previous Messages

Before
SendBird.queryMessageList(SendBird.getChannelUrl()).prev(Long.MAX_VALUE, LIMIT, MessageListQueryResult handler);
After
MessageListQuery messageListQuery = openChannel.createMessageListQuery();
messageListQuery.prev(EARLIEST_MESSAGE_TIMESTAMP, LIMIT, REVERSE_ORDER, MessageListQueryResult handler);

Getting a List of All Participants in an Open Channel

Participant means online users in a certain Open Channel. Once a user leaves the Open Channel, the user is no longer participant of that channel and query does not include him/her.

Before
SendBird.queryMemberList(CHANNEL_URL, TRUE_OR_FALSE).get(MemberListQueryResult handler);
After
UserListQuery query = openChannel.createParticipantListQuery();
query.next(UserListQueryResultHandler handler);

Group Channel (Previous Messaging Channel)

From v3, we call Messaging Channel as Group Channel. Users having joined a Group Channel are referred to as Members of the channel.
GroupChannel and GroupChannelListQuery handle Group Channel related features.

Creating a Group Channel

All startMessaging related methods are replaced by createChannel and createChannelWithUserIds.

Before
SendBird.startMessaging(USER_IDS);
After
GroupChannel.createChannelWithUserIds(USER_IDS, TRUE_OR_FALSE, GroupChannelCreateHandler handler);

Getting a List of Group Channels

Before
MessagingListQuery messagingChannelListQuery = SendBird.queryMessagingChannelList();
messagingChannelListQuery.setLimit(LIMIT);
messagingChannelListQuery.next(MessagingChannelListQueryResult handler);
After
GroupChannelListQuery query = GroupChannel.createMyGroupChannelListQuery();
query.setIncludeEmpty(TRUE_OR_FALSE); // If you want to only retrieve channels having previous messages, set this false. 
query.next(GroupChannelListQueryResultHandler handler);

Connecting to a Group Channel

You don't have to do anything to connect to each Group Channel in v3. All required connections are automatically made once you have called connect after init.

Before
SendBird.queryMessageList(CHANNEL_URL).load(Long.MAX_VALUE, PREV_LIMIT, NEXT_LIMIT, new MessageListQueryResult() {
    @Override
    public void onResult(List<MessageModel> messageModels) {
        SendBird.join(CHANNEL_URL);
        SendBird.connect(MAX_MESSAGE_TIMESTAMP);
    }

    @Override
    public void onError(Exception e) {
    }
});
After
// Do nothing.

Disconnecting a Group Channel

You don't have to do anything to disconnect a Group Channel in v3. All connections are automatically disconnected when you call disconnect on application termination.

Before
SendBird.disconnect();
After
// Do nothing.

Inviting Users to an Existing Channel

Before
SendBird.inviteMessaging(CHANNEL_URL, USER_ID);
After
groupChannel.inviteWithUserIds(USER_IDS, GroupChannelInviteHandler handler);

Removing a User from Channel Members

Before
SendBird.endMessaging(CHANNEL_URL);
After
groupChannel.leave(GroupChannelLeaveHandler handler);

Sending Messages

Mentioned message is NOT currently supported in v3.

Before
SendBird.send(MESSAGE);
SendBird.sendWithData(MESSAGE, DATA);

SendBird.uploadFile(FILE, TYPE, SIZE, CUSTOM_FIELD, SendBirdFileUploadEventHandler handler);
FileInfo fileInfo = FileInfo.build(URL_FROM_ABOVE, NAME, TYPE, SIZE, CUSTOM_FIELD);
SendBird.sendFile(fileInfo);
After
groupChannel.sendUserMessage(MESSAGE, DATA, SendUserMessageHandler handler);
groupChannel.sendFileMessage(FILE, FILE_NAME, FILE_TYPE, FILE_SIZE, CUSTOM_DATA, SendFileMessageHandler handler);

Receiving Messages

ChannelHandler replaces SendBirdEventHandler. Multiple ChannelHandlers are allowed.

Before
SendBird.setEventHandler(new SendBirdEventHandler() {
    ...
});
After
SendBird.addChannelHandler(UNIQUE_HANDLER_ID, new SendBird.ChannelHandler() {
    @Override
    public void onMessageReceived(BaseChannel baseChannel, BaseMessage baseMessage) {
    }
});

Loading Previous Messages

Before
SendBird.queryMessageList(SendBird.getChannelUrl()).prev(Long.MAX_VALUE, LIMIT, MessageListQueryResult handler);
After
PreviousMessageListQuery prevMessageListQuery = groupChannel.createPreviousMessageListQuery();
prevMessageListQuery.load(LIMIT, TRUE_OR_FALSE, MessageListQueryResult handler);

Monitoring Multiple Channels

ChannelHandler replaces SendBirdNotificationHandler. For details, please refer to Event Handler.

Before
SendBird.registerNotificationHandler(SendBirdNotificationHandler handler);
After
SendBird.addChannelHandler(UNIQUE_HANDLER_ID, ChannelHandler handler);

Getting a List of All Members

Before
messagingChannel.getMembers();
After
groupChannel.getMembers();

Typing Indicators

Before
SendBird.typeStart();
SendBird.typeEnd();
After
groupChannel.startTyping();
groupChannel.endTyping();

Getting Read Receipt

From v3, various of methods to get read receipt are possible. You can get a timestamp for a certain member just like in v2 or the automatically calculated read receipt as well.

Before
long timestamp = messagingChannel.getLastReadMillis(USER_ID);
After
long timestamp = groupChannel.getLastSeenAtByWithUserId(USER_ID);
int unreadCount = mGroupChannel.getReadReceipt(MESSAGE);

Broadcasting Read Status

Before
SendBird.markAsRead();
After
groupChannel.markAsRead();

Channel Metadata

Meta data and meta counter can be created, updated and read in v3 by counter parts of methods in v2.

Meta Data

Before
SendBird.queryChannelMetaData(CHANNEL_URL).set(DATA_MAP, ChannelMetaDataQueryResult handler);
SendBird.queryChannelMetaData(CHANNEL_URL).get(KEYS, ChannelMetaDataQueryResult handler);
After
channel.createMetaData(DATA_MAP, MetaDataHandler handler);
channel.updateMetaData(DATA_MAP, MetaDataHandler handler);
channel.getMetaData(KEYS, MetaDataHandler handler);

Meta Counter

Before
SendBird.queryChannelMetaCounter(CHANNEL_URL).increase(KEY, AMOUNT, ChannelMetaCounterQueryResult handler);
SendBird.queryChannelMetaCounter(CHANNEL_URL).get(KEYS, ChannelMetaCounterQueryResult handler);
After
channel.increaseMetaCounters(COUNTER_MAP, MetaCounterHandler handler);
channel.getMetaCounters(KEYS, MetaCounterHandler handler);

Event Handler

SendBirdEventHandler, SendBirdSystemEventHandler and SendBirdNotificationHandler are replaced by ChannelHandler.
Please refer to Event Handler for details.

Push Notifications

From v3, you have to call registerPushTokenForCurrentUser explicitly after connection is made to register push tokens.
Please refer to Push Notifications for details.

Before
SendBird.login(SendBird.LoginOption.build(UUID).setUserName(NICKNAME).setGCMRegToken(TOKEN));
After
SendBird.connect(USER_ID, new SendBird.ConnectHandler() {
    @Override
    public void onConnected(User user, SendBirdException e) {
        SendBird.registerPushTokenForCurrentUser(TOKEN, RegisterPushTokenWithStatusHandler handler);
    }
});

Change Log

v3.0.28(Jun 20, 2017)

  • Fixed bug BaseMessage.buildFromSerializedData incurring crash for messages serialized by versions prior to v3.0.27.

v3.0.27(Jun 15, 2017)

  • Added custom type filter to OpenChannelListQuery and GroupChannelListQuery.
  • Added messaging editing feature.
  • Added file uploading cancel.

v3.0.26(May 19, 2017)

  • Added OpenChannel deletion.

v3.0.25(May 9, 2017)

  • Fixed custom host bug.

v3.0.24(Apr 19, 2017)

  • Added connect() custom host feature.

v3.0.23(Apr 12, 2017)

  • Added real size property for generated thumbnails.
  • Added progress handler for file uploading.
  • Support file uploading on background.

v3.0.22(Mar 9, 2017)

  • Fixed bug GroupChannel.getUnreadMessageCount() returning wrong value not covered on v3.0.20.

v3.0.21(Mar 8, 2017)

  • Added getTotalUnreadChannelCount() to GroupChannel to enable you get channel count of having unread messages.
  • Fixed getTotalUnreadMessageCount() to return correct value when it is called in ChannelHandler.onChannelChanged after markAsRead() is called.

v3.0.20(Mar 6, 2017)

  • Added getting messages methods to BaseChannel and they support type/customType filtering.
  • Added option for using channel member's profile URL and nickname as message sender's.
  • Support updating channel member's profile URL and nickname automatically.
  • Fixed bug GroupChannel.getUnreadMessageCount() not returning correct value.

v3.0.19(Mar 2, 2017)

  • Added file encryption and access control feature.
  • Provide serialization/deserialization for user, channel and message objects for developers to take advantage of their own local cache.
  • Sender is excluded from read receipt.

v3.0.18(Feb 22, 2017)

  • Fixed bug GroupChannel.getReadReceipt() always returning the count of members of the channel after the first connection.
  • From now, empty string as user ID is not accepted.

v3.0.17(Feb 10, 2017)

  • Fixed bug calling ChannelHandler.onReadReceiptUpdated() even when markAsRead() is called from the same user connected on other devices.
  • Improved stability by removing occasional NPE crash on connection or reconnection.

v3.0.16(Jan 31, 2017)

  • Added reconnect() to support explicit reconnection process.
  • Added removeAllConnectionHandlers() and removeAllChannelHandlers().
  • Improved network call performance after reconnection is established.
  • Fixed bug removing connection handlers and channel handlers when disconnect() is called.
  • Improved stability.

v3.0.15(Jan 18, 2017)

  • Fixed bugs returning wrong unread count of messages when users are invited to GroupChannel.
  • Fixed bugs occurring occasional crash when getReadReceipt(), getReadMembers() or etc are called.

v3.0.14(Jan 4, 2017)

  • Added thumbnail generating option when image file is uploaded.

v3.0.13(Jan 3, 2017)

  • Changed connection protocol to avoid connection reset which can occur when application runs behind proxy.

v3.0.12(Dec 23, 2016)

  • Added push notification template option, which gives option to users the way to display push notification messages.
  • Improved to try connection without delay when reconnection is needed.

v3.0.11(Dec 16, 2016)

  • Added unique push token registration option, which makes sure to maintain only one GCM/FCM token for the current user.

v3.0.10(Dec 7, 2016)

  • Added GroupChannel.isPushEnabled() to check push preference for each group channel.
  • Deprecated GroupChannel.getPushPreference().
  • Fixed randomly occurring NPE crash when user join event or read receipt update event happen.
  • Improved stability.

v3.0.9(Nov 30, 2016)

  • Added user IDs filters and query type to GroupChannelListQuery.
  • Added channel custom type for OpenChannel and GroupChannel.
  • Fixed to call ChannelHandler.onChannelChanged when unread message count or last message has been updated.

v3.0.8(Nov 23, 2016)

  • Fixed to update last message of group channel when UserMessage or FileMessage is successfully sent.
  • Improved stability.

v3.0.7(Nov 4, 2016)

  • Fixed connection bug.

v3.0.6(Nov 3, 2016)

  • Added group channel list search by a member nickname. (Search by multiple nicknames option in v3.0.5 is no more supported.)
  • Added auto-translating feature to UserMessage.
  • Improved connection performance.
  • Improved stability.

v3.0.5(Oct 31, 2016)

  • Added custom type to messages.
  • Added group channel list search by member nicknames and user IDs.
  • Added creating and updating channel cover image with binary file.

v3.0.4(Sep 30, 2016)

  • Fixed file uploading timeout.

v3.0.3(Sep 27, 2016)

  • Supports Android 7.0 (Nougat) and FCM.
  • Fixed to increase unread message count only for others' message reception.
  • Improved performance and stability.

v3.0.2(Sep 6, 2016)

  • Added features like filtered user list, open channel keyword search, push preference setting, etc.
  • Added auto background detection.
  • Improved stability.

v3.0.1(Sep 1, 2016)

  • Fixed minor bugs.

v3.0.0(Aug 12, 2016)

  • First release.