LiveData

Last Edit: May 05 2020

The livedata library exposes easy to use livedata objects for messages, reads, typing, members, watchers and more. It also adds support for offline chat. This means you can send messages, reactions and even create channels while you're offline. When the user comes back online the library will automatically recover lost events and retry sending messages.

A note about RXJava: At the moment our livedata library doesn't directly support RXJava. If you prefer to use RXJava we recommend that you integrate using the low level client. Alternatively you could use LiveDataReactiveStreams to convert livedata to RX.

Install

Open up your application level build.gradle file and add the following line


dependencies {
   ...
   implementation 'com.github.GetStream:stream-chat-android-livedata:0.4.1'
   ...
}
                    

Building the ChatDomain

The livedata library follows the clean architecture concepts. You can build your chatDomain object like this:


val chatDomain = ChatDomain.Builder(context, client, data.user1).offlineEnabled().userPresenceEnabled().build()
                    

ChatDomain chatDomain = new ChatDomain.Builder(getContext().getApplicationContext(), client, user).offlineEnabled().userPresenceEnabled().build();
                    

You should only have a single chatDomain object in your application. Note that the chatDomain is user specific so you'll want to disconnect your chatDomain and create a new one when the user in your app changes. You can retrieve the current chatDomain from anywhere in your app:


val chatDomain = ChatDomain.instance()
                    

ChatDomain chatDomain = ChatDomain.instance();
                    

To disconnect you can make the following call:


chatDomain.disconnect()
                    

chatDomain.disconnect();
                    

By default the retry policy for the chatDomain is set to retry 5 times and wait attempt*1000 milliseconds in between attempts. It will also retry when the connection recovers. You can customize the default behavior like this:


chatDomain.retryPolicy = object : RetryPolicy {
  override fun shouldRetry(client: ChatClient, attempt: Int, error: ChatError): Boolean {
    return attempt <= 3
  }
  override fun retryTimeout(client: ChatClient, attempt: Int, error: ChatError): Int? {
    return 1000 * attempt
  }
}
                    

chatDomain.setRetryPolicy(new RetryPolicy() {
  @Override
  public boolean shouldRetry(@NotNull ChatClient client, int attempt, @NotNull ChatError error) {
    return attempt <= 3;
  }

  @Nullable  
  @Override
  public Integer retryTimeout(@NotNull ChatClient client, int attempt, @NotNull ChatError error) {
   return 1000*attempt;
  }
});
                    

The chatDomain exposes the following LiveData objects for the user:

Name Type Description
initialized LiveData<Boolean> If the connection with Stream's chat API has been initialized
online LiveData<Boolean> If you are currently online
totalUnreadCount LiveData<Int> The total count of unread messages for the current user
channelUnreadCount LiveData<Int> The number of unread channels for the current user
muted LiveData<List<Mute>> Who you've muted, automatically updated when new mute events are received.
banned LiveData<Boolean> If you're currently banned or not

Watching a Channel

All operations on the chatDomain are exposed via useCases. The following example will show you how to get the messages, reads and typing information for a channel:


val result = chatDomain.useCases.watchChannel("messaging:123", 0).execute()
var channelController = result.data()
// livedata objects for messages reads and typing
channelController.messages
channelController.reads
channelController.typing
                    

Result<ChannelController> result = chatDomain.getUseCases().getWatchChannel().invoke("messaging:123", 30).execute();
ChannelController channelController = result.data();
// livedata objects for messages reads and typing
channelController.getMessages();
channelController.getReads();
channelController.getTyping();
                    

All use cases return a Call object. You can either execute the call in the current thread, or enqueue it with a callback. All calls return a Result object. The result object has a data() call, error(), isError and isSuccess. This behavior is identical to the low level client.

As you see in the example above, the channel controller exposes the messages, reads and typing livedata objects. The full list of livedata objects available is:

