Android Offline

LAST EDIT Apr 26 2021

The offline library exposes easy to use LiveData/StateFlow 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. The offline storage also enables it to implement optimistic UI updates.

Optimistic UI Updates explained: If you send a message using the offline support lib it will immediately update the underlying LiveData objects and the connected UI. The actual API call happens in the background. Especially in high latency or unreliable network conditions this massively improves the perceived performance of the chat interface.

Install

Copied!

Open up your app's module level build.gradle file and add the following line:

For the latest version, check our GitHub releases page.

Building the ChatDomain

Copied!

The offline library builds on top of the client library. You can build your ChatDomain object like this:

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:

To disconnect from ChatDomain, just disconnect from ChatClient and it will trigger a disconnection for ChatDomain too:

By default, the retry policy for the ChatDomain is set to retry three 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:

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

Copied!

All operations on the ChatDomain are exposed via use case functions returning a Call instance. The following example shows how to get the messages, reads and typing information for a channel:

All use case functions return a Call object. You can run a Call in one of three ways:

  • execute synchronously in the current thread (make sure this is not the UI thread!),

  • enqueue asynchronously with a callback, which executes the call in the background and then notifies you on the UI thread,

  • await it inside a coroutine, which is a suspending operation.

All calls return a Result object, which is either successful or an error, which you should always check this using isSuccess or isError, and then access either data() or error(). 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

messagesState

LiveData<MessagesState>

sealed class for various message states

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

channelData

LiveData<ChannelData>

updated whenever we receive a channel.updated event

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:

Sending a Message

Copied!

This is how you can send a message

The following use case functions 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

Copied!

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

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

The following LiveData objects are available on a query channels controller

Name

Type

DESCRIPTION

channels

LiveData<List<Channel>>

The list of channels for this query. Will update when the underlying channel data changes or new channels are added.

channelsState

LiveData<ChannelsState>

Similar to channels but exposes the various loading states via a sealed class

loading

LiveData<Boolean>

If we are currently loading the first screen of channels

loadingMore

LiveData<Boolean>

If we are currently loading more channels

Unread Counts

Copied!

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

Threads

Copied!

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

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