This is documentation for Stream Chat Flutter SDK v9, which is no longer actively maintained. For up-to-date documentation, see the latest version (v10).

Offline Support

Introduction

Most messaging apps need to work regardless of whether the app is currently connected to the internet. Local data persistence stores the fetched data from the backend on a local SQLite database using the drift package in Flutter. All packages in the SDK can use local data persistence to store messages across multiple platforms.

Implementation

To add data persistence you can extend the class ChatPersistenceClient and pass an instance to the StreamChatClient.

class CustomChatPersistentClient extends ChatPersistenceClient {
...
}

final client = StreamChatClient(
  apiKey ?? kDefaultStreamApiKey,
  logLevel: Level.INFO,
)..chatPersistenceClient = CustomChatPersistentClient();

We provide an official persistent client in the stream_chat_persistence package that works using the library drift, an SQLite ORM.

Add this to your package's pubspec.yaml file, using the latest version.

dependencies:
  stream_chat_persistence: ^latest_version

You should then run flutter pub get

The usage is straightforward.

  1. Create a new instance of StreamChatPersistenceClient providing logLevel and connectionMode
final chatPersistentClient = StreamChatPersistenceClient(
  logLevel: Level.INFO,
  connectionMode: ConnectionMode.background,
);
  1. Pass the instance to the official StreamChatClient
final client = StreamChatClient(
  apiKey ?? kDefaultStreamApiKey,
  logLevel: Level.INFO,
)..chatPersistenceClient = chatPersistentClient;

The StreamChatClient uses the chatPersistentClient to synchronize the database with the newest information every time it receives new data about channels, messages, and users.

Connection mode

The connectionMode parameter controls which isolate the persistence client uses to run its SQLite operations. It accepts a ConnectionMode value:

  • ConnectionMode.regular (default) — The database runs on the main isolate, the same isolate that renders your UI.
  • ConnectionMode.background — The database runs on a dedicated background isolate, so database work does not block the main thread.

If you do not provide a connectionMode, the client defaults to ConnectionMode.regular.

When and why to change it

With ConnectionMode.regular, all SQLite reads and writes run on the main isolate. Because Flutter also renders your UI on that isolate, large or frequent database operations can compete with frame rendering. For most apps — those with a moderate number of channels and messages — this is unnoticeable, which is why it is the default.

Switch to ConnectionMode.background if your app loads a large number of channels or messages, or if you notice UI jank during the initial sync or when reconnecting after being offline (moments when the SDK writes a large batch of data at once). Running the database on a background isolate keeps these heavy operations off the UI thread.

The trade-off is a small amount of overhead. Setting up the connection is slower than with ConnectionMode.regular, because a new background isolate has to be spawned when the client connects (the main isolate has no such startup cost). In addition, each query is passed between isolates. For typical workloads this overhead is negligible.

Note that connectionMode only takes effect on native platforms (iOS, Android, macOS, Windows, and Linux). On web it is ignored, since the web implementation does not use isolates.

Multi-user

The database file is named after the userId, so if you instantiate a client using a different userId you will use a different database. Calling client.disconnectUser(flushChatPersistence: true) flushes all current database data.

Updating/deleting/sending a message while offline

The information about the action is saved in offline storage. When the client returns online, all pending actions are retried.