NAME TYPE DESCRIPTION
messages LiveData<List<Message>> list of messages sorted by message.createdAt
watcherCount LiveData<Int> the number of people currently watching the channel
watchers LiveData<List<User>> the list of users currently watching this channel. note that for large channels you will need to paginate to get all watchers
typing LiveData<List<User>> who is currently typing (current user is excluded from this)
reads LiveData<List<ChannelUserRead>> how far every user in this channel has read
read LiveData<ChannelUserRead> read status for the current user
unreadCount LiveData<Int> unread count for this channel, calculated based on read state (this works even if you're offline)
members LiveData<List<Member>> the list of members of this channel
loading LiveData<Boolean> if we are currently loading
loadingOlderMessages LiveData<Boolean> if we are currently loading older messages
loadingNewerMessages LiveData<Boolean> if we are currently loading newer messages
endOfOlderMessages LiveData<Boolean> set to true if there are no more older messages to load
endOfNewerMessages LiveData<Boolean> set to true if there are no more newer messages to load

You can load more messages for a channel like this:


val result = chatDomain.useCases.loadOlderMessages("messaging:123", 10).execute(
                    

Result<Channel> result = chatDomain.getUseCases().getLoadOlderMessages().invoke("messaging:123", 10).execute();
                    

Sending a Message

This is how you can send a message


var message = Message()
var result = chatDomain.useCases.sendMessage(message).execute()
                    

Result<Message> result = chatDomain.getUseCases().getSendMessage().invoke(message).execute();
                    

The following useCases help you update chat data

  • createChannel

  • sendMessage

  • editMessage

  • deleteMessage

  • sendReaction

  • deleteReaction

  • keystroke (sends a typing event as needed)

  • stopTyping (sends a stop typing event, only if needed)

  • markRead (marks the channel as read)

Querying Channels

The other most commonly used interface for chat is showing a list of channels. You can query channels like this:


val filter = Filters.and(Filters.eq("type", "messaging"), Filters.`in`("members", listOf("myuserid")))
val result = chatDomain.useCases.queryChannels(data.filter1).execute()
val queryChannelsController = result.data()
// livedata objects exposed by the queryChannelsController
queryChannelsController.channels
queryChannelsController.loading
queryChannelsController.endOfChannels
                    

List<String> members = new ArrayList<>();
members.add("myuserid");
FilterObject filter = Filters.and(Filters.eq("type", "messaging"), Filters.in("members", members));

Result<List<Channel>> result = chatDomain.getUseCases().queryChannels(filter).execute();
QueryChannelsController queryChannelsController = result.data();
// livedata objects exposed by the queryChannelsController
queryChannelsController.getChannels();
queryChannelsController.getLoading();
queryChannelsController.getEndOfChannels();
                    

Loading more channels for a particular query can be done like this:


val result = chatDomain.useCases.queryChannelsLoadMore(filter, sort, 10).execute()
                    

chatDomain.getUseCases().getQueryChannelsLoadMore().invoke(filter, sort, pageSize,20);
                    

Unread Counts

Stream provides 2 unread counts for a user. Here's how to retrieve livedata objects for them:


val totalUnreadCountLive = chatDomain.useCases.getTotalUnreadCount().execute().data()
val unreadChannelCountLive = chatDomain.useCases.getUnreadChannelCount().execute().data()
                    

LiveData<Int> totalUnreadCountLive = chatDomain.getUseCases().getTotalUnreadCount().invoke().execute().data();
LiveData<Int> unreadChannelCountLive = chatDomain.getUseCases().getUnreadChannelCount().invoke().execute().data();
                    

Threads

Here's how you can retrieve the messages for a thread:


val result = chatDomain.useCases.getThread("messaging:123", "parent-id-here").execute()
val threadController = result.data()
// livedata objects exposed by the threadController
threadController.messages
threadController.loadingOlderMessages
threadController.endOfOlderMessages
                    

Result<ThreadController> result = chatDomain.getUseCases().getGetThread().invoke(cid, parentId).execute();
ThreadController threadController = result.data()
// livedata objects exposed by the threadController
threadController.getMessages();
threadController.getLoadingOlderMessages();
threadController.getEndOfOlderMessages();
                    

Loading more messages for a thread can be done like this:


val result = chatDomain.useCases.threadLoadMore("messaging:123", "parent-id-here", 100).execute()
                    

Result<List<Message>> result = chatDomain.getUseCases().getThreadLoadMore().invoke(cid, parentId, 30).execute();