This blog post illustrates how to offer offline support in CometChat. It explains how to integrate the offline capabilities into your application with the help of the powerful CometChat SDK and the ObjectBox database.
This post is a continuation of our previous blogs on CometChat, where we explained how to implement group chat and role-based access, and how to implement one-on-one chat in Flutter using the CometChat SDK. You can also check them out.
A smooth chat experience is effectively facilitated by offline support. At times, users might not be fully satisfied with the chat experience; especially if an unreliable or poor network connection causes any inconvenience to them during a chat. Hence, at this point, offline support becomes crucial to overcome this challenge. You should include it in your chat-based app so that users can stay connected and engaged even without a consistent internet connection.
Before outlining the integration of offline features into your app, let’s understand the advantages of extending offline support in CometChat.
The major benefits of including offline support in your chat app are as stated below:
Better user experience: It diminishes and eradicates disruptions caused by a poor or unreliable internet connection. This makes the app come in handy and usable even in remote areas.
Uninterrupted Communication: It establishes steady communication by ensuring the definitive delivery of messages even when the recipient is offline.
Increased Accessibility: The integration of offline features immediately transforms your chat application into a better and more accessible version. Users are able to engage with their friends, family, or acquaintances living in far-flung or remote locations that face connectivity issues.
Time-Shifted Conversations: Time constraints in messaging are completely removed with offline support as the users can share messages at any point of time. It removes the dependency on internet availability. The messages are locally stored and sent when a stable connection is restored.
Battery Efficiency: Since offline support decreases the dependency on having an internet connection, the app becomes highly energy efficient. The battery life increases due to the preservation.
Optimized Data Usage: Offline support also enhances data optimization, making the app even more effective and attractive for users. It becomes especially reliable for users who are looking to reduce their mobile phone expenses.
Now, let’s detail the integration process of offline support with the help of ObjectBox, which is a high-quality NoSQL database. However, before delving into the process flow, let us first understand what it is.
ObjectBox is an efficient NoSQL database customized to perfection for mobile and edge devices. It helps with localizing data storage, thereby making it the most appropriate match for the CometChat app.
Being a NoSQL database, it offers an extremely flexible schema-less data model, which also helps facilitate integration with different data structures.
To simplify, we can say that ObjectBox is the local storage for your CometChat app, which makes the process of saving and fetching data effortless. With this, the users can communicate with each other even in the absence of an internet connection. You will also understand the importance of ObjectBox in greater detail as we move deeper into the setup process, especially for furnishing offline support. Let’s continue.
ObjectBox is the perfect choice to store data locally in your CometChat application. The reasons are:
Exceptional performance: ObjectBox is super-efficient. It delivers outstanding performance with the least strain on CPU, memory, and battery resources. This ensures your app remains effective and sustainable.
Efficient Query Methods: ObjectBox simplifies the querying process with its built-in methods. It removes the hassle of inputting manual queries.
Scalability: ObjectBox maintains its exceptional performance and keeps your app responsive and stable even if it gains popularity and attracts a massive user base.
Data Consistency: ObjectBox prioritizes the integrity of stored data. It helps users receive accurate, consistent, and latest information all the time, even in offline scenarios.
To initialize ObjectBox, create a file called ‘object_box.dart’ and call the initialize() method from ‘main.dart’.
class ObjectBox {
ObjectBox._();
static ObjectBox get instance => _instance;
static final ObjectBox _instance = ObjectBox._();
/// The Store of this app.
late final Store store;
/// Initialize the [Store] in the secured application document directory.
Future<void> initialize() async {
final dir = await getApplicationDocumentsDirectory();
if (Store.isOpen(dir.path)) {
store = Store.attach(getObjectBoxModel(), dir.path);
return;
}
store = await openStore(directory: dir.path);
}
}
To store data in ObjectBox, you need to define an entity for each object you want to store using the @Entity tag. This will create a table for each entity class in the database. ObjectBox requires the id parameter as a primary key for each entity, which allows fast lookups and joins on the entities.
You can also use the @Index tag to improve the query performance on specific object properties by enabling data indexing on them. During code generation, ObjectBox generates ‘objectbox.g.dart’ and ‘objectbox-model.json’ configuration files containing entity and property information. To use ObjectBox in your app, you need to initialize the global store in your code, which will open the database and facilitate CRUD operations.
To store conversation data, create a ChatConversation object resembling CometChat’s Conversation object. Assign the @Index tag to ConversationId, as it will be frequently used for filtering and searching records in the local database. Since custom Dart objects cannot be stored directly in ObjectBox, converting them to strings using json.encode and then decoding them back to custom objects using json.decode is recommended.
@Entity()
@JsonSerializable(explicitToJson: true)
class ChatConversation {
int id;
@Index()
String? conversationId;
String? conversationType;
@Transient()
@JsonKey(fromJson: _userFromJson, toJson: _userToJson)
ChatUser? user;
@Transient()
@JsonKey(fromJson: _groupFromJson, toJson: _groupToJson)
ChatGroup? group;
@Transient()
Message? lastMessage;
@Property(type: PropertyType.date)
DateTime? updatedAt;
int? unreadMessageCount;
List<String>? tags;
ChatConversation({
this.id = 0,
this.conversationId,
this.conversationType,
this.user,
this.group,
this.lastMessage,
this.updatedAt,
this.unreadMessageCount,
this.tags,
});
CometChat provides a list of conversations through the ConversationsRequestBuilder().build().fetchNext() method. In the onSuccess method of the conversation request, you can store the conversations in ObjectBox using the putMany() method.
final conversationBox = ObjectBox.instance.store.box<ChatConversation>();
conversationBox.putMany(
conversations.map(ChatConversation.fromCometConversation).toList(),
);
To fetch records from the local database, you can use the initState() method of the conversations page and add the retrieved data to the state’s variable. The order() method can be used to order records based on the updated time in descending order.
void initState() {
final conversations = conversationBox
.query()
.order(Conversation_.updatedAt, flags: Order.descending)
.build()
.find();
}
To delete a conversation from the server using CometChat.deleteConversation(), you need to remove the corresponding conversation from the local database as well.
CometChat.deleteConversation(receiverUid, receiverType,
onSuccess: (message) {
final conversation = conversationBox
.query(ChatConversation_.conversationId.equals(conversationId))
.build().findUnique();
if (conversation != null) {
conversationBox.remove(conversation.id);
}
onError: (error) {},
);
To store message data, create a Message object resembling CometChat’s BaseMessage object. Similar to conversations, assign the @Index tag to ConversationId for efficient filtering and searching.
@Entity()
@JsonSerializable(explicitToJson: true)
class Message {
@Id(assignable: true)
int id;
String name;
String text;
bool isEdited;
bool isDeleted;
String senderId;
bool isForwarded;
@Transient()
MessageType type;
String? imageUrl;
@Property(type: PropertyType.date)
DateTime? sentAt;
@Property(type: PropertyType.date)
DateTime? readAt;
@Property(type: PropertyType.date)
DateTime? editedAt;
String? receiverId;
@Property(type: PropertyType.date)
DateTime? deliveredAt;
@Index()
String? conversationId;
@Transient()
Message? parentMessage;
@Transient()
List<Reaction>? reactions;
@Transient()
MessageAction? messageAction;
@Transient()
MessageAttachment? attachment;
@Transient()
DeliveryStatus deliveryStatus;
@Transient()
Map<String, dynamic>? baseMessageJson;
Message({
required this.id,
required this.name,
required this.text,
required this.senderId,
this.deliveryStatus = DeliveryStatus.none,
this.messageAction = MessageAction.info,
this.type = MessageType.text,
this.isForwarded = false,
this.isDeleted = false,
this.isEdited = false,
this.baseMessageJson,
this.conversationId,
this.parentMessage,
this.deliveredAt,
this.attachment,
this.receiverId,
this.reactions,
this.editedAt,
this.imageUrl,
this.readAt,
this.sentAt,
});
You can get a list of messages in CometChat using the `MessagesRequestBuilder().build().fetchPrevious()` or `MessagesRequestBuilder().build().fetchNext()` methods. You must specify the `uid` or `guid` in the `MessagesRequestBuilder()` to get messages for one-to-one or group conversations. The `fetchPrevious()` method fetches records before the given messageId, while the `fetchNext()` method fetches records after the given messageId.
You can save these messages in ObjectBox using the `putMany()` method. It ensures proper casting. The `onSuccess` method of the message request produces a list of base messages that need to be cast to the desired format.
final messageBox = ObjectBox.instance.store.box<Message>();
messageBox.putMany(
baseMessages.map(Message.fromCometChatBaseMessage).toList(),
);
You can use the initState() method on the message page to get records from the local database and add them to the state variable. Use the query() method with a filter based on the conversationId for efficient retrieval.
void initState() {
final messages = messageBox
.query(Message_.conversationId.equals(conversationId))
.build()
.find();
}
To remove a single message, use the remove() method with a query based on the messageId.
messageBox.query(Message_.id.equals(messageId)).build().remove();
To remove all messages in a conversation, use the remove() method with a query based on the conversationId.
messageBox
.query(Message_.conversationId.equals(conversationId))
.build()
.remove();
You must update the related message object and conversation object in the local database whenever a message is edited or deleted. You should handle it with the appropriate callback method.
messageBox.put(message);
conversationBox.put(conversation);
With this, you are now proficient with the necessary skills to integrate offline support in CometChat with Flutter and ObjectBox. This will transform your app to be highly efficient and attractive to users who don’t have 24/7 internet access. Integration of CometChat SDK and ObjectBox facilitates a smooth chat experience for all users.
If you are looking to upgrade the performance of your Flutter chat app with offline features and offer a smooth chat experience, then Aubergine Solutions can help you end-to-end. Our expertise in tailoring our Flutter app development services to your specific requirements is one of the key highlights we are known for. Connect with us today to integrate offline features into your app with the help of CometChat and ObjectBox.
If you enjoyed this blog post, you might also like these other blogs: