versioning docs

This commit is contained in:
Salvatore Giordano
2022-04-28 11:15:38 +02:00
parent 0d770614cd
commit a3a23feb3e
86 changed files with 4503 additions and 0 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 876 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 128 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 380 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 459 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 242 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 564 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 259 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

@@ -0,0 +1,4 @@
{
"label": "Introduction",
"position": 1
}
@@ -0,0 +1,55 @@
---
id: choose_package
sidebar_position: 2
title: Choosing The Right Flutter Package
---
### Why the SDK is split into different packages
Different applications need different levels of customization and integration with the Stream Chat SDK.
To do this, the Flutter SDK is split into three different packages which build upon the last and give
varying levels of control to the developer. The higher level packages offer better compatibility out of the
box while the lower level SDKs offer fine grained control. There is also a separate package for persistence
which allows you persist data locally which works with all packages.
### How do I choose?
#### The case for stream_chat_flutter
For the quickest way to integrate Stream Chat with your app, the UI SDK (`stream_chat_flutter`) is the
way to go. `stream_chat_flutter` contains prebuilt components that manage most operations like data
fetching, pagination, sending a message, and more. This ensures you have a nearly out-of-the-box
experience adding chat to your applications. It is also possible to use this in conjunction with
lower level operations of the SDK to get the best of both worlds.
:::note
The package allows customization of components to a large extent making it easy to tweak the theme
to match your app colors and such. If you require any additional feature or customization, feel free
to request this through our support channels.
:::
<b>Summary:</b>
For the quickest and easiest way to add Chat to your app with prebuilt UI components, use stream_chat_flutter
#### The case for stream_chat_flutter_core
If your application involves UI that does not fit in with the stream_chat_flutter components, stream_chat_flutter_core
strips away the UI associated with the components and provides the data fetching and manipulation
capabilities while supplying builders for UI. This allows you to implement your own UI and themes
completely independently while not worrying about writing functions for data and pagination.
<b>Summary:</b>
For implementing your own custom UI while not having to worry about lower level API calls, use stream_chat_flutter_core.
#### The case for stream_chat
The stream_chat package is the Low-level Client (LLC) of Stream Chat in Flutter. This package wraps
the underlying functionality of Stream Chat and allows the most customization in terms of UI, data,
and architecture.
<b>Summary:</b>
For the most control over the SDK and dealing with low level calls to the API, use stream_chat.
@@ -0,0 +1,77 @@
---
slug: /
id: introduction
sidebar_position: 1
title: About The Flutter SDK
---
Exploring The Basics Of Stream Chat
![](../assets/sdk_title.png)
Stream Chat is a service that helps you easily build a full chat experience in your Flutter apps.
We also support a variety of other SDKs.
This section of the documentation focuses on our Flutter SDK which helps you easily
ship high quality messaging experiences in apps and programs built with the [Flutter toolkit
made by Google](https://flutter.dev).
The Stream Chat Flutter SDK comprises five different packages to choose from, ranging from ones
giving you complete control to ones that give you a rich out-of-the-box chat experience.
The packages that make up the Stream Chat SDK are:
1. <b>Low Level Client (stream_chat)</b>: a pure Dart package that can be used on any Dart project.
It provides a low-level client to access the Stream Chat service.
2. <b>Core (stream_chat_flutter_core)</b>: provides business logic to fetch common things required
for integrating Stream Chat into your application.
The core package allows more customisation and hence provides business logic but no UI components.
3. <b>UI (stream_chat_flutter)</b>: this library includes both a low-level chat SDK and a set of
reusable and customisable UI components.
4. <b>Persistence (stream_chat_persistence)</b>: provides a persistence client for fetching and
saving chat data locally.
5. <b>Localizations (stream_chat_localizations)</b>: provides a set of localizations for the SDK.
We recommend building prototypes using the full UI package, [stream_chat_flutter](https://pub.dev/packages/stream_chat_flutter),
since it contains UI widgets already integrated with Stream's API. It is the fastest way to get up
and running using Stream chat in your app.
The Flutter SDK enables you to build any type of chat or messaging experience for Android, iOS, Web
and Desktop.
If you're building a very custom UI and would prefer a more lean package,
[stream_chat_flutter_core](https://pub.dev/packages/stream_chat_flutter_core) will be suited to this
use case. Core allows you to build custom, expressive UIs while retaining the benefits of our full
Flutter SDK. APIs for accessing and controlling users, sending messages, and so forth are seamlessly integrated
into this package and accessible via providers and builders.
Before going into the docs, let's take a small detour to look at how the elements of Stream Chat are structured.
### Basic Structure
There are two core elements in chat, <b>Users</b> and <b>Channels</b>.
Channels are groups of one or more users that can message each other.
In an app, you need to have a user connected to query channels.
There is no specific distinction between a chat with only two people and a group chat,
but there is a way to create a unique chat between a certain number of people by creating a <b>distinct</b> channel.
![](../assets/chat_basics.png)
In essence, a normal two-person chat would be a <b>distinct channel</b> created with two members (you cannot add or delete members in this channel), whereas a group created with two people would simply be a <b>non distinct channel</b> (possible to add or remove members).
:::note
It is also possible to add more than two people in a distinct channel which retains the same add/removal properties and resembles the Slack DMs where you can DM one or more people as well.
:::
In summary, if you were creating a Whatsapp-like app, the first screen would be a <b>list of channels</b> - which on opening would show a <b>list of messages</b> that were sent by the users in the Channel.
While this is a simplistic overview of the service, the Flutter SDK handles the UI and more time consuming things (media upload, offline storage, theming, etc.) for you.
Before reading the docs, consider trying our [online API tour](https://getstream.io/chat/get_started/),
it is a nice way to learn how the API works.
It's in-browser so you'll need to use Javascript but the core conceps are pretty much the same as Dart.
You may also like to look at the [Flutter tutorial](https://getstream.io/chat/flutter/tutorial/)
which focuses on using the UI package to get Stream Chat integrated into a Flutter app.
Further sections break down each individual packages and explain several common operations.
@@ -0,0 +1,20 @@
---
id: versioning_policy
sidebar_position: 3
title: Versioning Policy
---
All of the Stream Chat packages follow [semantic versioning (semver)](https://semver.org/).
That means that with a version number x.y.z (major.minor.patch):
- When releasing bug fixes (backwards compatible), we make a patch release by changing the z number (ex: 3.6.2 to 3.6.3). A bug fix is defined as an internal change that fixes incorrect behavior.
- When releasing new features or non-critical fixes, we make a minor release by changing the y number (ex: 3.6.2 to 3.7.0).
- When releasing breaking changes (backward incompatible), we make a major release by changing the x number (ex: 3.6.2 to 4.0.0).
See the [semantic versioning](https://dart.dev/tools/pub/versioning#semantic-versions) section from the Dart docs for more information.
This versioning policy does not apply to prerelease packages (below major version of 1). See this
[StackOverflow thread](https://stackoverflow.com/questions/66201337/how-do-dart-package-versions-work-how-should-i-version-my-flutter-plugins)
for more information on Dart package versioning.
Whenever possible, we will add deprecation warnings in preparation for future breaking changes.
@@ -0,0 +1,4 @@
{
"label": "Guides",
"position": 2
}
@@ -0,0 +1,93 @@
---
id: adding_chat_to_video_livestreams
sidebar_position: 7
title: Adding Chat To Video Livestreams
---
Adding Chat To Video Livestreams
### Introduction
Video livestreams are usually complemented with a chat section to make the livestream more interactive
and encourage retention. There are several ways to show the chat interface on the screen and requires
some design choices.
This guide details multiple ways of adding chat functionality to your video livestream.
### Implementing Chat
There are two common scenarios in live-streaming applications depending how well integrated the two
components (video + chat) are allowed to be on the screen. Two common types are split-screen and a
chat overlay that fades in.
Let's explore creating both types:
### Split-screen
In the split-screen implementation, we have a visual split between the video and the message list.
This allows the content to be unobstructed by chat and have a clear separation of boundaries.
![](../assets/live_stream_1.jpg)
```dart
Scaffold(
body: Column(
children: <Widget>[
Expanded(
child: // Your video implementation here,
),
Expanded(
child: Column(
children: [
Expanded(
child: MessageListView(),
),
MessageInput(),
],
),
),
],
),
)
```
### Overlapping chat with a transparency gradient
Another way to add chat is to overlay the video content with messages which progressively fade out
as we go to the top of the screen. This gives the content a more rich feel as it takes the whole
screen and allows the chat to be more homogeneously integrated with the content.
The second type looks like this:
![](../assets/live_stream_2.jpg)
We can use a `Stack` for achieving this:
```dart
Scaffold(
body: Stack(
children: <Widget>[
// Add your video implementation here
ShaderMask(
shaderCallback: (rect) {
return LinearGradient(
begin: Alignment.bottomCenter,
end: Alignment.topCenter,
colors: [Colors.black, Colors.transparent],
stops: [0.4, 0.65]
).createShader(Rect.fromLTRB(0, 0, rect.width, rect.height));
},
blendMode: BlendMode.dstIn,
child: Column(
children: [
Expanded(
child: MessageListView(),
),
MessageInput(),
],
),
),
],
),
)
```
@@ -0,0 +1,264 @@
---
id: adding_custom_attachments
sidebar_position: 3
title: Adding Custom Attachments
---
Adding Your Own Types Of Attachments To A Message
### Introduction
Stream Chat supports attachment types like images, video and files by default. You can also add your
own types of attachments through the SDK such as location, audio, etc.
This involves doing three things:
1) Rendering the attachment thumbnail in the `MessageInput`
2) Sending a message with the custom attachment
3) Rendering the custom message attachment
To do this, let's check out an example to add location sharing to Stream Chat.
### Location Sharing
Let's build an example of location sharing option in the app:
![](../assets/location_sharing_example.jpg)
* Show a "Share Location" button next to MessageInput Textfield.
* When the user presses this button, it should fetch the current location coordinates of the user, and send a message on the channel as follows:
```dart
Message(
text: 'This is my location',
attachments: [
Attachment(
uploadState: UploadState.success(),
type: 'location',
extraData: {
'latitude': 'fetched_latitude',
'longitude': 'fetched_longitude',
},
),
],
)
```
For our example, we are going to use [geolocator](https://pub.dev/packages/geolocator) library.
Please check their [setup instructions](https://pub.dev/packages/geolocator) on their docs.
NOTE: If you are testing on iOS simulator, you will need to set some dummy coordinates, as mentioned [here](https://stackoverflow.com/a/31238119/7489541).
Also don't forget to enable "location update" capability in background mode, from XCode.
On the receiver end, `location` type attachment should be rendered in map view, in the `MessageListView`.
We are going to use [Google Static Maps API](https://developers.google.com/maps/documentation/maps-static/overview) to render the map in the message.
You can use other libraries as well such as [google_maps_flutter](https://pub.dev/packages/google_maps_flutter).
First, we add a button which when clicked fetches and shares location into the `MessageInput`:
```dart
MessageInput(
actions: [
InkWell(
child: Icon(
Icons.location_on,
size: 20.0,
color: StreamChatTheme.of(context).colorTheme.grey,
),
onTap: () {
var channel = StreamChannel.of(context).channel;
var user = StreamChat.of(context).user;
_determinePosition().then((value) {
channel.sendMessage(
Message(
text: 'This is my location',
attachments: [
Attachment(
uploadState: UploadState.success(),
type: 'location',
extraData: {
'latitude': value.latitude.toString(),
'longitude': value.longitude.toString(),
},
),
],
),
);
}).catchError((err) {
print('Error getting location!');
});
},
),
],
),
Future<Position> _determinePosition() async {
bool serviceEnabled;
LocationPermission permission;
serviceEnabled = await Geolocator.isLocationServiceEnabled();
if (!serviceEnabled) {
return Future.error('Location services are disabled.');
}
permission = await Geolocator.checkPermission();
if (permission == LocationPermission.denied) {
permission = await Geolocator.requestPermission();
if (permission == LocationPermission.deniedForever) {
return Future.error(
'Location permissions are permanently denied, we cannot request permissions.');
}
if (permission == LocationPermission.denied) {
return Future.error(
'Location permissions are denied');
}
}
return await Geolocator.getCurrentPosition();
}
```
Next, we build the Static Maps URL (Add your API key before using the code snippet):
```dart
String _buildMapAttachment(String lat, String long) {
var baseURL = 'https://maps.googleapis.com/maps/api/staticmap?';
var url = Uri(
scheme: 'https',
host: 'maps.googleapis.com',
port: 443,
path: '/maps/api/staticmap',
queryParameters: {
'center': '${lat},${long}',
'zoom': '15',
'size': '600x300',
'maptype': 'roadmap',
'key': 'YOUR_API_KEY',
'markers': 'color:red|${lat},${long}'
});
return url.toString();
}
```
And then modify the `MessageListView` and tell it how to build a location attachment, using the `messageBuilder` property and copying the default message implementation overriding the `customAttachmentBuilders` property:
```dart
MessageListView(
messageBuilder: (context, details, messages, defaultMessage) {
return defaultMessage.copyWith(
customAttachmentBuilders: {
'location': (context, message, attachments) {
final attachmentWidget = Image.network(
_buildMapAttachment(
attachments[0].extraData['latitude'],
attachments[0].extraData['longitude'],
),
);
return wrapAttachmentWidget(context, attachmentWidget, null, true, BorderRadius.circular(8.0));
}
},
);
},
),
```
This gives us the final location attachment:
![](../assets/location_sharing_example_message.jpg)
Additionally, you can also add a thumbnail if a message has a location attachment (unlike in this case, where we sent the message directly).
To do this, we will:
1) Add an attachment instead of sending a message
2) Customize the `MessageInput`
First, we add the attachment when the location button is clicked:
```dart
GlobalKey<MessageInputState> _messageInputKey = GlobalKey();
MessageInput(
key: _messageInputKey,
actions: [
InkWell(
child: Icon(
Icons.location_on,
size: 20.0,
color: StreamChatTheme.of(context).colorTheme.grey,
),
onTap: () {
_determinePosition().then((value) {
_messageInputKey.currentState.addAttachment(
Attachment(
uploadState: UploadState.success(),
type: 'location',
extraData: {
'latitude': value.latitude.toString(),
'longitude': value.longitude.toString(),
},
),
);
}).catchError((err) {
print('Error getting location!');
});
},
),
],
),
```
After this, we can build the thumbnail:
```dart
MessageInput(
key: _messageInputKey,
actions: [
InkWell(
child: Icon(
Icons.location_on,
size: 20.0,
color: StreamChatTheme.of(context).colorTheme.grey,
),
onTap: () {
_determinePosition().then((value) {
_messageInputKey.currentState.addAttachment(
Attachment(
uploadState: UploadState.success(),
type: 'location',
extraData: {
'latitude': value.latitude.toString(),
'longitude': value.longitude.toString(),
},
),
);
}).catchError((err) {
print('Error getting location!');
});
},
),
],
attachmentThumbnailBuilders: {
'location': (context, attachment) {
return Image.network(
_buildMapAttachment(
attachment.extraData['latitude'],
attachment.extraData['longitude'],
),
);
},
},
),
```
And we can see the thumbnails in the MessageInput:
![](../assets/location_sharing_example_message_thumbnail.jpg)
@@ -0,0 +1,76 @@
---
id: adding_local_data_persistence
sidebar_position: 9
title: Adding Local Data Persistence
---
Adding Local Data Persistence
### 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
moor 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.
```dart
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](https://pub.dev/packages/stream_chat_persistence)
package that works using the library [moor](https://moor.simonbinder.eu), an SQLite ORM.
Add this to your package's `pubspec.yaml` file, using the latest version.
```yaml
dependencies:
stream_chat_persistence: ^latest_version
```
You should then run `flutter packages get`
The usage is pretty simple.
1. Create a new instance of `StreamChatPersistenceClient` providing `logLevel` and `connectionMode`
```dart
final chatPersistentClient = StreamChatPersistenceClient(
logLevel: Level.INFO,
connectionMode: ConnectionMode.background,
);
```
2. Pass the instance to the official `StreamChatClient`
```dart
final client = StreamChatClient(
apiKey ?? kDefaultStreamApiKey,
logLevel: Level.INFO,
)..chatPersistenceClient = chatPersistentClient;
```
And you are ready to go...
Note that passing `ConnectionMode.background` the database uses a background isolate to unblock the main thread.
The `StreamChatClient` uses the `chatPersistentClient` to synchronize the database with the newest
information every time it receives new data about channels/messages/users.
### Multi-user
The DB 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, everything is retried.
@@ -0,0 +1,191 @@
---
id: adding_localization
sidebar_position: 2
title: Adding Localization (l10n) / Internationalization (i18n)
---
Adding Localization To UI Widgets
### Introduction
We have a dedicated package for adding localization to our UI widgets. It's called `stream_chat_localizations` and you can find it [here](https://pub.dev/packages/stream_chat_localizations).
![](../assets/localization_support.jpg)
## What is Localization?
If you deploy your app to users who speak another language, you'll need to internationalize (localize) it. That means you need to write the app in a way that makes it possible to localize values like text and layouts for each language or locale that the app supports. For more information, see the [Flutter documentation](https://flutter.dev/docs/development/accessibility-and-localization/internationalization).
What this package allows you to do is to provide localized strings for the Stream chat widgets. For example, depending on the application locale, the Stream Chat widgets will display the appropriate language. The locale will be set automatically, based on system preferences, or you could set it programmatically in your app. The package supports several different languages, with more to be added. The package allows you to override any supported language or add a new language that isn't supported.
:::note
If you want to translate messages, or enable automatic translation, please see the [Translation documentation](https://getstream.io/chat/docs/flutter-dart/translation/?language=dart).
:::
### Supported languages
At the moment we support the following languages:
- [English](https://github.com/GetStream/stream-chat-flutter/blob/master/packages/stream_chat_localizations/lib/src/stream_chat_localizations_en.dart)
- [Hindi](https://github.com/GetStream/stream-chat-flutter/blob/master/packages/stream_chat_localizations/lib/src/stream_chat_localizations_hi.dart)
- [Italian](https://github.com/GetStream/stream-chat-flutter/blob/master/packages/stream_chat_localizations/lib/src/stream_chat_localizations_it.dart)
- [French](https://github.com/GetStream/stream-chat-flutter/blob/master/packages/stream_chat_localizations/lib/src/stream_chat_localizations_fr.dart)
- [Spanish](https://github.com/GetStream/stream-chat-flutter/blob/master/packages/stream_chat_localizations/lib/src/stream_chat_localizations_es.dart)
- [Japanese](https://github.com/GetStream/stream-chat-flutter/blob/master/packages/stream_chat_localizations/lib/src/stream_chat_localizations_ja.dart)
- [Korean](https://github.com/GetStream/stream-chat-flutter/blob/master/packages/stream_chat_localizations/lib/src/stream_chat_localizations_ko.dart)
More languages will be added in the future. Feel free to [contribute](https://github.com/GetStream/stream-chat-flutter/blob/master/CONTRIBUTING.md) to add more languages.
### Add dependency
Add this to your package's `pubspec.yaml` file, use the latest version [![Pub](https://img.shields.io/pub/v/stream_chat_localizations.svg)](https://pub.dartlang.org/packages/stream_chat_localizations)
```yaml
dependencies:
stream_chat_localizations: ^latest_version
```
Then run `flutter packages get`
### Usage
Generally, Flutter and the Stream Chat SDK will use the system locale of the user's device, if that locale is supported (see below). If the locale is not supported we will default to `en` (however it's always possible to [customize that](#changing-the-default-language)).
Make sure to read more about localization in the [official Flutter docs](https://flutter.dev/docs/development/accessibility-and-localization/internationalization).
```dart
import 'package:flutter/material.dart';
import 'package:stream_chat_localizations/stream_chat_localizations.dart';
void main() {
WidgetsFlutterBinding.ensureInitialized();
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
// Add all the supported locales
supportedLocales: const [
Locale('en'),
Locale('hi'),
Locale('fr'),
Locale('it'),
Locale('es'),
Locale('ja'),
Locale('ko'),
],
// Add GlobalStreamChatLocalizations.delegates
localizationsDelegates: GlobalStreamChatLocalizations.delegates,
builder: (context, widget) => StreamChat(
client: client,
child: widget,
),
home: StreamChannel(
channel: channel,
child: const ChannelPage(),
),
);
}
}
```
## Setting a language
The application language can be changed through system preferences or programmatically.
### System Preferences
The application locale can be changed by changing the language for your device or emulator within the device's system preferences.
[iOS change language](https://support.apple.com/en-us/HT204031)
[Android change language](https://support.google.com/websearch/answer/3333234?co=GENIE.Platform%3DAndroid&hl=en)
Note that the language needs to be supported in your application to work.
### Programmatically
You can also set the locale programmatically in your Flutter application without changing the device's language.
```dart
return MaterialApp(
...
locale: const Locale('fr'),
...
);
```
There are many ways that this can be set for additional control. For information and examples, see this [Stack Overflow post](https://stackoverflow.com/questions/49441212/flutter-multi-lingual-application-how-to-override-the-locale).
### Adding a new language
To add a new language, create a new class extending `GlobalStreamChatLocalizations` and create a delegate for it, adding it to the `delegates` array.
Check out [this example](https://github.com/GetStream/stream-chat-flutter/blob/master/packages/stream_chat_localizations/example/lib/add_new_lang.dart) to see how to add a new language.
### Override existing languages
To override an existing language, create a new class extending that particular language class and create a delegate for it, adding it to the `delegates` array.
Check out [this example](https://github.com/GetStream/stream-chat-flutter/blob/master/packages/stream_chat_localizations/example/lib/override_lang.dart) to see how to override an existing language.
### Changing the default language
To change the default language you can use the `MaterialApp.localeListResolutionCallback` property.
Here is an example of how that would look like:
```dart
MaterialApp(
theme: ThemeData.light(),
darkTheme: ThemeData.dark(),
// Add all the supported locales
supportedLocales: const [
Locale('en'),
Locale('hi'),
Locale('fr'),
Locale('it'),
Locale('es'),
Locale('ja'),
Locale('ko'),
],
// locales are the locales of the device
// supportedLocales are the app supported locales
localeListResolutionCallback: (locales, supportedLocales) {
// We map the supported locales to language codes
// note that this is completely optional and this logic can be changed as you like
final supportedLanguageCodes =
supportedLocales.map((e) => e.languageCode);
if (locales != null) {
// we iterate over the locales and find the first one that is supported
for (final locale in locales) {
if (supportedLanguageCodes.contains(locale.languageCode)) {
return locale;
}
}
}
// if we didn't find a supported language, we return the Italian language
return const Locale('it');
},
// Add GlobalStreamChatLocalizations.delegates
localizationsDelegates: GlobalStreamChatLocalizations.delegates,
...
```
In this case, we're using Italian as the default language.
### ⚠️ Note on **iOS**
For translation to work on **iOS** you need to add supported locales to
`ios/Runner/Info.plist` as described [here](https://flutter.dev/docs/development/accessibility-and-localization/internationalization#specifying-supportedlocales).
Example:
```xml
<key>CFBundleLocalizations</key>
<array>
<string>en</string>
<string>nb</string>
<string>fr</string>
<string>it</string>
<string>es</string>
<string>ja</string>
<string>ko</string>
</array>
```
@@ -0,0 +1,263 @@
---
id: adding_push_notifications
sidebar_position: 1
title: Adding Push Notifications (V1 legacy)
---
Adding Push Notifications To Your Application
:::note
Version 1 (legacy) of push notifications won't be removed immediately but there won't be any new features. That's why new applications are highly recommended to use version 2 from the beginning to leverage upcoming new features.
:::
### Introduction
Push notifications are a core part of the experience for a messaging app. Users often need to be notified
of new messages and old notifications sometimes need to be updated silently as well.
This guide details how to add push notifications to your app.
Make sure to check [this section](https://getstream.io/chat/docs/flutter-dart/push_introduction/?language=dart) of the docs to read about the push delivery logic.
### Setup FCM
To integrate push notifications in your Flutter app you need to use the package [firebase_messaging](https://pub.dev/packages/firebase_messaging).
Follow the [Firebase documentation](https://firebase.flutter.dev/docs/messaging/overview/) to know how to set up the plugin for both Android and iOS.
Once that's done FCM should be able to send push notifications to your devices.
### Integration with Stream
#### Step 1
From the [Firebase Console](https://console.firebase.google.com/), select the project your app belongs to.
#### Step 2
Click on the gear icon next to `Project Overview` and navigate to **Project settings**
![](../assets/firebase_project_settings.jpeg)
#### Step 3
Navigate to the `Cloud Messaging` tab
#### Step 4
Under `Project Credentials`, locate the `Server key` and copy it
![](../assets/server_key.png)
#### Step 5
Upload the `Server Key` in your chat dashboard
![](../assets/dashboard_firebase_enable.jpeg)
![](../assets/dashboard_firebase_key.jpeg)
:::note
We are setting up the Android section, but this will work for both Android and iOS if you're using Firebase for both of them!
:::
#### Step 6
Save your push notification settings changes
![](../assets/dashboard_save_changes.jpeg)
**OR**
Upload the `Server Key` via API call using a backend SDK
```js
await client.updateAppSettings({
firebase_config: {
server_key: 'server_key',
notification_template: `{"message":{"notification":{"title":"New messages","body":"You have {{ unread_count }} new message(s) from {{ sender.name }}"},"android":{"ttl":"86400s","notification":{"click_action":"OPEN_ACTIVITY_1"}}}}`,
data_template: `{"sender":"{{ sender.id }}","channel":{"type": "{{ channel.type }}","id":"{{ channel.id }}"},"message":"{{ message.id }}"}`
},
});
```
### Registering a device at Stream Backend
Once you configure Firebase server key and set it up on Stream dashboard a device that is supposed to receive push notifications needs to be registered at Stream backend. This is usually done by listening for Firebase device token updates and passing them to the backend as follows:
```dart
firebaseMessaging.onTokenRefresh.listen((token) {
client.addDevice(token, PushProvider.firebase);
});
```
### Possible issues
We only send push notifications when the user doesn't have any active websocket connection (which is established when you call `client.connectUser`). If you set the [onBackgroundEventReceived](https://pub.dev/documentation/stream_chat_flutter/latest/stream_chat_flutter/StreamChat/onBackgroundEventReceived.html) property of the StreamChat widget, when your app goes to background, your device will keep the ws connection alive for 1 minute, and so within this period, you won't receive any push notification.
Make sure to read the [general push docs](https://getstream.io/chat/docs/flutter-dart/push_introduction/?language=dart) in order to avoid known gotchas that may make your relationship with notifications go bad 😢
### Testing if Push Notifications are Setup Correctly
If you're not sure if you've set up push notifications correctly (e.g. you don't always receive them, they work unreliably), you can follow these steps to make sure your config is correct and working:
1. Clone our repo for push testing git clone git@github.com:GetStream/chat-push-test.git
2. `cd flutter`
3. In folder run `flutter pub get`
4. Input your api key and secret in `lib/main.dart`
5. Change the bundle identifier/application ID and development team/user so you can run the app in your device (**do not** run on iOS simulator, Android emulator is fine)
6. Add your `google-services.json/GoogleService-Info.plist`
7. Run the app
8. Accept push notification permission (iOS only)
9. Tap on `Device ID` and copy it
10. Send the app to background
11. After configuring [stream-cli](https://github.com/GetStream/stream-cli) paste the following command on command line using your user ID
```shell
stream chat:push:test -u <USER-ID>
```
You should get a test push notification
### App in the background but still connected
The [StreamChat](https://pub.dev/documentation/stream_chat_flutter/latest/stream_chat_flutter/StreamChat-class.html) widget lets you define a [onBackgroundEventReceived](https://pub.dev/documentation/stream_chat_flutter/latest/stream_chat_flutter/StreamChat/onBackgroundEventReceived.html) handler in order to handle events while the app is in the background, but the client is still connected.
This is useful because it lets you keep the connection alive in cases in which the app goes in the background just for some seconds (eg: multitasking, picking pictures from the gallery...)
You can even customize the [backgroundKeepAlive](https://pub.dev/documentation/stream_chat_flutter/latest/stream_chat_flutter/StreamChat/backgroundKeepAlive.html) duration.
In order to show notifications in such a case we suggest using the package [flutter_local_notifications](https://pub.dev/packages/flutter_local_notifications); follow the package guide to successfully set up the plugin.
Once that's done you should set the [onBackgroundEventReceived](https://pub.dev/documentation/stream_chat_flutter/latest/stream_chat_flutter/StreamChat/onBackgroundEventReceived.html); here is an example:
```dart
...
StreamChat(
client: client,
onBackgroundEventReceived: (e) {
final currentUserId = client.state.user.id;
if (![
EventType.messageNew,
EventType.notificationMessageNew,
].contains(event.type) ||
event.user.id == currentUserId) {
return;
}
if (event.message == null) return;
final flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();
final initializationSettingsAndroid =
AndroidInitializationSettings('launch_background');
final initializationSettingsIOS = IOSInitializationSettings();
final initializationSettings = InitializationSettings(
android: initializationSettingsAndroid,
iOS: initializationSettingsIOS,
);
await flutterLocalNotificationsPlugin.initialize(initializationSettings);
await flutterLocalNotificationsPlugin.show(
event.message.id.hashCode,
event.message.user.name,
event.message.text,
NotificationDetails(
android: AndroidNotificationDetails(
'message channel',
'Message channel',
'Channel used for showing messages',
priority: Priority.high,
importance: Importance.high,
),
iOS: IOSNotificationDetails(),
),
);
},
child: ....
);
...
```
As you can see we generate a local notification whenever a message.new or notification.message_new event is received.
### Foreground notifications
Sometimes you may want to show a notification when the app is in the foreground.
For example, when you're in a channel and you receive a new message from someone in another channel.
For this scenario, you can also use the `flutter_local_notifications` package to show a notification.
You need to listen for new events using `StreamChatClient.on` and handle them accordingly.
Here we're checking if the event is a `message.new` or `notification.message_new` event, and if the message is from a different user than the current user. In that case we'll show a notification.
```dart
client.on(
EventType.messageNew,
EventType.notificationMessageNew,
).listen((event) {
if (event.message?.user?.id == client.state.currentUser?.id) {
return;
}
showLocalNotification(event, client.state.currentUser!.id, context);
});
```
:::note
You should also check that the channel of the message is different than the channel in the foreground.
How you do this depends on your app infrastructure and how you handle navigation.
Take a look at the [Stream Chat v1 sample app](https://github.com/GetStream/flutter-samples/blob/main/packages/stream_chat_v1/lib/home_page.dart#L11) to see how we're doing it over there.
:::
### Saving notification messages to the offline storage
You may want to save received messages when you receive them via a notification so that later on when you open the app they're already there.
To do this we need to update the push notification data payload at Stream Dashboard and clear the notification one:
```json
{
"message_id": "{{ message.id }}",
"channel_id": "{{ channel.id }}",
"channel_type": "{{ channel.type }}"
}
```
Then we need to integrate the package [stream_chat_persistence](https://pub.dev/packages/stream_chat_persistence) in our app that exports a persistence client, learn [here](https://pub.dev/packages/stream_chat_persistence#usage) how to set it up.
Then during the call `firebaseMessaging.configure(...)` we need to set the `onBackgroundMessage` parameter using a TOP-LEVEL or STATIC function to handle background messages; here is an example:
```dart
Future<dynamic> myBackgroundMessageHandler(message) async {
if (message.containsKey('data')) {
final data = message['data'];
final messageId = data['message_id'];
final channelId = data['channel_id'];
final channelType = data['channel_type'];
final cid = '$channelType:$channelId';
final client = StreamChatClient(apiKey);
final persistenceClient = StreamChatPersistenceClient();
await persistenceClient.connect(userId);
final message = await client.getMessage(messageId).then((res) => res.message);
await persistenceClient.updateMessages(cid, [message]);
persistenceClient.disconnect();
/// This can be done using the package flutter_local_notifications as we did before 👆
_showLocalNotification();
}
}
```
@@ -0,0 +1,301 @@
---
id: adding_push_notifications_v2
sidebar_position: 1
title: Adding Push Notifications (V2)
---
Adding Push Notifications To Your Application
### Introduction
Push notifications are a core part of the experience for a messaging app. Users often need to be notified
of new messages and old notifications sometimes need to be updated silently.
This guide details how to add push notifications to your app.
You can read more about Streams [push delivery logic](https://getstream.io/chat/docs/flutter-dart/push_introduction/?language=dart#push-delivery-rules).
### Setup FCM
To integrate push notifications in your Flutter app, you need to use the package [firebase_messaging](https://pub.dev/packages/firebase_messaging).
Follow the [Firebase documentation](https://firebase.flutter.dev/docs/messaging/overview/) to set up the plugin for Android and iOS.
Once that's done, FCM should be able to send push notifications to your devices.
### Integration With Stream
#### Step 1 - Get the Firebase Credentials
These credentials are the [private key file](https://firebase.google.com/docs/admin/setup#:~:text=To%20generate%20a%20private%20key%20file%20for%20your%20service%20account%3A) for your service account, in firebase console.
To generate a private key file for your service account, in the Firebase console:
- Open Settings > Service Accounts.
- Click **Generate New Private Key**, then confirm by clicking **Generate Key**.
- Securely store the JSON file containing the key.
This JSON file contains the credentials which needs to be uploaded to Streams server as explained in next step.
#### Step 2 - Upload the Firebase Credentials to Stream
You can upload your Firebase credentials using either the dashboard or the app settings API (available only in backend SDKs).
##### Using the Stream Dashboard
1. Go to the **Chat Overview** page on Stream Dashboard
![](../assets/chat_overview_page-2fbd5bbfb70c5623bd37ff7d6c41bf4d.png)
2. Enable **Firebase Notification** toggle on **Chat Overview**
![](../assets/firebase_notifications_toggle-5aeabfcbdc24cb8f1fea7d41d0e845fc.png)
3. Enter your Firebase Credentials and press "Save".
##### Using the API
You can also enable Firebase notifications and upload the Firebase credentials using one of our server SDKs.
For example, using the JavaScript SDK:
```js
const client = StreamChat.getInstance('api_key', 'api_secret');
client.updateAppSettings({
push_config: {
version: 'v2'
},
firebase_config: {
credentials_json: fs.readFileSync(
'./firebase-credentials.json',
'utf-8',
),
});
```
### Registering a Device With Stream Backend
Once you configure a Firebase server key and set it up on Stream dashboard then a device that is supposed to receive push notifications needs to be registered on the Stream backend. This is usually done by listening for Firebase device token updates and passing them to the backend as follows:
```dart
firebaseMessaging.onTokenRefresh.listen((token) {
client.addDevice(token, PushProvider.firebase);
});
```
### Receiving Notifications
Push notifications behave a bit differently depending on whether you are using iOS or Android.
See [here](https://firebase.flutter.dev/docs/messaging/usage#message-types) to understand the difference between **notification** and **data** payloads.
#### iOS
On iOS we send both a **notification** and a **data** payload.
This means you don't need to do anything special to get the notification to show up. However, you might want to handle the data payload to perform some logic when the user taps on the notification.
To update the template, you can use a backend SDK.
For example, using the javascript SDK:
```js
const client = StreamChat.getInstance(api_key, api_secret);
const apn_template = `{
"aps": {
"alert": {
"title": "New message from {{ sender.name }}",
"body": "{{ truncate message.text 2000 }}"
},
"mutable-content": 1,
"category": "stream.chat"
},
"stream": {
"sender": "stream.chat",
"type": "message.new",
"version": "v2",
"id": "{{ message.id }}",
"cid": "{{ channel.cid }}"
}
}`;
client.updateAppSettings({
firebase_config: {
apn_template,
});
```
#### Android
On Android we send only a **data** payload. This gives you more flexibility and lets you decide what to do with the notification.
For example, you can listen and generate a notification from them.
To generate a notification when a **data-only** message is received and the app is in background:
```dart
Future<void> onBackgroundMessage(RemoteMessage message) async {
final chatClient = StreamChatClient(apiKey);
chatClient.connectUser(
User(id: userId),
userToken,
connectWebSocket: false,
);
handleNotification(message, chatClient);
}
void handleNotification(
RemoteMessage message,
StreamChatClient chatClient,
) async {
final data = message.data;
if (data['type'] == 'message.new') {
final flutterLocalNotificationsPlugin = await setupLocalNotifications();
final messageId = data['id'];
final response = await chatClient.getMessage(messageId);
flutterLocalNotificationsPlugin.show(
1,
'New message from ${response.message.user.name} in ${response.channel.name}',
response.message.text,
NotificationDetails(
android: AndroidNotificationDetails(
'new_message',
'New message notifications channel',
)),
);
}
}
FirebaseMessaging.onBackgroundMessage(onBackgroundMessage);
```
In the above example, you get the message details using the `getMessage` method and then you use the [flutter_local_notifications](https://pub.dev/packages/flutter_local_notifications) package to show the actual notification.
##### Using a Template on Android
It's still possible to add a **notification** payload to Android notifications.
You can do so by adding a template using a backend SDK.
For example, using the javascript SDK:
```js
const client = StreamChat.getInstance(api_key, api_secret);
const notification_template = `
{
"title": "{{ sender.name }} @ {{ channel.name }}",
"body": "{{ message.text }}",
"click_action": "OPEN_ACTIVITY_1",
"sound": "default"
}`;
client.updateAppSettings({
firebase_config: {
notification_template,
});
```
### Possible Issues
Make sure to read the [general push notification docs](https://getstream.io/chat/docs/flutter-dart/push_introduction/?language=dart) in order to avoid known gotchas that may make your relationship with notifications difficult 😢.
### Testing if Push Notifications are Setup Correctly
If you're not sure whether you've set up push notifications correctly, for example, you don't always receive them, or they dont work reliably, then you can follow these steps to make sure your config is correct and working:
1. Clone our repo for push testing: `git clone git@github.com:GetStream/chat-push-test.git`
2. `cd flutter`
3. In that folder run `flutter pub get`
4. Input your api key and secret in `lib/main.dart`
5. Change the bundle identifier/application ID and development team/user so you can run the app on your physical device.**Do not** run on an iOS simulator, as it will not work. Testing on an Android emulator is fine.
6. Add your `google-services.json/GoogleService-Info.plist`
7. Run the app
8. Accept push notification permission (iOS only)
9. Tap on `Device ID` and copy it
11. After configuring [stream-cli](https://github.com/GetStream/stream-cli), run the following command using your user ID:
```shell
stream chat:push:test -u <USER-ID>
```
You should get a test push notification 🥳
### Foreground Notifications
Sometimes you may want to show a notification when the app is in the foreground.
For example, when you're in a channel and you receive a new message from someone in another channel.
For this scenario, you can also use the `flutter_local_notifications` package to show a notification.
You need to listen for new events using `FirebaseMessaging.onMessage.listen()` and handle them accordingly:
```dart
FirebaseMessaging.onMessage.listen((message) async {
handleNotification(
message,
chatClient,
);
});
```
:::note
You should also check that the channel of the message is different than the channel in the foreground.
How you do this depends on your app infrastructure and how you handle navigation.
Take a look at the [Stream Chat v1 sample app](https://github.com/GetStream/flutter-samples/blob/main/packages/stream_chat_v1/lib/home_page.dart#L11) to see how we're doing it over there.
:::
### Saving Notification Messages to the Offline Storage (Only Android)
When the app is closed you may want to save received messages when you receive them via a notification so that later on when you open the app they're already there.
To do this you need to integrate the package [stream_chat_persistence](https://pub.dev/packages/stream_chat_persistence) in our app that exports a persistence client, see [here](https://pub.dev/packages/stream_chat_persistence#usage) how to set it up.
Then calling `FirebaseMessaging.onBackgroundMessage(...)` you need to use a TOP-LEVEL or STATIC function to handle background messages; here is an example:
```dart
Future<void> onBackgroundMessage(RemoteMessage message) async {
final chatClient = StreamChatClient(apiKey);
final persistenceClient = StreamChatPersistenceClient();
await persistenceClient.connect(userId);
chatClient.connectUser(
User(id: userId),
userToken,
connectWebSocket: false,
);
handleNotification(message, chatClient);
}
void handleNotification(
RemoteMessage message,
StreamChatClient chatClient,
) async {
final data = message.data;
if (data['type'] == 'message.new') {
final flutterLocalNotificationsPlugin = await setupLocalNotifications();
final messageId = data['id'];
final cid = data['cid'];
final response = await chatClient.getMessage(messageId);
await persistenceClient.updateMessages(cid, [response.message]);
persistenceClient.disconnect();
flutterLocalNotificationsPlugin.show(
1,
'New message from ${response.message.user.name} in ${response.channel.name}',
response.message.text,
NotificationDetails(
android: AndroidNotificationDetails(
'new_message',
'New message notifications channel',
)),
);
}
}
FirebaseMessaging.onBackgroundMessage(onBackgroundMessage);
```
@@ -0,0 +1,82 @@
---
id: customize_message_actions
sidebar_position: 8
title: Customize Message Actions
---
Customizing Message Actions
### Introduction
Message actions pop up in message overlay, when you long-press a message.
![](../assets/message_actions.jpg)
We have provided granular control over these actions.
By default we render the following message actions:
* edit message
* delete message
* reply
* thread reply
* copy message
* flag message
* pin message
:::note
Edit and delete message are only available on messages sent by the user.
Additionally, pinning a message requires you to add the roles which are allowed to pin messages.
:::
### Partially remove some message actions
For example, if you only want to keep "copy message" and "delete message",
here is how to do it using the `messageBuilder` with our `MessageWidget`.
```dart
MessageListView(
messageBuilder: (context, details, messages, defaultMessage) {
return defaultMessage.copyWith(
showFlagButton: false,
showEditMessage: false,
showCopyMessage: true,
showDeleteMessage: details.isMyMessage,
showReplyMessage: false,
showThreadReplyMessage: false,
);
},
)
```
### Add a new custom message action
The SDK also allows you to add new actions into the dialog.
For example, let's suppose you want to introduce a new message action - "Demo Action":
We use the `customActions` parameter of the `MessageWidget` to add extra actions.
```dart
MessageListView(
messageBuilder: (context, details, messages, defaultMessage) {
return defaultMessage.copyWith(
customActions: [
MessageAction(
leading: Icon(Icons.add),
title: Text('Demo Action'),
onTap: (message) {
/// Complete action here
},
),
],
);
},
)
```
@@ -0,0 +1,182 @@
---
id: customize_message_widget
sidebar_position: 11
title: Customizing The MessageWidget
---
Customizing Text Messages
### Introduction
Every application provides a unique look and feel to their own messaging interface including and not
limited to fonts, colors, and shapes.
This guide details how to customize the `MessageWidget` in the Stream Chat Flutter UI SDK.
### Building Custom Messages
This guide goes into detail about the ability to customize the `MessageWidget`. However, if you want
to customize the default `MessageWidget` in the `MessageListView` provided, you can use the `.copyWith()` method
provided inside the `messageBuilder` parameter of the `MessageListView` like this:
```dart
MessageListView(
messageBuilder: (context, details, messageList, defaultImpl) {
// Your implementation of the message here
// E.g: return Text(details.message.text ?? '');
},
),
```
### Theming
You can customize the `MessageWidget` using the `StreamChatTheme` class, so that you can change the
message theme at the top instead of creating your own `MessageWidget` at the lower implementation level.
There are several things you can change in the theme including text styles and colors of various elements.
You can also set a different theme for the user's own messages and messages received by them.
:::note
Theming allows you to change minor factors like style while using the widget directly allows you much
more customization such as replacing a certain widget with another. Some things can only be customized
through the widget and not the theme.
:::
Here is an example:
```dart
StreamChatThemeData(
/// Sets theme for user's messages
ownMessageTheme: MessageThemeData(
messageBackgroundColor: colorTheme.textHighEmphasis,
),
/// Sets theme for received messages
otherMessageTheme: MessageThemeData(
avatarTheme: AvatarThemeData(
borderRadius: BorderRadius.circular(8),
),
),
)
```
![](../assets/message_theming.png)
#### Change message text style
The `MessageWidget` has multiple `Text` widgets that you can manipulate the styles of. The three main
are the actual message text, user name, message links, and the message timestamp.
```dart
MessageThemeData(
messageTextStyle: TextStyle(...),
createdAtStyle: TextStyle(...),
messageAuthorStyle: TextStyle(...),
messageLinksStyle: TextStyle(...),
)
```
![](../assets/message_styles.png)
#### Change avatar theme
You can change the attributes of the avatar (if displayed) using the `avatarTheme` property.
```dart
MessageThemeData(
avatarTheme: AvatarThemeData(
borderRadius: BorderRadius.circular(8),
),
)
```
![](../assets/message_rounded_avatar.png)
#### Changing Reaction theme
You also customize the reactions attached to every message using the theme.
```dart
MessageThemeData(
reactionsBackgroundColor: Colors.red,
reactionsBorderColor: Colors.redAccent,
reactionsMaskColor: Colors.pink,
),
```
![](../assets/message_reaction_theming.png)
### Changing Message Actions
When a message is long pressed, the `MessageActionsModal` is shown.
The `MessageWidget` allows showing or hiding some options if you so choose.
```dart
MessageWidget(
...
showUsername = true,
showTimestamp = true,
showReactions = true,
showDeleteMessage = true,
showEditMessage = true,
showReplyMessage = true,
showThreadReplyMessage = true,
showResendMessage = true,
showCopyMessage = true,
showFlagButton = true,
showPinButton = true,
showPinHighlight = true,
),
```
![](../assets/message_widget_actions.png)
### Building attachments
The `customAttachmentBuilder` property allows you to build any kind of attachment (inbuilt or custom)
in your own way. While a separate guide is written for this, it is included here because of relevance.
```dart
MessageListView(
messageBuilder: (context, details, messages, defaultMessage) {
return defaultMessage.copyWith(
customAttachmentBuilders: {
'location': (context, message, attachments) {
final attachmentWidget = Image.network(
_buildMapAttachment(
attachments[0].extraData['latitude'],
attachments[0].extraData['longitude'],
),
);
return wrapAttachmentWidget(context, attachmentWidget, null, true, BorderRadius.circular(8.0));
}
},
);
},
),
```
### Widget Builders
Some parameters allow you to construct your own widget in place of some elements in the `MessageWidget`.
These are:
* `userAvatarBuilder` : Allows user to substitute their own widget in place of the user avatar.
* `editMessageInputBuilder` : Allows user to substitute their own widget in place of the input in edit mode.
* `textBuilder` : Allows user to substitute their own widget in place of the text.
* `bottomRowBuilder` : Allows user to substitute their own widget in the bottom of the message when not deleted.
* `deletedBottomRowBuilder` : Allows user to substitute their own widget in the bottom of the message when deleted.
```dart
MessageWidget(
...
textBuilder: (context, message) {
// Add your own text implementation here.
},
),
```
@@ -0,0 +1,158 @@
---
id: customize_text_messages
sidebar_position: 6
title: Customize Text Messages
---
Customizing Text Messages
### Introduction
Every application provides a unique look and feel to their own messaging interface including and not
limited to fonts, colors, and shapes.
This guide details how to customize message text in the `MessageListView` / `MessageWidget` in the
Stream Chat Flutter UI SDK.
:::note
This guide is specifically for the `MessageListView` but if you intend to display a `MessageWidget`
separately, follow the same process without the `.copyWith` and use the default constructor instead.
:::
### Basics of customizing a `MessageWidget`
First, add a `MessageListView` in the appropriate place where you intend to display messages from a
channel.
```dart
MessageListView(
...
)
```
Now, we use the `messageBuilder` parameter to build a custom message. The builder function also provides
the default implementation of the `MessageWidget` so that we can change certain aspects of the widget
without redoing all of the default parameters.
:::note
In earlier versions of the SDK, some `MessageWidget` parameters were exposed directly through the `MessageListView`,
however, this quickly becomes hard to maintain as more parameters and customizations are added to the
`MessageWidget`. Newer version utilise a cleaner interface to change the parameters by supplying a
default message implementation as aforementioned.
:::
```dart
MessageListView(
...
messageBuilder: (context, messageDetails, messageList, defaultWidget) {
return defaultWidget;
},
)
```
We use `.copyWith()` to customize the widget:
```dart
MessageListView(
...
messageBuilder: (context, messageDetails, messageList, defaultWidget) {
return defaultWidget.copyWith(
...
);
},
)
```
### Customizing text
If you intend to simply change the theme for the text, you need not recreate the whole widget. The
`MessageWidget` has a `messageTheme` parameter that allows you to pass the theme for most aspects
of the message.
```dart
MessageListView(
...
messageBuilder: (context, messageDetails, messageList, defaultWidget) {
return defaultWidget.copyWith(
messageTheme: MessageTheme(
...
messageText: TextStyle(),
),
);
},
)
```
If you want to replace the entire text widget in the `MessageWidget`, you can use the `textBuilder`
parameter which provides a builder for creating a widget to substitute the default text.parameter
```dart
MessageListView(
...
messageBuilder: (context, messageDetails, messageList, defaultWidget) {
return defaultWidget.copyWith(
textBuilder: (context, message) {
return Text(message.text);
},
);
},
)
```
### Adding Hashtags
To add elements like hashtags, we can override the `textBuilder` in the MessageWidget:
```dart
MessageListView(
...
messageBuilder: (context, messageDetails, messageList, defaultWidget) {
return defaultWidget.copyWith(
textBuilder: (context, message) {
final text = _replaceHashtags(message.text).replaceAll('\n', '\\\n');
final messageTheme = StreamChatTheme.of(context).ownMessageTheme;
return MarkdownBody(
data: text,
onTapLink: (
String link,
String href,
String title,
) {
// Do something with tapped hashtag
},
styleSheet: MarkdownStyleSheet.fromTheme(
Theme.of(context).copyWith(
textTheme: Theme.of(context).textTheme.apply(
bodyColor: messageTheme.messageText.color,
decoration: messageTheme.messageText.decoration,
decorationColor: messageTheme.messageText.decorationColor,
decorationStyle: messageTheme.messageText.decorationStyle,
fontFamily: messageTheme.messageText.fontFamily,
),
),
).copyWith(
a: messageTheme.messageLinks,
p: messageTheme.messageText,
),
);
},
);
},
)
String _replaceHashtags(String text) {
RegExp exp = new RegExp(r"\B#\w\w+");
exp.allMatches(text).forEach((match){
text = text.replaceAll(
'${match.group(0)}', '[${match.group(0)}](${match.group(0).replaceAll(' ', '')})');
});
return text;
}
```
We can replace the hashtags using RegEx and add links for the MarkdownBody which is done here in the
`_replaceHashtags()` function.
Inside the textBuilder, we use the `flutter_markdown` package to build our hashtags as links.
![](../assets/hashtag_example.jpg)
@@ -0,0 +1,258 @@
---
id: end_to_end_chat_encryption
sidebar_position: 12
title: End To End Chat Encryption
---
## Introduction
When you communicate over a chat application with another person or group,
you may exchange sensitive information, like personally identifiable information, financial details, or passwords.
A chat application should use end-to-end encryption to ensure that users' data stays secure.
:::note
Before you start, keep in mind that this guide is a basic example intended for educational purposes only.
If you want to implement end-to-end encryption in your production app, please consult a security professional first.
Theres a lot more to consider from a security perspective that isnt covered here.
:::
## What is End-to-End Encryption?
End-to-end encryption (E2EE) is the process of securing a message from third parties so that only the sender and receiver can access the message.
E2EE provides security by storing the message in an encrypted form on the application's server or database.
You can only access the message by decrypting and signing it using a known public key (distributed freely)
and a corresponding private key (only known by the owner).
Each user in the application has their own public-private key pair.
Public keys are distributed publicly and encrypt the senders messages.
The receiver can only decrypt the senders message with the matching private key.
Check out the diagram below for an example:
![](../assets/end_to_end_encryption.png)
## Setup
### Dependencies
Add the [webcrypto](https://pub.dev/packages/webcrypto) package in your `pubspec.yaml` file.
```yaml
dependencies:
webcrypto: ^0.5.2 # latest version
```
### Generate Key Pair
Write a function that generates a key pair using the **ECDH** algorithm and the **P-256** elliptic curve (**P-256** is well-supported and
offers the right balance of security and performance).
The pair will consist of two keys:
- **PublicKey**: The key that is linked to a user to encrypt messages.
- **PrivateKey**: The key that is stored locally to decrypt messages.
```dart
Future<JsonWebKeyPair> generateKeys() async {
final keyPair = await EcdhPrivateKey.generateKey(EllipticCurve.p256);
final publicKeyJwk = await keyPair.publicKey.exportJsonWebKey();
final privateKeyJwk = await keyPair.privateKey.exportJsonWebKey();
return JsonWebKeyPair(
privateKey: json.encode(privateKeyJwk),
publicKey: json.encode(publicKeyJwk),
);
}
// Model class for storing keys
class JsonWebKeyPair {
const JsonWebKeyPair({
required this.privateKey,
required this.publicKey,
});
final String privateKey;
final String publicKey;
}
```
### Generate a Crypto Key
Next, create a symmetric **Crypto Key** using the keys generated in the previous step.
You will use those keys to encrypt and decrypt messages.
```dart
// SendersJwk -> sender.privateKey
// ReceiverJwk -> receiver.publicKey
Future<List<int>> deriveKey(String senderJwk, String receiverJwk) async {
// Sender's key
final senderPrivateKey = json.decode(senderJwk);
final senderEcdhKey = await EcdhPrivateKey.importJsonWebKey(
senderPrivateKey,
EllipticCurve.p256,
);
// Receiver's key
final receiverPublicKey = json.decode(receiverJwk);
final receiverEcdhKey = await EcdhPublicKey.importJsonWebKey(
receiverPublicKey,
EllipticCurve.p256,
);
// Generating CryptoKey
final derivedBits = await senderEcdhKey.deriveBits(256, receiverEcdhKey);
return derivedBits;
}
```
### Encrypting Messages
Once you have generated the **Crypto Key**, you're ready to encrypt the message.
You can use the **AES-GCM** algorithm for its known security and performance balance and good browser availability.
```dart
// The "iv" stands for initialization vector (IV). To ensure the encryptions strength,
// each encryption process must use a random and distinct IV.
// Its included in the message so that the decryption procedure can use it.
final Uint8List iv = Uint8List.fromList('Initialization Vector'.codeUnits);
```
```dart
Future<String> encryptMessage(String message, List<int> deriveKey) async {
// Importing cryptoKey
final aesGcmSecretKey = await AesGcmSecretKey.importRawKey(deriveKey);
// Converting message into bytes
final messageBytes = Uint8List.fromList(message.codeUnits);
// Encrypting the message
final encryptedMessageBytes =
await aesGcmSecretKey.encryptBytes(messageBytes, iv);
// Converting encrypted message into String
final encryptedMessage = String.fromCharCodes(encryptedMessageBytes);
return encryptedMessage;
}
```
### Decrypting Messages
Decrypting a message is the opposite of encrypting one.
To decrypt a message to a human-readable format, use the code snippet below:
```dart
Future<String> decryptMessage(String encryptedMessage, List<int> deriveKey) async {
// Importing cryptoKey
final aesGcmSecretKey = await AesGcmSecretKey.importRawKey(deriveKey);
// Converting message into bytes
final messageBytes = Uint8List.fromList(encryptedMessage.codeUnits);
// Decrypting the message
final decryptedMessageBytes =
await aesGcmSecretKey.decryptBytes(messageBytes, iv);
// Converting decrypted message into String
final decryptedMessage = String.fromCharCodes(decryptedMessageBytes);
return decryptedMessage;
}
```
## Implement as a Stream Chat Feature
Now that your setup is complete you can use it to implement end-to-end encryption in your app.
### Store User's Public Key
The first thing you need to do is store the generated `publicKey` as an `extraData` property, in order
for other users to encrypt messages.
```dart
// Generating keyPair using the function defined in above steps
final keyPair = generateKeys();
```
```dart
await client.connectUser(
User(
id: 'cool-shadow-7',
name: 'Cool Shadow',
image: 'https://getstream.io/cool-shadow',
// set publicKey as a extraData property
extraData: { 'publicKey': keyPair.publicKey },
),
client.devToken('cool-shadow-7').rawValue,
);
```
### Sending Encrypted Messages
Now you will use the `encryptMessage()` function created in the previous steps to encrypt the message.
To do that, you need to make some minor changes to the **MessageInput** widget.
```dart
final receiverJwk = receiver.extraData['publicKey'];
// Generating derivedKey using user's privateKey and receiver's publicKey
final derivedKey = await deriveKey(keyPair.privateKey, receiverJwk);
```
```dart
MessageInput(
...
preMessageSending: (message) async {
// Encrypting the message text using derivedKey
final encryptedMessage = await encryptMessage(message.text, derivedKey);
// Creating a new message with the encrypted message text
final newMessage = message.copyWith(text: encryptedMessage);
return newMessage;
},
),
```
`preMessageSending` is a parameter that allows your app to process the message before it goes to Streams server.
Here, you have used it to encrypt the message before sending it to Streams backend.
### Showing Decrypted Messages
Now, its time to decrypt the message and present it in a human-readable format to the receiver.
You can customize the **MessageListView** widget to have a custom `messagebuilder`, that can decrypt the message.
```dart
MessageListView(
...
messageBuilder: (context, messageDetails, currentMessages, defaultWidget) {
// Retrieving the message from details
final message = messageDetails.message;
// Decrypting the message text using the derivedKey
final decryptedMessageFuture = decryptMessage(message.text, derivedKey);
return FutureBuilder<String>(
future: decryptedMessageFuture,
builder: (context, snapshot) {
if (snapshot.hasError) return Text('Error: ${snapshot.error}');
if (!snapshot.hasData) return Container();
// Updating the original message with the decrypted text
final decryptedMessage = message.copyWith(text: snapshot.data);
// Returning defaultWidget with updated message
return defaultWidget.copyWith(
message: decryptedMessage,
);
},
);
},
),
```
That's it! That's all you need to implement E2EE in a Stream powered chat app.
For more details, check out our [end-to-end encrypted chat article](https://getstream.io/blog/end-to-end-encrypted-chat-in-flutter/#whats-end-to-end-encryption).
@@ -0,0 +1,298 @@
---
id: mig_guide_2_0
sidebar_position: 4
title: Migrating to 2.0 (Null-safety)
---
A Migration Guide For Switching To v2.0 Of The Flutter SDK
### Overview
v2.0 of the Stream Chat Flutter SDK brings along several changes - primarily making the SDK null-safe.
Null safety allows your apps to run faster, with fewer errors, and with less code.
Check [this link](https://flutter.dev/docs/null-safety) for more about Null Safety in Flutter.
This guide is intended to enumerate and better explain the changes in the SDK.
The changes will be listed by package and a concise changelog will follow with more info.
### Changelog of `stream_chat_flutter`
#### 🛑️ Breaking Changes from 1.5.4
* Migrate this package to null safety
* Renamed `ChannelImage` to `ChannelAvatar`
* Updated `StreamChatThemeData.reactionIcons` to accept custom builder
* Renamed `ColorTheme` properties to reflect the purpose of the colors
* `ColorTheme.black` -> `ColorTheme.textHighEmphasis`
* `ColorTheme.grey` -> `ColorTheme.textLowEmphasis`
* `ColorTheme.greyGainsboro` -> `ColorTheme.disabled`
* `ColorTheme.greyWhisper` -> `ColorTheme.borders`
* `ColorTheme.whiteSmoke` -> `ColorTheme.inputBg`
* `ColorTheme.whiteSnow` -> `ColorTheme.appBg`
* `ColorTheme.white` -> `ColorTheme.barsBg`
* `ColorTheme.blueAlice` -> `ColorTheme.linkBg`
* `ColorTheme.accentBlue` -> `ColorTheme.accentPrimary`
* `ColorTheme.accentRed` -> `ColorTheme.accentError`
* `ColorTheme.accentGreen` -> `ColorTheme.accentInfo`
* `ChannelListCore` options property is removed in favor of individual properties
* `options.state` -> `bool state`
* `options.watch` -> `bool watch`
* `options.presence` -> `bool presence`
* `UserListView` options property is removed in favor of individual properties
* `options.presence` -> `bool presence`
* Renamed `ImageHeader` to `GalleryHeader`
* Renamed `ImageFooter` to `GalleryFooter`
* `MessageBuilder` and `ParentMessageBuilder` signature is now
```dart
typedef MessageBuilder = Widget Function(
BuildContext,
MessageDetails,
List<Message>,
MessageWidget defaultMessageWidget,
);
```
The last parameter is the default `MessageWidget`
You can call `.copyWith` to customize just a subset of properties
#### ✅ Added
Added video compress options (frame and quality) to MessageInput
`TypingIndicator` now has a property called `parentId` to show typing indicator specific to threads
#493: add support for `MessageListView` header/footer
`MessageWidget` accepts a `userAvatarBuilder`
Added `pinMessage` ui support
Added `MessageListView.threadSeparatorBuilder` property
Added `MessageInput.onError` property to allow error handling
Added `GalleryHeader`/`GalleryFooter` theme classes
#### 🐞 Fixed
#483: Keyboard covers input text box when editing message
`Modals` are shown using the nearest `Navigator` to make using the SDK easier in a nested navigator use case
#484: messages don't update without a reload
`MessageListView` not rendering if the user is not a member of the channel
Fix `MessageInput` overflow when there are no actions
Minor fixes and improvements
### Migrating to 2.0 for `stream_chat_flutter`
:::note
If you are migrating your full Flutter project to null-safety, first make sure you follow the
instructions from the [official Null Safety migration guide](https://dart.dev/null-safety/migration-guide).
:::
To migrate to v2.0 for `stream_chat_flutter`, first change the version of the package to the latest
null-safe version.
```yaml
dependencies:
stream_chat_flutter: ^2.0.0
```
Upon doing this, all breaking changes from the package will take immediate effect. Here are steps to
remedy the issues:
1) Replace the offending class names with the revised class names
* `ChannelImage` -> `ChannelAvatar`
* `ImageHeader` -> `GalleryHeader`
* `ImageFooter` -> `GalleryFooter`
2) The new version comes with revised color names since the previous names do not suit light/dark mode
nomenclature. Make sure any old colors used from theme are changed over to the new theme color names:
* `ColorTheme.black` -> `ColorTheme.textHighEmphasis`
* `ColorTheme.grey` -> `ColorTheme.textLowEmphasis`
* `ColorTheme.greyGainsboro` -> `ColorTheme.disabled`
* `ColorTheme.greyWhisper` -> `ColorTheme.borders`
* `ColorTheme.whiteSmoke` -> `ColorTheme.inputBg`
* `ColorTheme.whiteSnow` -> `ColorTheme.appBg`
* `ColorTheme.white` -> `ColorTheme.barsBg`
* `ColorTheme.blueAlice` -> `ColorTheme.linkBg`
* `ColorTheme.accentBlue` -> `ColorTheme.accentPrimary`
* `ColorTheme.accentRed` -> `ColorTheme.accentError`
* `ColorTheme.accentGreen` -> `ColorTheme.accentInfo`
3) We decided to make messages easier to customize and now supply the default implementation of the
messages in the builder - so you can now customize a single parameter without having to redo the
entire implementation. Please reform your builders to take into account the new format:
```
typedef MessageBuilder = Widget Function(
BuildContext,
MessageDetails,
List<Message>,
MessageWidget defaultMessageWidget,
);
```
To tweak any of the default properties individually, you can use `defaultMessageWidget.copyWith()`.
### Changelog of `stream_chat_flutter_core`
#### 🛑️ Breaking Changes from 1.5.3
* Migrate this package to null safety
* `channelsBloc.queryChannels()`, `ChannelListCore` options param/property is removed in favor of individual params/properties
* `options.state` -> `bool state`
* `options.watch` -> `bool watch`
* `options.presence` -> `bool presence`
* `usersBloc.queryUsers()`, `UserListCore` options param/property is removed in favor of individual params/properties
* `options.presence` -> `bool presence`
#### ✅ Added
* Monitor connection using `connectivity_plus` package
#### 🐞 Fixed
* Minor fixes
* Performance improvements
### Migrating to 2.0 for `stream_chat_flutter_core`
:::note
If you are migrating your full Flutter project to null-safety, first make sure you follow the
instructions from the [official Null Safety migration guide](https://dart.dev/null-safety/migration-guide).
:::
To migrate to v2.0 for `stream_chat_flutter_core`, first change the version of the package to the latest
null-safe version.
```yaml
dependencies:
stream_chat_flutter_core: ^2.0.0
```
Upon doing this, all breaking changes from the package will take immediate effect. Here are steps to
remedy the issue:
:::note
The major changes in `stream_chat_flutter_core` consist of changing over from a map full of options
to a more type safe and sound approach by changing over to explicit parameters.
:::
1) Change over Core widget implementations by using the explicit parameters instead of the options map.
Use these explicit parameters in the widget constructor instead of the option keys:
* `options.state` -> `bool state`
* `options.watch` -> `bool watch`
* `options.presence` -> `bool presence`
2) Change over query calls in the BLoCs in the same way (change from options map to explicit parameters
in the constructor)
### Changelog of `stream_chat`
#### 🛑️ Breaking Changes from 1.5.3
* Migrate this package to null safety
* `ConnectUserWithProvider` now requires `tokenProvider` as a required param. (Removed from the constructor)
* `client.disconnect()` is now divided into two different functions
* `client.closeConnection()` -> for closing user websocket connection.
* `client.disconnectUser()` -> for disconnecting user and resetting client state.
* `client.devToken()` now returns a Token model instead of String.
* `ApiError` is removed in favor of `StreamChatError`
* `StreamChatError` -> parent type for all the stream errors.
* `StreamWebSocketError` -> for user websocket related errors.
* `StreamChatNetworkError` -> for network related errors.
* `client.queryChannels()`, `channel.query()` options param is removed in favor of individual params
* `option.state` -> `bool state`
* `option.watch` -> `bool watch`
* `option.presence` -> `bool presence`
* `client.queryUsers()` options param is removed in favor of individual params
* `option.presence` -> `bool presence`
* Added typed filters
#### 🐞 Fixed
* #369: Client does not return without internet connection
* Several minor fixes
* Performance improvements
#### ✅ Added
* New Location enum is introduced for easily changing the client location/baseUrl.
* New `client.openConnection()` and `client.closeConnection()` is introduced to connect/disconnect user ws connection.
* New `client.partialUpdateMessage` and `channel.partialUpdateMessage` methods
* `connectWebSocket` parameter in connect user calls to use the client in "connection-less" mode.
#### 🔄 Changed
* `baseURL` is now deprecated in favor of using Location to change data location.
### Migrating to 2.0 for `stream_chat`
If you are migrating your full Flutter project to null-safety, first make sure you follow the
instructions from the [official Null Safety migration guide](https://dart.dev/null-safety/migration-guide).
:::
To migrate to v2.0 for `stream_chat`, first change the version of the package to the latest
null-safe version.
```yaml
dependencies:
stream_chat: ^2.0.0
```
Upon doing this, all breaking changes from the package will take immediate effect. Here are steps to
remedy the issues:
1) Change over the constructor of `connectUserWithProvider()` to the new format which has `tokenProvider` as a required param.
2) We added more nuance to `disconnectUser()` by adding two new methods - one to close the connection
and the other to disconnect the user. This allows more fine-grained control of disconnection.
* `client.closeConnection()` -> for closing user websocket connection.
* `client.disconnectUser()` -> for disconnecting user and resetting client state.
3) We refactored how we handle errors - new error types are now introduced that replace ApiError.
* `StreamChatError` -> parent type for all the stream errors.
* `StreamWebSocketError` -> for user websocket related errors.
* `StreamChatNetworkError` -> for network related errors.
4) We changed over from a map full of options to a more type-safe and sound approach by changing over to explicit parameters.
Use these explicit parameters in the query parameters instead of the option keys:
* `client.queryChannels()`, `channel.query()` options param is removed in favor of individual params
* `option.state` -> `bool state`
* `option.watch` -> `bool watch`
* `option.presence` -> `bool presence`
* `client.queryUsers()` options param is removed in favor of individual params
* `option.presence` -> `bool presence`
5) We added type-safe filters to make filtering in the app easier. Change over the filters to the
new implementation.
As an example, in the old app this filter:
```dart
filter: {
'members': {
'\$in': [StreamChat.of(context).user.id],
}
},
```
Would turn into:
```dart
filter: Filter.in_('members', [StreamChat.of(context).user.id])
```
@@ -0,0 +1,194 @@
---
id: understanding_filters
sidebar_position: 10
title: Understanding Filters
---
Understanding Filters
### Introduction
Filters are used to get a specific subset of objects (channels, users, messages, members, etc) which
fit the conditions specified. Earlier versions of the SDK contained String-based filters which are now replaced by type-safe
filters. This guide aims to explain the different types of filters and how to use them.
### Types Of Filters
#### Filter.equal
The 'equal' filter gets the objects where the given key has the specified value.
```dart
Filter.equal('type', 'messaging'),
```
#### Filter.notEqual
The 'notEqual' filter gets the objects where the given key does not have the specified value.
```dart
Filter.notEqual('type', 'messaging'),
```
#### Filter.greater
The 'greater' filter gets the objects where the given key has a higher value than the specified value.
```dart
Filter.greater('count', 5),
```
#### Filter.greaterOrEqual
The 'greaterOrEqual' filter gets the objects where the given key has an equal or higher value than the specified value.
```dart
Filter.greaterOrEqual('count', 5),
```
#### Filter.less
The 'less' filter gets the objects where the given key has a lesser value than the specified value.
```dart
Filter.less('count', 5),
```
#### Filter.lessOrEqual
The 'lessOrEqual' filter gets the objects where the given key has a lesser or equal value than the specified value.
```dart
Filter.lessOrEqual('count', 5),
```
#### Filter.in_
The 'in_' filter allows getting objects where the key matches any in a specified array.
```dart
Filter.in_('members', [user.id])
```
:::note
Since 'in' is a keyword in Dart, the filter has an underscore added. This does not apply to the 'notIn'
keyword.
:::
#### Filter.notIn
The 'notIn' filter allows getting objects where the key matches none in a specified array.
```dart
Filter.notIn('members', [user.id])
```
#### Filter.query
The 'query' filter matches values by performing text search with the specified value.
```dart
Filter.query('name', 'demo')
```
#### Filter.autoComplete
The 'autoComplete' filter matches values with the specified prefix.
```dart
Filter.autoComplete('name', 'demo')
```
#### Filter.exists
The 'exists' filter matches values that exist, or don't exist, based on the specified boolean value.
```dart
Filter.exists('name')
```
#### Filter.notExists
The 'notExists' filter checks if the specified key doesn't exist. This is a simplified call to `Filter.exists`
with the value set to false.
```dart
Filter.notExists('name')
```
#### Filter.contains
The 'contains' filter matches any list that contains the specified value.
```dart
Filter.contains('teams', 'red')
```
#### Filter.empty
The 'empty' filter constructor returns an empty filter. It's the equivalent of an empty map `{}`;
```dart
Filter.empty();
```
#### Filter.raw
The 'raw' filter constructor lets you specify a raw filter. We suggest using this only if you can't manage to build what you want using the other constructors.
```dart
Filter.raw(value: {
'members': [
..._selectedUsers.map((e) => e.id),
chatState.currentUser!.id,
],
'distinct': true,
});
```
#### Filter.custom
The 'custom' filter is used to create a custom filter in case it does not exists or it's not been added to the SDK yet.
Note that the filter must be supported by the Stream backend in order to work.
```dart
Filter.custom(
operator: '\$max',
value: 10,
)
```
### Group Queries
#### Filter.and
The 'and' operator combines multiple queries.
```dart
final filter = Filter.and([
Filter.equal('type', 'messaging'),
Filter.in_('members', [user.id])
])
```
#### Filter.or
Combines the provided filters and matches the values matched by at least one of the filters.
```dart
final filter = Filter.or([
Filter.in_('bannedUsers', [user.id]),
Filter.in_('shadowBannedUsers', [user.id])
])
```
#### Filter.nor
Combines the provided filters and matches the values not matched by all the filters.
```dart
final filter = Filter.nor([
Filter.in_('bannedUsers', [user.id]),
Filter.in_('shadowBannedUsers', [user.id])
])
```
@@ -0,0 +1,448 @@
---
id: token_generation_with_firebase
sidebar_position: 5
title: User Token Generation With Firebase Auth and Cloud Functions
---
Securely generate Stream Chat user tokens using Firebase Authentication and Cloud Functions.
:::note
This guide assumes that you are familiar with Firebase Authentication and Cloud Functions for Flutter and using the Flutter Stream Chat SDK.
:::
### Introduction
In this guide, you'll explore how you can use Firebase Auth as an authentication provider and create Firebase Cloud functions to securely
generate Stream Chat user tokens.
You will use Stream's [NodeJS client](https://getstream.io/chat/docs/node/?language=javascript) for Stream account creation and
token generation, and [Flutter Cloud Functions for Firebase](https://firebase.flutter.dev/docs/functions/overview) to invoke the cloud functions
from your Flutter app.
Stream supports several different [backend clients](https://getstream.io/chat/sdk/#backend-clients) to integrate with your server. This guide only shows an easy way to integrate Stream Chat authentication using Firebase and Flutter.
### Flutter Firebase
See the [Flutter Firebase getting started](https://firebase.flutter.dev/docs/overview) docs for setup and installation instructions.
You will also need to add the [Flutter Firebase Authentication](https://firebase.flutter.dev/docs/auth/overview), and [Flutter Firebase Cloud Functions](https://firebase.flutter.dev/docs/functions/overview) packages to your app. Depending on the platform that you target, there may be specific configurations that you need to do.
#### Starting Code
The following code shows a basic application with **FirebaseAuth** and **FirebaseFunctions**.
You will extend this later to execute cloud functions.
```dart
import 'package:cloud_functions/cloud_functions.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
import 'dart:async';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Auth(),
),
);
}
}
class Auth extends StatefulWidget {
Auth({Key? key}) : super(key: key);
@override
_AuthState createState() => _AuthState();
}
class _AuthState extends State<Auth> {
late FirebaseAuth auth;
late FirebaseFunctions functions;
@override
void initState() {
super.initState();
auth = FirebaseAuth.instance;
functions = FirebaseFunctions.instance;
}
final email = 'test@getstream.io';
final password = 'password';
Future<void> createAccount() async {
// Create Firebase account
await auth.createUserWithEmailAndPassword(email: email, password: password);
print('Firebase account created');
}
Future<void> signIn() async {
// Sign in with Firebase
await auth.signInWithEmailAndPassword(email: email, password: password);
print('Firebase signed in');
}
Future<void> signOut() async {
// Revoke Stream chat token.
final callable = functions.httpsCallable('revokeStreamUserToken');
await callable();
print('Stream user token revoked');
}
@override
Widget build(BuildContext context) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
AuthenticationState(
streamUser: auth.authStateChanges(),
),
ElevatedButton(
onPressed: createAccount,
child: Text('Create account'),
),
ElevatedButton(
onPressed: signIn,
child: Text('Sign in'),
),
ElevatedButton(
onPressed: signOut,
child: Text('Sign out'),
),
],
),
);
}
}
class AuthenticationState extends StatelessWidget {
const AuthenticationState({
Key? key,
required this.streamUser,
}) : super(key: key);
final Stream<User?> streamUser;
@override
Widget build(BuildContext context) {
return StreamBuilder<User?>(
stream: streamUser,
builder: (context, snapshot) {
if (snapshot.hasData) {
return (snapshot.data != null)
? Text('Authenticated')
: Text('Not Authenticated');
}
return Text('Not Authenticated');
},
);
}
}
```
Running the above will give this:
![](../assets/authentication_demo_app.jpg)
The `Auth` widget handles all of the authentication logic. It initializes a `FirebaseAuth.instance` and uses that
in the `createAccount`, `signIn` and `signOut` methods. There is a button to envoke each of these methods.
The `FirebaseFunctions.instance` will be used later in this guide.
The `AuthenticationState` widget listens to `auth.authStateChanges()` to display a message
indicating if a user is authenticated.
### Firebase Cloud Functions
Firebase Cloud Functions allows you to extend Firebase with custom operations that an event can trigger:
- **Internal event**: For example, when creating a new Firebase account this is automatically triggered.
- **External event**: For example, directly calling a cloud function from your Flutter application.
To set up your local environment to deploy cloud functions, please see the
[Cloud Functions getting started](https://firebase.flutter.dev/docs/overview) docs.
After initializing your project with cloud functions, you should have a **functions** folder in your project, including a `package.json` file.
There should be two dependencies already added, **firebase-admin** and **firebase-functions**. You will also need to add the **stream-chat** dependency.
Navigate to the **functions** folder and run `npm install stream-chat --save-prod`.
This will install the node module and add it as a dependency to `package.json`.
Now open `index.js` and add the following (this is the complete example):
```js
const StreamChat = require('stream-chat').StreamChat;
const functions = require("firebase-functions");
const admin = require("firebase-admin");
admin.initializeApp();
const serverClient = StreamChat.getInstance(functions.config().stream.key, functions.config().stream.secret);
// When a user is deleted from Firebase their associated Stream account is also deleted.
exports.deleteStreamUser = functions.auth.user().onDelete((user, context) => {
return serverClient.deleteUser(user.uid);
});
// Create a Stream user and return auth token.
exports.createStreamUserAndGetToken = functions.https.onCall(async (data, context) => {
// Checking that the user is authenticated.
if (!context.auth) {
// Throwing an HttpsError so that the client gets the error details.
throw new functions.https.HttpsError('failed-precondition', 'The function must be called ' +
'while authenticated.');
} else {
try {
// Create user using the serverClient.
await serverClient.upsertUser({
id: context.auth.uid,
name: context.auth.token.name,
email: context.auth.token.email,
image: context.auth.token.image,
});
/// Create and return user auth token.
return serverClient.createToken(context.auth.uid);
} catch (err) {
console.error(`Unable to create user with ID ${context.auth.uid} on Stream. Error ${err}`);
// Throwing an HttpsError so that the client gets the error details.
throw new functions.https.HttpsError('aborted', "Could not create Stream user");
}
}
});
// Get Stream user token.
exports.getStreamUserToken = functions.https.onCall((data, context) => {
// Checking that the user is authenticated.
if (!context.auth) {
// Throwing an HttpsError so that the client gets the error details.
throw new functions.https.HttpsError('failed-precondition', 'The function must be called ' +
'while authenticated.');
} else {
try {
return serverClient.createToken(context.auth.uid);
} catch (err) {
console.error(`Unable to get user token with ID ${context.auth.uid} on Stream. Error ${err}`);
// Throwing an HttpsError so that the client gets the error details.
throw new functions.https.HttpsError('aborted', "Could not get Stream user");
}
}
});
// Revoke the authenticated user's Stream chat token.
exports.revokeStreamUserToken = functions.https.onCall((data, context) => {
// Checking that the user is authenticated.
if (!context.auth) {
// Throwing an HttpsError so that the client gets the error details.
throw new functions.https.HttpsError('failed-precondition', 'The function must be called ' +
'while authenticated.');
} else {
try {
return serverClient.revokeUserToken(context.auth.uid);
} catch (err) {
console.error(`Unable to revoke user token with ID ${context.auth.uid} on Stream. Error ${err}`);
// Throwing an HttpsError so that the client gets the error details.
throw new functions.https.HttpsError('aborted', "Could not get Stream user");
}
}
});
```
First, you import the necessary packages and call `admin.initializeApp();` to set up Firebase cloud functions.
Next, you initialize the **StreamChat** server client by calling `StreamChat.getInstance`. This function requires your Stream app's
**token** and **secret**. You can get this from the Stream Dashboard for your app.
Set these values as environment data on Firebase Functions.
```bash
firebase functions:config:set stream.key="app-key" stream.secret="app-secret"
```
*Replace **app-key** and **app-secret** with the values for your Stream app.*
This creates an object of **stream** with properties **key** and **secret**. To access this environment
data use `functions.config().stream.key` and `functions.config().stream.secret`.
See the [Firebase environment configuration](https://firebase.google.com/docs/functions/config-env)
documentation for additional information.
To deploy these functions to Firebase, run:
```bash
firebase deploy --only functions
```
### Create a Stream User and Get the User's Token
In the `createStreamUserAndGetToken` cloud function you create an `onCall` HTTPS handler, which exposes
a cloud function that can be envoked from your Flutter app.
```js
// Create a Stream user and return auth token.
exports.createStreamUserAndGetToken = functions.https.onCall(async (data, context) => {
// Checking that the user is authenticated.
if (!context.auth) {
// Throwing an HttpsError so that the client gets the error details.
throw new functions.https.HttpsError('failed-precondition', 'The function must be called ' +
'while authenticated.');
} else {
try {
// Create user using the serverClient.
await serverClient.upsertUser({
id: context.auth.uid,
name: context.auth.token.name,
email: context.auth.token.email,
image: context.auth.token.image,
});
/// Create and return user auth token.
return serverClient.createToken(context.auth.uid);
} catch (err) {
console.error(`Unable to create user with ID ${context.auth.uid} on Stream. Error ${err}`);
// Throwing an HttpsError so that the client gets the error details.
throw new functions.https.HttpsError('aborted', "Could not create Stream user");
}
}
});
```
This function first does a check to see that the client that calls it is authenticated,
by ensuring that `context.auth` is not null. If it is null, then it throws an `HttpsError` with a descriptive
message. This error can be caught in your Flutter application.
If the caller is authenticated the function proceeds to use the `serverClient` to create a new Stream Chat
user by calling the `upsertUser` method and passing in some user data. It uses the authenticated caller's **uid** as an **id**.
After the user is created it generates a token for that user. This token is then returned to the caller.
To call this from Flutter, you will need to use the `cloud_functions` package.
Update the **createAccount** method in your Flutter code to the following:
```dart
Future<void> createAccount() async {
// Create Firebase account
await auth.createUserWithEmailAndPassword(email: email, password: password);
print('Firebase account created');
// Create Stream user and get token
final callable = functions.httpsCallable('createStreamUserAndGetToken');
final results = await callable();
print('Stream account created, token: ${results.data}');
}
```
Calling this method will do the following:
1. Create a new Firebase User and authenticate that user.
2. Call the `createStreamUserAndGetToken` cloud function and get the Stream user token for the authenticated user.
As you can see, calling a cloud function is easy and will also send all the necessary user authentication information (such as the UID)
in the request.
Once you have the Stream user token, you can authenticate your Stream Chat user as you normally would.
Please see our [initialization documention](https://getstream.io/chat/docs/flutter-dart/init_and_users/?language=dart) for more information.
As you can see below, the User ID matches on both Firebase's and Stream's user database.
##### Firebase Authentication Database
![Firebase Auth Database with new user created](../assets/firebase_authentication_dashboard.jpg)
##### Stream Chat User Database
![Stream chat user database new account created](../assets/stream_chat_user_database.jpg)
### Get the Stream User Token
The `getStreamUserToken` cloud function is very similar to the `createStreamUserAndGetToken` function. The only difference is
that it only creates a user token and does not create a new user account on Stream.
Update the **signIn** method in your Flutter code to the following:
```dart
Future<void> signIn() async {
// Sign in with Firebase
await auth.signInWithEmailAndPassword(email: email, password: password);
print('Firebase signed in');
// Get Stream user token
final callable = functions.httpsCallable('getStreamUserToken');
final results = await callable();
print('Stream user token retrieved: ${results.data}');
}
```
Calling this method will do the following:
1. Sign in using Firebase Auth.
2. Call the `getStreamUserToken` cloud function to get a Stream user token.
:::note
The user needs to be authenticated to call this cloud function. Otherwise, the function will throw
the **failed-precondition** error that you specified.
:::
### Revoke Stream User Token
You may also want to revoke the Stream user token if you sign out from Firebase.
Update the `signOut` method in your Flutter code to the following:
```dart
Future<void> signOut() async {
// Revoke Stream user token.
final callable = functions.httpsCallable('revokeStreamUserToken');
await callable();
print('Stream user token revoked');
// Sign out Firebase.
await auth.signOut();
print('Firebase signed out');
}
```
:::note
Call the cloud function before signing out from Firebase.
:::
### Delete Stream User
When deleting a Firebase user account, it would make sense also to delete the
associated Stream user account.
The cloud function looks like this:
```js
// When a user is deleted from Firebase their associated Stream account is also deleted.
exports.deleteStreamUser = functions.auth.user().onDelete((user, context) => {
return serverClient.deleteUser(user.uid);
});
```
In this function, you are listening to delete events on Firebase auth. When an account is deleted, this function will be triggered, and you can get the
user's **uid** and call the `deleteUser` method on the `serverClient`.
This is not an external cloud function; it can only be triggered when an
account is deleted.
### Conclusion
In this guide, you have seen how to securely create Stream Chat tokens using
Firebase Authentication and Cloud Functions.
The principles shown in this guide can be applied to your preferred authentication
provider and cloud architecture of choice.
@@ -0,0 +1,4 @@
{
"label": "Stream Chat Flutter",
"position": 3
}
@@ -0,0 +1,84 @@
---
id: channel_header
sidebar_position: 10
title: ChannelHeader
---
A Widget To Display Common Channel Details
Find the pub.dev documentation [here](https://pub.dev/documentation/stream_chat_flutter/latest/stream_chat_flutter/ChannelHeader-class.html)
![](../assets/channel_header.png)
### Background
When a user opens a channel, it is helpful to provide context of which channel they are in. This may
be in the form of a channel name or the users in the channel. Along with that, there also needs to be
a way for the user to look at more details of the channel (media, pinned messages, actions, etc.) and
preferably also a way to navigate back to where they came from.
To encapsulate all of this functionality into one widget, the Flutter SDK contains a `ChannelHeader`
widget which provides these out of the box.
### Basic Example
Let's just add a `ChannelHeader` to a page with a `MessageListView` and a `MessageInput` to display
and send messages.
```dart
class ChannelPage extends StatelessWidget {
const ChannelPage({
Key? key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: ChannelHeader(),
body: Column(
children: <Widget>[
Expanded(
child: MessageListView(
threadBuilder: (_, parentMessage) {
return ThreadPage(
parent: parentMessage,
);
},
),
),
MessageInput(),
],
),
);
}
}
```
### Customizing Parts Of The Header
The header works like a `ListTile` widget.
Use the `title`, `subtitle`, `leading`, or `actions` parameters to substitute the widgets for your own.
```dart
//...
ChannelHeader(
title: Text('My Custom Name'),
),
```
![](../assets/channel_header_custom_title.png)
### Showing Connection State
The `ChannelHeader` can also display connection state below the tile which shows the user if they
are connected or offline, etc. on connection events.
To enable this, use the `showConnectionStateTile` property.
```dart
//...
ChannelHeader(
showConnectionStateTile: true,
),
```
@@ -0,0 +1,88 @@
---
id: channel_list_header
sidebar_position: 9
title: ChannelListHeader
---
A Header Widget For A List Of Channels
Find the pub.dev documentation [here](https://pub.dev/documentation/stream_chat_flutter/latest/stream_chat_flutter/ChannelListHeader-class.html)
![](../assets/channel_list_header.png)
### Background
A common pattern for most messaging apps is to show a list of Channels (chats) on the first screen
and navigate to an individual one on being clicked. On this first page where the list of channels are
displayed, it is usual to have functionality such as adding a new chat, display the user logged in, etc.
To encapsulate all of this functionality into one widget, the Flutter SDK contains a `ChannelListHeader`
widget which provides these out of the box.
### Basic Example
This is a basic example of a page which has a `ChannelListView` and a `ChannelListHeader` to recreate a
common Channels Page.
```dart
class DemoPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: ChannelListHeader(),
body: ChannelsBloc(
child: ChannelListView(
filter: Filter.in_('members', [StreamChat.of(context).user.id]),
sort: [SortOption('last_message_at')],
pagination: PaginationParams(
limit: 20,
),
channelWidget: ChannelPage(),
),
),
);
}
}
```
### Customizing Parts Of The Header
The header works like a `ListTile` widget.
Use the `titleBuilder`, `subtitle`, `leading`, or `actions` parameters to substitute the widgets for your own.
```dart
//...
ChannelListHeader(
subtitle: Text('My Custom Subtitle'),
),
```
![](../assets/channel_list_header_custom_subtitle.png)
The `titleBuilder` param helps you build different titles depending on the connection state:
```dart
//...
ChannelListHeader(
titleBuilder: (context, status, client) {
switch(status) {
/// Return your title widget
}
},
),
```
### Showing Connection State
The `ChannelListHeader` can also display connection state below the tile which shows the user if they
are connected or offline, etc. on connection events.
To enable this, use the `showConnectionStateTile` property.
```dart
//...
ChannelListHeader(
showConnectionStateTile: true,
),
```
@@ -0,0 +1,111 @@
---
id: channel_list_view
sidebar_position: 4
title: ChannelListView
---
A Widget For Displaying A List Of Channels
Find the pub.dev documentation [here](https://pub.dev/documentation/stream_chat_flutter/latest/stream_chat_flutter/ChannelListView-class.html)
![](../assets/channel_list_view.png)
### Background
Channels are fundamental elements of Stream Chat and constitute shared spaces which allow users to
message each other.
1:1 conversations and groups are both examples of channels, albeit with some (distinct/non-distinct)
differences. Displaying the list of channels that a user is a part of is a pattern present in most messaging apps.
The `ChannelListView` widget allows displaying a list of channels to a user. By default, this is NOT
ONLY the channels that the user is a part of. This section goes into setting up and using a `ChannelListView`
widget.
### Basic Example
Here is a basic example of the `ChannelListView` widget. It consists of the main widget itself, a `Filter`
to filter only the channels that the user is a part of, sorting by last message time, pagination params,
and the widget to use when a particular channel is clicked.
```dart
class ChannelListPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: ChannelsBloc(
child: ChannelListView(
filter: Filter.in_('members', [StreamChat.of(context).user.id]),
sort: [SortOption('last_message_at')],
pagination: PaginationParams(
limit: 20,
),
channelWidget: ChannelPage(),
),
),
);
}
}
```
This example by default displays the channels that a user is a part of. Now let's look at customizing
the widget.
### Customizing the Channel Preview
A common aspect of the widget needed to be tweaked according to each app is the Channel Preview (the
Channel tile in the list). To do this, we use the `channelPreviewBuilder` param like this:
```dart
ChannelListView(
...
channelPreviewBuilder: (context, channel) {
return ListTile(
tileColor: Colors.amberAccent,
title: Center(
child: ChannelName(),
),
);
},
),
```
Which gives you a new Channel preview in the list:
![](../assets/channel_preview.png)
### Adding Swipe Actions
To add actions (such as delete, more info, etc) when Channel preview is swiped left, set the `swipeToAction`
parameter to `true`.
```dart
ChannelListView(
...
swipeToAction: true,
),
```
This adds two basic actions - info and delete:
![](../assets/swipe_channel.png)
To add custom actions of your own, use the `swipeActions` param:
```dart
ChannelListView(
...
swipeToAction: true,
swipeActions: [
SwipeAction(
color: Colors.blue,
iconWidget: Icon(Icons.add),
onTap: (channel) {
// Things to do on icon tap
},
),
// Other actions here
]
),
```
@@ -0,0 +1,18 @@
---
id: introduction
sidebar_position: 1
title: Introduction
---
Understanding The UI Package Of The Flutter SDK
### What function does `stream_chat_flutter` serve?
The UI SDK (`stream_chat_flutter`) contains official Flutter components for Stream Chat, a service for building chat applications.
While the Stream Chat service provides the backend for messaging and the LLC provides an easy way to
use it in your Flutter apps, we wanted to make sure that adding Chat functionality to your app was as quick as possible.
The UI package is built on top of the low-level client and the core package and allows you to build a
full fledged app with either the inbuilt components, modify existing components, or easily add widgets
of your own to match your app's style better.
@@ -0,0 +1,170 @@
---
id: message_input
sidebar_position: 6
title: MessageInput
---
A Widget Dealing With Everything Related To Sending A Message
Find the pub.dev documentation [here](https://pub.dev/documentation/stream_chat_flutter/latest/stream_chat_flutter/MessageInput-class.html)
![](../assets/message_input.png)
### Background
In Stream Chat, we can send messages in a channel. However, sending a message isn't as simple as adding
a `TextField` and logic for sending a message. It involves additional processes like addition of media,
quoting a message, adding a custom command like a GIF board, and much more. Moreover, most apps also
need to customize the input to match their theme, overall color and structure pattern, etc.
To do this, we created a `MessageInput` widget which abstracts all expected functionality a modern input
needs - and allows you to use it out of the box.
### Basic Example
A `StreamChannel` is required above the widget tree in which the `MessageInput` is rendered since the channel is
where the messages sent actually go. Let's look at a common example of how we could use the `MessageInput`:
```dart
class ChannelPage extends StatelessWidget {
const ChannelPage({
Key key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: ChannelHeader(),
body: Column(
children: <Widget>[
Expanded(
child: MessageListView(
threadBuilder: (_, parentMessage) {
return ThreadPage(
parent: parentMessage,
);
},
),
),
MessageInput(),
],
),
);
}
}
```
It is common to put this widget in the same page of a `MessageListView` as the bottom widget.
### Quoting A Message
The quoting functionality allows us to 'reply' to a specific message without creating a thread out of it.
It adds the other message as context when sending a message and also displays it above the sent message.
To quote a message, we provide a `quotedMessage` to the `MessageInput`.
```dart
Message? message;
// ...
MessageInput(
quotedMessage: message,
),
```
This will add the message given above the message about to be sent.
While you can implement your own functionality to select which message to reply to, the `MessageListView`
widget helps in this case since it has an inbuilt `onMessageSwiped` callback which we can use.
```dart
class ChannelPage extends StatefulWidget {
@override
_ChannelPageState createState() => _ChannelPageState();
}
class _ChannelPageState extends State<ChannelPage> {
Message? quotedMessage;
@override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: <Widget>[
Expanded(
child: MessageListView(
// ...
onMessageSwiped: (message) {
setState(() {
quotedMessage = message;
});
},
),
),
MessageInput(
quotedMessage: _quotedMessage,
onQuotedMessageCleared: () {
setState(() => _quotedMessage = null);
},
),
],
),
);
}
}
```
![](../assets/message_input_quoted_message.png)
### Adding Custom Actions
By default, the `MessageInput` has two actions: one for attachments and one for commands like Giphy.
To add your own action, we use the `actions` parameter like this:
```dart
MessageInput(
actions: [
InkWell(
child: Icon(
Icons.location_on,
size: 20.0,
color: StreamChatTheme.of(context).colorTheme.grey,
),
onTap: () {
// Do something here
},
),
],
),
```
This will add on your action to the existing ones.
### Disable Attachments
To disable attachments being added to the message, set the `disableAttachments` parameter to true.
```dart
MessageInput(
disableAttachments: true,
),
```
### Changing Position Of MessageInput Components
You can also change the position of the TextField, actions and 'send' button relative to each other.
To do this, use the `actionsLocation` or `sendButtonLocation` parameters which help you decide the location
of the buttons in the input.
For example, if we want the actions on the right and the send button inside the TextField, we can do:
```dart
MessageInput(
sendButtonLocation: SendButtonLocation.inside,
actionsLocation: ActionsLocation.right,
),
```
![](../assets/message_input_change_position.png)
@@ -0,0 +1,115 @@
---
id: message_list_view
sidebar_position: 5
title: MessageListView
---
A Widget For Displaying A List Of Messages
Find the pub.dev documentation [here](https://pub.dev/documentation/stream_chat_flutter/latest/stream_chat_flutter/MessageListView-class.html)
![](../assets/message_list_view.png)
### Background
Every channel can contain a list of messages sent by users inside it. The `MessageListView` widget
displays the list of messages inside a particular channel along with possible attachments and
other message attributes (if the message is pinned for example). This sets it apart from the `MessageSearchListView`
which may not contain messages only from a single channel and is used to search for messages across
many.
### Basic Example
The `MessageListView` shows the list of messages of the current channel. It has inbuilt support for
common messaging functionality: displaying and editing messages, adding / modifying reactions, support
for quoting messages, pinning messages, and more.
An example of how you can use the MessageListView is:
```dart
class ChannelPage extends StatelessWidget {
const ChannelPage({
Key key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: ChannelHeader(),
body: Column(
children: <Widget>[
Expanded(
child: MessageListView(
threadBuilder: (_, parentMessage) {
return ThreadPage(
parent: parentMessage,
);
},
),
),
MessageInput(),
],
),
);
}
}
```
### Enable Threads
Threads are made of a parent message and replies linked to it. To enable threading, the SDK requires you
to supply a `threadBuilder` which will supply the page when the thread is clicked.
```dart
MessageListView(
threadBuilder: (_, parentMessage) {
return ThreadPage(
parent: parentMessage,
);
},
),
```
![](../assets/message_list_view_threads.png)
The `MessageListView` itself can render the thread by supplying the `parentMessage` parameter.
```dart
MessageListView(
parentMessage: parent,
),
```
### Building Custom Messages
You can also supply your own implementation for displaying messages using the `messageBuilder` parameter.
:::note
To customize the existing implementation, look at the `MessageWidget` documentation instead.
:::
```dart
MessageListView(
messageBuilder: (context, details, messageList, defaultImpl) {
// Your implementation of the message here
// E.g: return Text(details.message.text ?? '');
},
),
```
### Enabling Message Pinning
Message pins save and highlight the message in the `MessageListView`. To enable users to pin the message,
make sure the pin permissions are granted for different types of users on the dashboard. After confirming
the appropriate users have permissions, add the user types in the `pinPermissions` parameter.
```dart
MessageListView(
//...
pinPermissions: ['admin', 'userType1', 'userType2'],
),
```
This will allow these user types to pin messages through the message actions modal.
![](../assets/message_list_view_pin.png)
@@ -0,0 +1,62 @@
---
id: message_search_list_view
sidebar_position: 8
title: MessageSearchListView
---
A Widget To Search For Messages Across Channels
Find the pub.dev documentation [here](https://pub.dev/documentation/stream_chat_flutter/latest/stream_chat_flutter/MessageSearchListView-class.html)
![](../assets/message_search_list_view.png)
### Background
Users in Stream Chat can have several channels and it can get hard to remember which channel has the
message they are searching for. As such, there needs to be a way to search for a message across multiple
channels. This is where `MessageSearchListView` comes in.
### Basic Example
While the MessageListView is tied to a certain `StreamChannel`, a `MessageSearchListView` is not.
```dart
class MessageSearchPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: MessageSearchBloc(
child: MessageSearchListView(
filters: Filter.in_('members', [StreamChat.of(context).user!.id],),
messageQuery: 'your query here',
limit: 20,
),
),
);
}
}
```
### Customize The Result Tiles
You can use your own widget for the result items using the `itemBuilder` parameter.
```dart
MessageSearchListView(
// ...
itemBuilder: (context, response) {
return Text(response.message.text);
},
),
```
### Show Result Count
You show the number of results via the `showResultCount` parameter.
```dart
MessageSearchListView(
// ...
showResultCount: true,
),
```
@@ -0,0 +1,98 @@
---
id: message_widget
sidebar_position: 11
title: MessageWidget
---
A Widget For Displaying Messages And Attachments
Find the pub.dev documentation [here](https://pub.dev/documentation/stream_chat_flutter/latest/stream_chat_flutter/MessageWidget-class.html)
### Background
There are several things that need to be displayed with text in a message in a modern messaging app:
attachments, highlights if the message is pinned, user avatars of the sender, etc.
To encapsulate all of this functionality into one widget, the Flutter SDK contains a `MessageWidget`
widget which provides these out of the box.
### Basic Example (Modifying `MessageWidget` in `MessageListView`)
Primarily, the `MessageWidget` is used in the `MessageListView`. To customize only a few properties
of the `MessageWidget` without supplying all other properties, the `messageBuilder` builder supplies
a default implementation of the widget for us to modify.
```dart
class ChannelPage extends StatelessWidget {
const ChannelPage({
Key key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
body: MessageListView(
messageBuilder: (context, details, messageList, defaultMessageWidget) {
return defaultMessageWidget.copyWith(
showThreadReplyIndicator: false,
);
},
),
);
}
}
```
### Building A Custom Attachment
When a custom attachment type (location, audio, etc.) is sent, the MessageWidget also needs to know
how to build it. For this purpose, we can use the `customAttachmentBuilders` parameter.
As an example, if a message has a attachment type 'location', we do:
```dart
MessageWidget(
//...
customAttachmentBuilders: {
'location': (context, message, attachments) {
var attachmentWidget = Image.network(
_buildMapAttachment(
attachments[0].extraData['latitude'],
attachments[0].extraData['longitude'],
),
);
return wrapAttachmentWidget(context, attachmentWidget, null, true, BorderRadius.circular(8.0));
}
},
)
```
You can also override the builder for existing attachment types like `image` and `video`.
### Show User Avatar For Messages
You can decide to show, hide, or remove user avatars of the sender of the message. To do this, set
the `showUserAvatar` property like this:
```dart
MessageWidget(
//...
showUserAvatar = DisplayWidget.show,
)
```
### Reverse the message
In most cases, `MessageWidget` needs to be a different orientation depending upon if the sender is the
user or someone else.
For this, we use the `reverse` parameter to change the orientation of the message:
```dart
MessageWidget(
//...
reverse = true,
)
```
@@ -0,0 +1,48 @@
---
id: setup
sidebar_position: 2
title: Setup
---
Understanding Setup For `stream_chat_flutter`
### Add pub.dev dependency
First, you need to add the `stream_chat_flutter` dependency to your `pubspec.yaml`.
You can either run this command:
```shell
flutter pub add stream_chat_flutter
```
OR
Add this line in the dependencies section of your pubspec.yaml after substituting latest version:
```yaml
dependencies:
stream_chat_flutter: ^latest_version
```
You can find the package details on [pub.dev](https://pub.dev/packages/stream_chat_flutter).
### Details On Platform Support
`stream_chat_flutter` was originally created for Android and iOS mobile platforms. As Flutter matured,
support for additional platforms was added and the package now has experimental support for web and desktop as
[detailed here](https://getstream.io/blog/announcing-experimental-multi-platform-support-for-the-stream-flutter-sdk/).
However, platforms other than mobile may have additional constraints due to not supporting all plugins,
which will be addressed by the respective plugin creators over time.
### Setup: iOS
The library uses [flutter file picker plugin](https://github.com/miguelpruivo/flutter_file_picker) to pick files from the os.
Follow [this wiki](https://github.com/miguelpruivo/flutter_file_picker/wiki/Setup#ios) to fulfill iOS requirements.
We also use [video_player](https://pub.dev/packages/video_player) to reproduce videos.
Follow [this guide](https://pub.dev/packages/video_player#installation) to fulfill the requirements.
To pick images from the camera, we use the [image_picker](https://pub.dev/packages/image_picker) plugin.
Follow [these instructions](https://pub.dev/packages/image_picker#ios) to check the requirements.
@@ -0,0 +1,71 @@
---
id: stream_chat_and_theming
sidebar_position: 3
title: StreamChat And Theming
---
Understanding How To Customize Widgets Using `StreamChatTheme`
Find the pub.dev documentation [here](https://pub.dev/documentation/stream_chat_flutter/latest/stream_chat_flutter/StreamChatTheme-class.html) and [here](https://pub.dev/documentation/stream_chat_flutter/latest/stream_chat_flutter/StreamChatThemeData-class.html)
### Background
Stream's UI SDK makes it easy for developers to add custom styles and attributes to our widgets. Like most Flutter frameworks, Stream exposes a dedicated widget for theming.
Using `StreamChatTheme`, users can customize most aspects of our UI widgets by setting attributes using `StreamChatThemeData`.
Similar to the `Theme` and `ThemeData` in Flutter, Stream Chat uses a top level [inherited widget](https://api.flutter.dev/flutter/widgets/InheritedWidget-class.html) to provide theming information throughout your application. This can be optionally set at the top of your application tree or at a localized point in your widget sub-tree.
If you'd like to customize the look and feel of Stream chat across your entire application, we recommend setting your theme at the top level. Conversely, users can customize specific screens or widgets by wrapping components in a `StreamChatTheme`.
### A closer look at StreamChatThemeData
Looking at the constructor for `StreamChatThemeData`, we can see the full list of properties and widgets available for customization.
Some high-level properties such as `textTheme` or `colorTheme` can be set application-wide directly from this class. In contrast, larger components such as `ChannelHeader`, `MessageInputs`, etc. have been broken up into smaller theme objects.
```dart
factory StreamChatThemeData({
Brightness? brightness,
TextTheme? textTheme,
ColorTheme? colorTheme,
ChannelListHeaderTheme? channelListHeaderTheme,
ChannelPreviewTheme? channelPreviewTheme,
ChannelTheme? channelTheme,
MessageTheme? otherMessageTheme,
MessageTheme? ownMessageTheme,
MessageInputTheme? messageInputTheme,
Widget Function(BuildContext, Channel)? defaultChannelImage,
Widget Function(BuildContext, User)? defaultUserImage,
IconThemeData? primaryIconTheme,
List<ReactionIcon>? reactionIcons,
});
```
### Stream Chat Theme in use
Let's take a look at customizing widgets using `StreamChatTheme`. In the example below, we can change the default color theme to yellow and override the channel header's typography and colors.
```dart
builder: (context, child) => StreamChat(
client: client,
child: child,
streamChatThemeData: StreamChatThemeData(
colorTheme: ColorTheme.light(
primaryAccent: const Color(0xffffe072),
),
channelTheme: ChannelTheme(
channelHeaderTheme: ChannelHeaderTheme(
color: const Color(0xffd34646),
title: TextStyle(
color: Colors.white,
),
),
),
),
),
```
We are creating this class at the very top of our widget tree using the `streamChatThemeData` parameter found in the `StreamChat` widget.
![](../assets/using_theme.jpg)
@@ -0,0 +1,88 @@
---
id: user_list_view
sidebar_position: 7
title: UserListView
---
A Widget For Displaying And Selecting Users
Find the pub.dev documentation [here](https://pub.dev/documentation/stream_chat_flutter/latest/stream_chat_flutter/UserListView-class.html)
![](../assets/user_list_view.png)
### Background
A list of users is required for many different purposes: showing a list of users in a Channel,
selecting users to add in a channel, etc. The `UserListView` displays and allows selection of a list
of users along with multiple display configurations like a list and grid.
### Basic Example
Let's take a look at an example where we use the widget to autocomplete user names:
```dart
class UsersListPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: UsersBloc(
child: UsersListView(
filter: Filter.notEqual('id', StreamChat.of(context).user!.id),
sort: [
SortOption(
'name',
direction: 1,
),
],
pagination: PaginationParams(
limit: 25,
),
),
),
);
}
}
```
### Customize The User Items
You can use your own widget for the user items using the `userItemBuilder` parameter.
```dart
UsersListView(
// ...
userItemBuilder: (context, user, isSelected) {
return Text(user.name);
},
),
```
### Group Alphabetically
You can group alphabetically using the `groupAlphabetically` parameter:
```dart
UsersListView(
//...
groupAlphabetically: true,
),
```
### Selecting Users
The `UserListView` widget allows selecting users in a list by supplying a selected users list and callbacks
for when user items are tapped.
```dart
Set<User>? selectedUsers = {};
UsersListView(
//...
selectedUsers: selectedUsers,
onUserTap: (user, _) {
setState(() {
selectedUsers.add(user);
});
},
),
```
@@ -0,0 +1,4 @@
{
"label": "Stream Chat Flutter Core",
"position": 4
}
@@ -0,0 +1,68 @@
---
id: channel_list_core
sidebar_position: 4
title: ChannelListCore
---
A Widget For Building A List Of Channels
### Background
The UI SDK of Stream Chat supplies a `ChannelListView` class that builds a list of channels fetching
according to the filters and sort order given. However, in some cases, implementing novel UI is necessary
that cannot be done using the customization approaches given in the widget.
To do this, we extracted the logic required for fetching channels into a 'Core' widget - a widget that
fetches channels in the expected way via the usual params but does not supply any UI and instead
exposes builders to build the UI in situations such as loading, empty data, errors, and on data received.
### Basic Example
`ChannelListCore` is a simplified class that allows fetching a list of
channels while exposing UI builders.
This allows you to construct your own UI while not having to
worry about the specific logic of fetching channels in your app.
A `ChannelListController` is used to reload and paginate data.
```dart
class ChannelListPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: ChannelListCore(
filter: Filter.in_(
'members',
[StreamChat.of(context).user!.id],
),
sort: [SortOption('last_message_at')],
pagination: PaginationParams(
limit: 20,
),
errorBuilder: (context, err) {
return Center(
child: Text('An error has occured'),
);
},
emptyBuilder: (context) {
return Center(
child: Text('Nothing here...'),
);
},
loadingBuilder: (context) {
return Center(
child: CircularProgressIndicator(),
);
},
listBuilder: (context, list) {
return ChannelPage(list);
}
),
);
}
}
```
Make sure to have a `StreamChatCore` ancestor in order to provide the
information about the channels.
@@ -0,0 +1,90 @@
---
id: channels_bloc
sidebar_position: 7
title: ChannelsBloc
---
A Widget Dedicated To The Management Of A Channel List With Pagination.
### Background
Most widgets in the Core SDK are focused on fetching a particular type of object from Stream Chat - channels,
messages, users, etc. The BLoC widgets bundle up the base functions used to fetch data as well as the current
data fetched by the respective functions. Furthermore, the Core widgets use this BLoC to fetch new or
existing data and build UI based on it.
All Core and UI widgets which focus on fetching a list of objects need to have their respective functions
above them in the widget tree. The ChannelListCore and ChannelListView require the ChannelsBloc
above them in the widget hierarchy without which they will fail.
### Understanding The Widget
`ChannelsBloc` is used together with `ChannelListCore` to manage a list of
Channels with pagination, re-ordering, querying and other operations
associated with Channels.
`ChannelsBloc` can be accessed at anytime by using the static `.of` method
using Flutter's `BuildContext`.
```dart
var _channelsBloc = ChannelsBloc.of(context);
```
The `ChannelsBloc` widget encapsulates common functionality related to channel lists such as fetching
the existing channels and querying new channels and also supplies them down the widget tree.
The widget is required for the respective core widget (`ChannelListCore`) to fetch channels and hence
must be above the core widget in the tree.
Here is a basic implementation of `ChannelsBloc`:
```dart
ChannelsBloc(
child: // Further Widget Tree
),
```
The `ChannelsBloc` widget allows three customisations:
#### Lock Channels Order
ChannelsBloc may change the order of channels when new messages arrive. To lock this order, we can
set the `lockChannelsOrder` property to true.
```dart
ChannelsBloc(
lockChannelsOrder: true,
child: // Further Widget Tree
),
```
#### Set custom channel order
We can decide the order of the channels in the list by supplying a comparator to the `channelsComparator`
parameter:
```dart
ChannelsBloc(
channelsComparator: (a, b) {
return a.createdAt!.millisecondsSinceEpoch >
b.createdAt!.millisecondsSinceEpoch
? 1
: -1;
},
child: // Further Widget Tree
),
```
#### Decide if channel should be added on new message event
When a new message arrives, a `message.new` event is received. We can decide if we want to add the channel
to the list using the `shouldAddChannel` parameter which is a callback supplying the event data:
```dart
ChannelsBloc(
shouldAddChannel: (event) {
return event.message!.extraData['priority'] == '1';
},
child: // Further Widget Tree
),
```
@@ -0,0 +1,80 @@
---
id: introduction
sidebar_position: 1
title: Introduction
---
Understanding The Core Package Of The Flutter SDK
This package provides business logic to fetch common things required for integrating Stream Chat into your application.
The core package allows more customisation and hence provides business logic but no UI components.
Please use the `stream_chat_flutter` package for the full fledged suite of UI components or `stream_chat` for the low-level client.
### Background
In the early days of the Flutter SDK, the SDK was only split into the LLC (`stream_chat`) and
the UI package (`stream_chat_flutter`). With this you could use a fully built interface with the UI package
or a fully custom interface with the LLC. However, we soon recognised the need for a third intermediary
package which made tasks like building and modifying a list of channels or messages easy but without
the complexity of using low level components. The Core package (`stream_chat_flutter_core`) is a manifestation
of the same idea and allows you to build an interface with Stream Chat without having to deal with
low level code and architecture as well as implementing your own theme and UI effortlessly.
Also, it has very few dependencies.
We will now explore the components of this intermediary package and understand how it helps you build
the experience you want your users to have.
The package primarily contains three types of classes:
* Business Logic Components
* Core Components
* Core Controllers
### Business Logic Components
These components allow you to have the maximum and lower-level control of the queries being executed.
In BLoCs, the basic functionalities - such as queries for messages, channels or queries - are bundled up
and passed along down the tree. Using a BLoC allows you to either create your own way to fetch and
build UIs or use an inbuilt Core widget to do the work such as queries, pagination, etc for you.
The BLoCs we provide are:
* ChannelsBloc
* MessageSearchBloc
* UsersBloc
### Core Components
Core components usually are an easy way to fetch data associated with Stream Chat.
Core components use functions exposed by the respective BLoCs (for example the ChannelListCore uses the ChannelsBloc)
and use the respective controllers for various operations. Unlike heavier components from the UI
package, core components are decoupled from UI and they expose builders instead to help you build
a fully custom interface.
Data fetching can be controlled with the controllers of the respective core components.
* ChannelListCore (Fetch a list of channels)
* MessageListCore (Fetch a list of messages from a channel)
* MessageSearchListCore (Fetch a list of search messages)
* UserListCore (Fetch a list of users)
* StreamChatCore (This is different from the other core components - it is a version of StreamChat decoupled from theme and initialisations.)
### Core Controllers
Core Controllers are supplied to respective CoreList widgets which allows reloading and pagination of data whenever needed.
Unlike the UI package, the Core package allows a fully custom user interface built with the data. This
in turn provides a few challenges: we do not know implicitly when to paginate your list or reload your data.
While this is handled out of the box in the UI package since the List implementation is inbuilt, a controller
needs to be used in the core package notifying the core components to reload or paginate the data existing
currently. For this, each core component has a respective controller which you can use to call the
specific function (reload / paginate) whenever such an event is triggered through / needed in your UI.
* ChannelListController
* MessageListController
* MessageSearchListController
* ChannelListController
This section goes into the individual core package widgets and their functional use.
@@ -0,0 +1,70 @@
---
id: message_list_core
sidebar_position: 5
title: MessageListCore
---
A Widget For Building A List Of Messages
### Background
The UI SDK of Stream Chat supplies a `MessageListView` class that builds a list of channels fetching
according to the filters and sort order given. However, in some cases, implementing novel UI is necessary
that cannot be done using the customization approaches given in the widget.
To do this, we extracted the logic required for fetching channels into a 'Core' widget - a widget that
fetches channels in the expected way via the usual params but does not supply any UI and instead
exposes builders to build the UI in situations such as loading, empty data, errors, and on data received.
### Basic Example
`MessageListCore` is a simplified class that allows fetching a list of
messages while exposing UI builders.
This allows you to construct your own UI while not having to
worry about the specific logic of fetching messages in a channel.
A `MessageListController` is used to paginate data.
```dart
class ChannelPage extends StatelessWidget {
const ChannelPage({
Key key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: <Widget>[
Expanded(
child: MessageListCore(
emptyBuilder: (context) {
return Center(
child: Text('Nothing here...'),
);
},
loadingBuilder: (context) {
return Center(
child: CircularProgressIndicator(),
);
},
messageListBuilder: (context, list) {
return MessagesPage(list);
},
errorWidgetBuilder: (context, err) {
return Center(
child: Text('Error'),
);
},
),
),
],
),
);
}
}
```
Make sure to have a `StreamChannel` ancestor in order to provide the
information about the channels.
@@ -0,0 +1,40 @@
---
id: message_search_list_block
sidebar_position: 8
title: MessageSearchListBloc
---
A Widget Used To Manage A List Of Messages With Pagination.
### Background
Most widgets in the Core SDK are focused on fetching a particular type of object from Stream Chat - channels,
messages, users etc. The BLoC widgets bundle up the base functions used to fetch data as well as the current
data fetched by the respective functions. Furthermore, the Core widgets use this BLoC to fetch new or
existing data and build UI based on it.
All Core and UI widgets which focus on fetching a list of objects need to have their respective functions
above them in the widget tree. The MessageSearchListCore and MessageSearchListView require the
MessageSearchListCore above them in the widget hierarchy without which they will fail.
### Understanding The Widget
This class can be used to load messages, perform queries, etc.
`MessageSearchBloc` can be accessed at anytime by using the static `.of` method
using Flutter's BuildContext.
```dart
var _searchBloc = MessageSearchBloc.of(context);
```
The `MessageSearchBloc` widget encapsulates common functionality related to searching for messages
across channels and also supplies them down the widget tree.
Here is a basic implementation of `ChannelsBloc`:
```dart
MessageSearchBloc(
child: // Further Widget Tree
),
```
@@ -0,0 +1,41 @@
---
id: message_search_list_core
sidebar_position: 6
title: MessageSearchListCore
---
A Widget For Displaying Message Searches
### Background
The UI SDK of Stream Chat supplies a `MessageSearchListView` class that builds a list of channels fetching
according to the filters and sort order given. However, in some cases, implementing novel UI is necessary
that cannot be done using the customization approaches given in the widget.
To do this, we extracted the logic required for fetching channels into a 'Core' widget - a widget that
fetches channels in the expected way via the usual params but does not supply any UI and instead
exposes builders to build the UI in situations such as loading, empty data, errors, and on data received.
### Basic Example
`MessageSearchListCore` is a simplified class that allows searching for
messages across channels while exposing UI builders.
A `MessageSearchListController` is used to load and paginate data.
```dart
class MessageSearchPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: MessageSearchListCore(
messageQuery: _messageFilter,
filters: _channelsFilter,
limit: 20,
),
);
}
}
```
Make sure to have a `MessageSearchBloc` ancestor in order to provide the
information about the messages.
@@ -0,0 +1,28 @@
---
id: setup
sidebar_position: 2
title: Setup
---
Understanding Setup For `stream_chat_flutter_core`
### Add pub.dev dependency
First, you need to add the `stream_chat_flutter_core` dependency to your pubspec.yaml
You can either run this command:
```shell
flutter pub add stream_chat_flutter_core
```
OR
Add this line in the dependencies section of your pubspec.yaml after substituting latest version:
```yaml
dependencies:
stream_chat_flutter_core: ^latest_version
```
You can find the package details on [pub.dev](https://pub.dev/packages/stream_chat_flutter_core).
@@ -0,0 +1,26 @@
---
id: stream_chat_core
sidebar_position: 3
title: StreamChatCore
---
`StreamChatCore` is a version of `StreamChat` found in `stream_chat_flutter` that is decoupled from
theme and initialisations.
`StreamChatCore` is used to provide information about the chat client to the widget tree.
This Widget is used to react to life cycle changes and system updates.
When the app goes into the background, the websocket connection is automatically closed and when it goes back to foreground the connection is opened again.
Like the `StreamChat` widget in the higher level UI package, the `StreamChatCore` widget should
be on the top level before using any Stream functionality:
```dart
return MaterialApp(
title: 'Stream Chat Core Example',
home: HomeScreen(),
builder: (context, child) => StreamChatCore(
client: client,
child: child!,
),
);
```
@@ -0,0 +1,64 @@
---
id: user_list_core
sidebar_position: 10
title: UserListCore
---
A Widget For Building A List Of Users
### Background
The UI SDK of Stream Chat supplies a `UserListView` class that builds a list of channels fetching
according to the filters and sort order given. However, in some cases, implementing novel UI is necessary
that cannot be done using the customization approaches given in the widget.
To do this, we extracted the logic required for fetching channels into a 'Core' widget - a widget that
fetches channels in the expected way via the usual params but does not supply any UI and instead
exposes builders to build the UI in situations such as loading, empty data, errors, and on data received.
### Basic Example
`UserListCore` is a simplified class that allows fetching users while
exposing UI builders.
A `UserListController` is used to load and paginate data.
```dart
class UsersListPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: UsersListCore(
sort: [SortOption('last_active')],
pagination: PaginationParams(
limit: 20,
),
errorBuilder: (err) {
return Center(
child: Text('An error has occured'),
);
},
emptyBuilder: (context) {
return Center(
child: Text('Nothing here...'),
);
},
emptyBuilder: (context) {
return Center(
child: CircularProgressIndicator(),
);
},
listBuilder: (context, list) {
return UsersPage(list);
}
),
);
}
}
```
`UsersBloc` must be the ancestor of this widget. This is necessary since
`UserListCore` depends on functionality contained within `UsersBloc`.
The parameters `listBuilder`, `loadingBuilder`, `emptyBuilder` and
`errorBuilder` must all be supplied and not null.
@@ -0,0 +1,37 @@
---
id: users_bloc
sidebar_position: 9
title: UsersBloc
---
A Widget Dedicated To The Management Of A Users List With Pagination.
### Background
Most widgets in the Core SDK are focused on fetching a particular type of object from Stream Chat - channels,
messages, users, etc. The BLoC widgets bundle up the base functions used to fetch data as well as the current
data fetched by the respective functions. Furthermore, the Core widgets use this BLoC to fetch new or
existing data and build UI based on it.
All Core and UI widgets which focus on fetching a list of objects need to have their respective functions
above them in the widget tree. The UserListCore and UserListView require the UserListCore
above them in the widget hierarchy without which they will fail.
### Understanding The Widget
`UsersBloc` can be accessed at anytime by using the static `.of` method
using Flutter's `BuildContext`.
```dart
var _userBloc_ = UsersBloc.of(context);
```
The `UsersBloc` widget encapsulates common functionality related to user lists and also supplies them down the widget tree.
Here is a basic implementation of `UsersBloc`:
```dart
UsersBloc(
child: // Further Widget Tree
),
```