val offlinePluginFactory = StreamOfflinePluginFactory(
config = Config(
backgroundSyncEnabled = true,
userPresence = true,
persistenceEnabled = true,
uploadAttachmentsNetworkType = UploadAttachmentsNetworkType.NOT_ROAMING,
useSequentialEventHandler = false,
),
appContext = context,
)
ChatClient.Builder(apiKey, context).withPlugin(offlinePluginFactory).build()
ChatDomain Migration
v5.0.0 release brings a big change to the offline support library - it replaces ChatDomain
with a new, easy-to-use OfflinePlugin
.
You can read more about the motivation behind the effort and featured changes in the announcement blog post.
The guide will help you with migrating from version 4.X.X
to 5.X.X
.
Initialization
Compared to ChatDomain
, which was a standalone singleton with its own API, OfflinePlugin
is a plugin that should be provided to the ChatClient.Builder
:
// Enables background sync which is performed to sync user actions done while offline.
boolean backgroundSyncEnabled = true;
// Enables the ability to receive information about user activity such as last active date and if they are online right now.
boolean userPresence = true;
// Enables using the database as an internal caching mechanism.
boolean persistenceEnabled = true;
// An enumeration of various network types used as a constraint inside upload attachments worker.
UploadAttachmentsNetworkType uploadAttachmentsNetworkType = UploadAttachmentsNetworkType.NOT_ROAMING;
// Whether the SDK will use a new sequential event handling mechanism.
boolean useSequentialEventHandler = false;
StreamOfflinePluginFactory offlinePluginFactory = new StreamOfflinePluginFactory(new Config(backgroundSyncEnabled, userPresence, persistenceEnabled, uploadAttachmentsNetworkType, useSequentialEventHandler), context);
new ChatClient.Builder("apiKey", context).withPlugin(offlinePluginFactory).build();
From this point onward, the caching mechanism will be enabled and you’ll have access to state objects.
You can read more about the initialization process in Getting Started page.
Requesting Data
ChatDomain
mirrored some of the ChatClient
API while adding offline support. For example, if you wanted to send a message with offline support, you were supposed to call:
val message = Message(cid = cid, text = "New message")
chatDomain.sendMessage(message).enqueue { result ->
if (result.isSuccess) {
// Handle success
} else {
// Handler error
}
}
chatDomain.sendMessage(message).enqueue(result -> {
if (result.isSuccess()) {
// Handle success
} else {
// Handle error
}
});
In the new approach, all the operations should be performed using the ChatClient
so you don’t need to bother yourself whether to use ChatDomain
or ChatClient
.
You can use the snippet below to send a message:
val message = Message(cid = cid, text = messageText)
chatClient.channel(cid).sendMessage(message).enqueue { result ->
if (result.isSuccess) {
// Handle success
} else {
// Handle error
}
}
chatClient.channel(cid).sendMessage(message).enqueue(result -> {
if (result.isSuccess()) {
// Handle success
} else {
// Handle error
}
});
chatClient.channel(cid)
returns a ChannelClient
that uses ChatClient
under the hood and simplifies performing actions in particular channel.
The approach mentioned above can be applied to all API-call-related ChatDomain
methods.
Observing the State
We’ve renamed the objects used to obtain state:
QueryChannelsController
was replaced byQueryChannelsState
.ChannelController
was replaced byChannelState
.ThreadController
was replaced byThreadState
.- Global state available through
ChatDomain
can be now obtained fromGlobalState
.
You can access the objects mentioned above like this:
// Returns QueryChannelsState object based on filter and sort used to query channels
val queryChannelsState = chatClient.state.queryChannels(filter = filter, sort = sort)
// Returns ChannelState object for a given channel
val channelState = chatClient.state.channel(channelType = "messaging", channelId = "sampleId")
// Returns ThreadState object for a thread associated with a given parentMessageId
val threadState = chatClient.state.thread(messageId = "parentMessageId")
// Gives you access to GlobalState object
val globalState = chatClient.globalState
// Returns QueryChannelsState object based on filter and sort used to query channels
QueryChannelsState queryChannelsState = ChatClientExtensions.getState(chatClient).queryChannels(filter, sort);
// Returns ChannelState object for a given channel
ChannelState channelState = ChatClientExtensions.getState(chatClient).channel("messaging", "sampleId");
// Returns ThreadState object for a thread associated with a given parentMessageId
ThreadState threadState = ChatClientExtensions.getState(chatClient).thread("parentMessageId");
// Gives you access to GlobalState object
GlobalState globalState = ChatClientExtensions.getGlobalState(chatClient);
Methods mentioned above return a state object associated with the API call but don’t perform the API call itself.
Make sure to request the data using ChatClient
object.
The snippet below shows you how to request channels and observe the state:
// 1. Get the first 30 channels to which thierry belongs
val filter = Filters.`in`("members", "thierry")
val sort = QuerySortByField.descByName<Channel>("lastUpdated")
val request = QueryChannelsRequest(
filter = filter,
querySort = sort,
limit = 30,
offset = 0,
messageLimit = 1,
memberLimit = 30,
)
chatClient.queryChannels(request).enqueue { result ->
if (result.isSuccess) {
// Request successful. Data will be propagated to the state object
} else {
// Handle error
}
}
// 2. Get the state object associated with the above API call
val queryChannelsState = chatClient.state.queryChannels(filter = filter, sort = sort)
// 1. Get the first 30 channels to which thierry belongs
FilterObject filter = Filters.in("members", "thierry");
QuerySorter<Channel> sort = QuerySortByField.descByName("lastUpdated");
int limit = 30;
int offset = 0;
int messageLimit = 1;
int memberLimit = 30;
QueryChannelsRequest request = new QueryChannelsRequest(filter, offset, limit, sort, messageLimit, memberLimit);
chatClient.queryChannels(request).enqueue(result -> {
if (result.isSuccess()) {
// Request successful. Data will be propagated to the state object
} else {
// Handle error
}
});
// 2. Get the state object associated with the above API call
QueryChannelsState queryChannelsState = ChatClientExtensions.getState(chatClient).queryChannels(filter, sort);
In addition to that, the offline library provides a bunch of extension methods that can be used to obtain the state after performing a particular API call:
// Old approach - returns ChannelController object and performs watchChannel request
ChatDomain.instance().watchChannel(cid = "messaging:sampleId", messageLimit = 30).enqueue { result ->
if (result.isSuccess) {
val channelController = result.data()
} else {
// Handle error
}
}
// New approach - returns the StateFlow<ChannelState?> object and performs watchChannel request
val channelState: StateFlow<ChannelState?> = chatClient.watchChannelAsState(
cid = "messaging:sampleId",
messageLimit = 30,
coroutineScope = scope,
)
// Old approach - returns ChannelController object and performs watchChannel request
ChatDomain.instance().watchChannel("messaging:sampleId", 30).enqueue(result -> {
if (result.isSuccess()) {
// Handle success
} else {
// Handle error
}
});
// New approach - returns the LiveData<ChannelState> object and performs watchChannel request
StateFlow<ChannelState> channelState = ChatClientExtensions.watchChannelAsState(chatClient, "messaging:sampleId", 30);
LiveData<ChannelState> channelStateLiveData = FlowExtensions.asLiveData(channelState);
You can read more in the Offline Support documentation.
Other changes
You might notice that some of the method names have changed. For example, editMessage
was replaced with updateMessage
:
// Old approach
val messageToUpdate = Message(text = "Updated text")
ChatDomain.instance().editMessage(messageToUpdate).enqueue { result ->
if (result.isSuccess) {
// Handle success
} else {
// Handle error
}
}
// New approach
val messageToUpdate = Message(text = "Updated text")
chatClient.updateMessage(messageToUpdate).enqueue { result ->
if (result.isSuccess) {
// Handle success
} else {
// Handle error
}
}
// Old approach of update message
Message messageToUpdate = new Message();
messageToUpdate.setText("Updated text");
ChatDomain.instance().editMessage(messageToUpdate).enqueue { result ->
if (result.isSuccess) {
// Handle success
} else {
// Handle error
}
}
// New approach of updating message
Message messageToUpdate = new Message();
messageToUpdate.setText("Updated text");
chatClient.updateMessage(messageToUpdate).enqueue(result -> {
if (result.isSuccess()) {
// Handle success
} else {
// Handle error
}
});
and leaveChannel
was replaced by removeMembers
:
// Old approach
ChatDomain.instance().leaveChannel(cid).enqueue { result ->
if (result.isSuccess) {
// Handle success
} else {
// Handle error
}
}
// New approach
chatClient.getCurrentUser()?.let { currentUser ->
chatClient.channel(cid).removeMembers(listOf(currentUser.id)).enqueue { result ->
if (result.isSuccess) {
// Handle success
} else {
// Handle error
}
}
}
// Old approach of leaving channel
ChatDomain.instance().leaveChannel(cid).enqueue(result -> {
if (result.isSuccess()) {
// Handle success
} else {
// Handle error
}
});
}
String cid = "cid";
// New approach of leaving channel
User currentUser = chatClient.getCurrentUser();
if (currentUser != null) {
chatClient.channel(cid).removeMembers(Collections.singletonList(currentUser.getId()), null).enqueue(result -> {
if (result.isSuccess()) {
// Handle success
} else {
// Handle error
}
});
}
The same applies to the method’s parameters as some of them were changed. Here you can find the list of available methods.
Last, but not least, we’ve reorganized the offline library package structure to better match the current implementation.
This shouldn’t affect you much because most of the classes are brand new, but you can spot that some of the remaining public classes were moved.
For example, io.getstream.chat.android.offline.querychannels.ChatEventHandler
was moved to io.getstream.chat.android.offline.event.handler.chat.ChatEventHandler
.
We strongly recommend to remove imports that cannot be resolved and reimport such classes again.