Docs: reorganize sidebar docs + fix broken links (#1369)

* reorganize sidebar docs

* fix broken links

* tweaks
This commit is contained in:
Sacha Arbonel
2022-10-31 15:06:21 +01:00
committed by Salvatore Giordano
parent 5db668f7d2
commit d0e06d96d8
66 changed files with 110 additions and 110 deletions
@@ -1,4 +0,0 @@
{
"label": "Guides",
"position": 2
}
@@ -1,98 +0,0 @@
---
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: StreamMessageListView(),
),
StreamMessageInput(),
],
),
),
],
),
)
```
### 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
Stack(
children: <Widget>[
// Add your video implementation here
ShaderMask(
shaderCallback: (rect) {
return const LinearGradient(
begin: Alignment.bottomCenter,
end: Alignment.topCenter,
colors: [Colors.black, Colors.transparent],
stops: [0.4, 0.8]).createShader(
Rect.fromLTRB(0, 0, rect.width, rect.height),
);
},
blendMode: BlendMode.dstIn,
child: Column(
children: const [
Expanded(
child: StreamMessageListViewTheme(
data: StreamMessageListViewThemeData(
backgroundColor: Colors.transparent,
),
child: StreamMessageListView(),
),
),
StreamMessageInput(),
],
),
),
],
),
```
@@ -1,267 +0,0 @@
---
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 `StreamMessageInput`
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 StreamMessageInput 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 `StreamMessageListView`.
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
StreamMessageInput(
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 `StreamMessageListView` and tell it how to build a location attachment, using the `messageBuilder` property and copying the default message implementation overriding the `customAttachmentBuilders` property:
```dart
StreamMessageListView(
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(
attachmentWidget: attachmentWidget,
attachmentShape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
);
}
},
);
},
),
```
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 `StreamMessageInput`
First, we add the attachment when the location button is clicked:
```dart
StreamMessageInputController _messageInputController = StreamMessageInputController();
StreamMessageInput(
messageInputController: _messageInputController,
actions: [
InkWell(
child: Icon(
Icons.location_on,
size: 20.0,
color: StreamChatTheme.of(context).colorTheme.grey,
),
onTap: () {
_determinePosition().then((value) {
_messageInputController.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
StreamMessageInput(
messageInputController: _messageInputController,
actions: [
InkWell(
child: Icon(
Icons.location_on,
size: 20.0,
color: StreamChatTheme.of(context).colorTheme.grey,
),
onTap: () {
_determinePosition().then((value) {
_messageInputController.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 StreamMessageInput:
![](../assets/location_sharing_example_message_thumbnail.jpg)
@@ -1,76 +0,0 @@
---
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.
@@ -1,200 +0,0 @@
---
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)
- [Portuguese](https://github.com/GetStream/stream-chat-flutter/blob/master/packages/stream_chat_localizations/lib/src/stream_chat_localizations_pt.dart)
- [German](https://github.com/GetStream/stream-chat-flutter/blob/master/packages/stream_chat_localizations/lib/src/stream_chat_localizations_de.dart)
- [Norwegian](https://github.com/GetStream/stream-chat-flutter/blob/master/packages/stream_chat_localizations/lib/src/stream_chat_localizations_no.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'),
Locale('pt'),
Locale('de'),
Locale('no'),
],
// 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>hi</string>
<string>fr</string>
<string>it</string>
<string>es</string>
<string>ja</string>
<string>ko</string>
<string>pt</string>
<string>de</string>
<string>no</string>
</array>
```
@@ -1,263 +0,0 @@
---
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 [email protected]: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();
}
}
```
@@ -1,309 +0,0 @@
---
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);
});
```
Push Notifications v2 also supports specifying a name to the push device tokens you register. By setting the optional `pushProviderName` param in the `addDevice` call you can support different configurations between the device and the `PushProvider`.
```dart
firebaseMessaging.onTokenRefresh.listen((token) {
client.addDevice(token, PushProvider.firebase, pushProviderName: 'my-custom-config');
});
```
### 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 [email protected]: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);
```
@@ -1,118 +0,0 @@
---
id: autocomplete_triggers
title: Adding Custom Autocomplete Triggers
---
Adding Custom Autocomplete Triggers
### Introduction
The [StreamMessageInput](../stream_chat_flutter/message_input.mdx) widget provides a way to add custom autocomplete triggers using the `StreamMessageInput.customAutocompleteTriggers` property.
By default we provide autocomplete triggers for mentions and commands, but it's very easy to add your custom ones.
### Add Emoji Autocomplete Trigger
To add a custom emoji autocomplete trigger, you must first create an `AutoCompleteOptions` widget.
This widget will be used to show the autocomplete options.
For this example we're using two external dependencies:
- [emojis](https://pub.dev/packages/emojis)
- [substring_highlight](https://pub.dev/packages/substring_highlight)
```dart
import 'package:emojis/emoji.dart';
import 'package:flutter/material.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'package:substring_highlight/substring_highlight.dart';
/// Overlay for displaying emoji that can be used
class StreamEmojiAutocompleteOptions extends StatelessWidget {
/// Constructor for creating a [StreamEmojiAutocompleteOptions]
const StreamEmojiAutocompleteOptions({
super.key,
required this.query,
this.onEmojiSelected,
});
/// Query for searching emoji.
final String query;
/// Callback called when an emoji is selected.
final ValueSetter<Emoji>? onEmojiSelected;
@override
Widget build(BuildContext context) {
final emojis = Emoji.all().where((it) {
final normalizedQuery = query.toUpperCase();
final normalizedShortName = it.shortName.toUpperCase();
return normalizedShortName.contains(normalizedQuery);
});
if (emojis.isEmpty) return const SizedBox.shrink();
return StreamAutocompleteOptions<Emoji>(
options: emojis,
optionBuilder: (context, emoji) {
final themeData = Theme.of(context);
return ListTile(
dense: true,
horizontalTitleGap: 0,
leading: Text(
emoji.char,
style: themeData.textTheme.headline6!.copyWith(
fontSize: 24,
),
),
title: SubstringHighlight(
text: emoji.shortName,
term: query,
textStyleHighlight: themeData.textTheme.headline6!.copyWith(
color: Colors.yellow,
fontSize: 14.5,
fontWeight: FontWeight.bold,
),
textStyle: themeData.textTheme.headline6!.copyWith(
fontSize: 14.5,
),
),
onTap: onEmojiSelected == null ? null : () => onEmojiSelected!(emoji),
);
},
);
}
}
```
Now it's time to use the `StreamEmojiAutocompleteOptions` widget.
```dart
StreamMessageInput(
customAutocompleteTriggers: [
StreamAutocompleteTrigger(
trigger: ':',
minimumRequiredCharacters: 2,
optionsViewBuilder: (
context,
autocompleteQuery,
messageEditingController,
) {
final query = autocompleteQuery.query;
return StreamEmojiAutocompleteOptions(
query: query,
onEmojiSelected: (emoji) {
// accepting the autocomplete option.
StreamAutocomplete.of(context).acceptAutocompleteOption(
emoji.char,
keepTrigger: false,
);
},
);
},
),
],
),
```
@@ -1,164 +0,0 @@
---
id: customize_attachment_picker_modal
title: Customizing the Attachment Picker Modal
---
Customizing the Attachment Picker Modal
### Introduction
The Attachment Picker is a modal that allows users to select attachments from their device.
It is generally used when a user taps the attachment button in the [StreamMessageInput](../stream_chat_flutter/message_input.mdx).
By default, the Attachment Picker provides multiple picker options as per the platform.
- For example, on Mobile, the default options are Camera, Gallery, File, and Video.
- On Web and Desktop, the default options are Image, Video and File.
### Customizing the Attachment Picker Modal
The Attachment Picker Modal can be customized by passing the different values to the `showStreamAttachmentPickerModalBottomSheet` function.
#### Initial Attachments
The initial attachments can be passed to the Attachment Picker Modal in two ways.
* By passing the `initialAttachments` parameter.
```dart
showStreamAttachmentPickerModalBottomSheet(
context: context,
initialAttachments: [
// Pass the initial attachments to the modal here if any are available already (optional)
...messageInputController.attachments,
],
);
```
* By creating a new instance of the `AttachmentPickerModalController` and passing it to the `controller` parameter.
```dart
final attachmentPickerController = StreamAttachmentPickerController(
initialAttachments: [
// Pass the initial attachments to the modal here if any are available already (optional)
...messageInputController.attachments,
],
// The `maxAttachmentSize` and `maxAttachmentCount` can also be set while creating a controller.
maxAttachmentSize: 10 * 1024 * 1024, // 10 MB
maxAttachmentCount: 10, // 10 attachments
);
showStreamAttachmentPickerModalBottomSheet(
context: context,
controller: attachmentPickerController,
);
```
#### Custom Attachment Picker Options
The Attachment Picker Modal provides a default set of options as per the platform.
However, you can also customize the options by passing the `customOptions` parameter.
```dart
showStreamAttachmentPickerModalBottomSheet(
context: context,
customOptions: [
// Pass the custom attachment picker options here
AttachmentPickerOption(
icon: Icon(Icons.audiotrack),
supportedTypes: [AttachmentPickerType.audios],
optionViewBuilder: (context, attachmentPickerController) {
return AudioPicker(
onAudioPicked: (audio) async {
await attachmentPickerController.addAttachment(audio);
return Navigator.pop(context, attachmentPickerController.value);
},
);
},
),
],
);
```
#### Attachment thumbnail size
The size of the attachment thumbnail item shown in the gallery picker can be defined by passing the `attachmentThumbnailSize` parameter.
```dart
showStreamAttachmentPickerModalBottomSheet(
context: context,
attachmentThumbnailSize: ThumbnailSize.square(600),
);
```
#### Attachment thumbnail format
The format of the attachment thumbnail item shown in the gallery picker can be defined by passing the `attachmentThumbnailFormat` parameter.
Possible values are `ThumbnailFormat.jpeg` and `ThumbnailFormat.png`.
```dart
showStreamAttachmentPickerModalBottomSheet(
context: context,
attachmentThumbnailFormat: ThumbnailFormat.jpeg,
);
```
#### Attachment thumbnail quality
The quality of the attachment thumbnail item shown in the gallery picker can be defined by passing the `attachmentThumbnailQuality` parameter.
Possible values are between 0 and 100.
```dart
showStreamAttachmentPickerModalBottomSheet(
context: context,
attachmentThumbnailQuality: 70,
);
```
#### Attachment thumbnail scale
The scale of the attachment thumbnail item shown in the gallery picker can be defined by passing the `attachmentThumbnailScale` parameter.
For example, if this is 2.0, it means that there are four image pixels for every one logical pixel, and the image's actual width and height are
double the height and width that should be used when painting the image.
```dart
showStreamAttachmentPickerModalBottomSheet(
context: context,
attachmentThumbnailScale: 2.0,
);
```
#### Additional modal bottom sheet parameters
The `showStreamAttachmentPickerModalBottomSheet` function also accepts the parameters that are available in the `showModalBottomSheet` function.
```dart
showStreamAttachmentPickerModalBottomSheet(
context: context,
isScrollControlled: true,
backgroundColor: Colors.transparent,
useRootNavigator: true,
elevation: 4,
isDismissible: true,
clipBehavior: Clip.antiAlias,
barrierColor: Colors.black.withOpacity(0.5),
constraints: BoxConstraints(
maxHeight: 500,
maxWidth: 500,
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(
top: Radius.circular(16.0),
),
),
);
```
@@ -1,82 +0,0 @@
---
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 `StreamMessageWidget`.
```dart
StreamMessageListView(
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 `StreamMessageWidget` to add extra actions.
```dart
StreamMessageListView(
messageBuilder: (context, details, messages, defaultMessage) {
return defaultMessage.copyWith(
customActions: [
StreamMessageAction(
leading: Icon(Icons.add),
title: Text('Demo Action'),
onTap: (message) {
/// Complete action here
},
),
],
);
},
)
```
@@ -1,185 +0,0 @@
---
id: customize_message_widget
sidebar_position: 11
title: Customizing The StreamMessageWidget
---
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 `StreamMessageWidget` in the Stream Chat Flutter UI SDK.
### Building Custom Messages
This guide goes into detail about the ability to customize the `StreamMessageWidget`. However, if you want
to customize the default `StreamMessageWidget` in the `StreamMessageListView` provided, you can use the `.copyWith()` method
provided inside the `messageBuilder` parameter of the `StreamMessageListView` like this:
```dart
StreamMessageListView(
messageBuilder: (context, details, messageList, defaultImpl) {
// Your implementation of the message here
// E.g: return Text(details.message.text ?? '');
},
),
```
### Theming
You can customize the `StreamMessageWidget` using the `StreamChatTheme` class, so that you can change the
message theme at the top instead of creating your own `StreamMessageWidget` 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: StreamMessageThemeData(
messageBackgroundColor: colorTheme.textHighEmphasis,
),
/// Sets theme for received messages
otherMessageTheme: StreamMessageThemeData(
avatarTheme: StreamAvatarThemeData(
borderRadius: BorderRadius.circular(8),
),
),
)
```
![](../assets/message_theming.png)
#### Change message text style
The `StreamMessageWidget` 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
StreamMessageThemeData(
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
StreamMessageThemeData(
avatarTheme: StreamAvatarThemeData(
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
StreamMessageThemeData(
reactionsBackgroundColor: Colors.red,
reactionsBorderColor: Colors.redAccent,
reactionsMaskColor: Colors.pink,
),
```
![](../assets/message_reaction_theming.png)
### Changing Message Actions
When a message is long pressed, the `StreamMessageActionsModal` is shown.
The `StreamMessageWidget` allows showing or hiding some options if you so choose.
```dart
StreamMessageWidget(
...
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 `customAttachmentBuilders` 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
StreamMessageListView(
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(
attachmentWidget: attachmentWidget,
attachmentShape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
);
}
},
);
},
),
```
### Widget Builders
Some parameters allow you to construct your own widget in place of some elements in the `StreamMessageWidget`.
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
StreamMessageWidget(
...
textBuilder: (context, message) {
// Add your own text implementation here.
},
),
```
@@ -1,158 +0,0 @@
---
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 `StreamMessageListView` / `StreamMessageWidget` in the
Stream Chat Flutter UI SDK.
:::note
This guide is specifically for the `StreamMessageListView` but if you intend to display a `StreamMessageWidget`
separately, follow the same process without the `.copyWith` and use the default constructor instead.
:::
### Basics of customizing a `StreamMessageWidget`
First, add a `StreamMessageListView` in the appropriate place where you intend to display messages from a
channel.
```dart
StreamMessageListView(
...
)
```
Now, we use the `messageBuilder` parameter to build a custom message. The builder function also provides
the default implementation of the `StreamMessageWidget` 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 `StreamMessageWidget` parameters were exposed directly through the `StreamMessageListView`,
however, this quickly becomes hard to maintain as more parameters and customizations are added to the
`StreamMessageWidget`. Newer version utilise a cleaner interface to change the parameters by supplying a
default message implementation as aforementioned.
:::
```dart
StreamMessageListView(
...
messageBuilder: (context, messageDetails, messageList, defaultWidget) {
return defaultWidget;
},
)
```
We use `.copyWith()` to customize the widget:
```dart
StreamMessageListView(
...
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
`StreamMessageWidget` has a `messageTheme` parameter that allows you to pass the theme for most aspects
of the message.
```dart
StreamMessageListView(
...
messageBuilder: (context, messageDetails, messageList, defaultWidget) {
return defaultWidget.copyWith(
messageTheme: StreamMessageThemeData(
...
messageTextStyle: TextStyle(),
),
);
},
)
```
If you want to replace the entire text widget in the `StreamMessageWidget`, you can use the `textBuilder`
parameter which provides a builder for creating a widget to substitute the default text.parameter
```dart
StreamMessageListView(
...
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 StreamMessageWidget:
```dart
StreamMessageListView(
...
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)
@@ -1,258 +0,0 @@
---
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 **StreamMessageInput** 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
StreamMessageInput(
...
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 **StreamMessageListView** widget to have a custom `messagebuilder`, that can decrypt the message.
```dart
StreamMessageListView(
...
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).
@@ -1,144 +0,0 @@
---
id: error_reporting_with_sentry
sidebar_position: 15
title: Error Reporting With Sentry
---
Error Reporting With Sentry
## Introduction
While one always tries to create apps that are free of bugs, they're sure to crop up from time to time. Since buggy apps lead to unhappy users and customers, it's important to understand how often your users experience bugs and where those bugs occur. That way, you can prioritize the bugs with the highest impact and work to fix them.
Whenever an error occurs, create a report containing the error that occurred and the associated stack trace. You can then send the report to an error tracking service, such as [Sentry](https://sentry.io/), [Rollbar](https://rollbar.com/), or [Firebase Crashlytics](https://firebase.google.com/docs/crashlytics).
The error tracking service aggregates all of the crashes your users experience and groups them together. This allows you to know how often your app fails and where your users run into trouble.
In this guide, learn how to report Stream Chat errors to the [Sentry](https://sentry.io/welcome/) crash reporting service using the following steps.
### 1. Get a DSN From Sentry
Before reporting errors to Sentry, you need a “DSN” to uniquely identify your app with the Sentry service:
To get a DSN, use the following steps:
- [Create an account with Sentry](https://sentry.io/signup/).
- Log in to the account.
- Create a new Flutter project.
- Copy the code snippet that includes the DSN.
### 2. Import the Sentry package
Import the `sentry_flutter` package into your app. The sentry package makes it easier to send error reports to the Sentry error tracking service.
```yaml
dependencies:
sentry_flutter: <latest_version>
```
### 3. Initialize the Sentry SDK
Initialize the SDK to capture different unhandled errors automatically.
```dart
import 'package:sentry_flutter/sentry_flutter.dart';
Future<void> main() async {
await SentryFlutter.init(
(options) => options.dsn = 'https://[email protected]/example',
appRunner: () => runApp(const MyApp()),
);
}
```
Or, if you want to run your app in your own error zone, use `runZonedGuarded`:
```dart
void main() async {
/// Captures errors reported by the Flutter framework.
FlutterError.onError = (FlutterErrorDetails details) {
if (kDebugMode) {
// In development mode, simply print to console.
FlutterError.dumpErrorToConsole(details);
} else {
// In production mode, report to the application zone to report to Sentry.
Zone.current.handleUncaughtError(details.exception, details.stack!);
}
};
Future<void> _reportError(dynamic error, StackTrace stackTrace) async {
// Print the exception to the console.
if (kDebugMode) {
// Print the full stack trace in debug mode.
print(stackTrace);
return;
} else {
// Send the Exception and Stacktrace to sentry in Production mode.
await Sentry.captureException(error, stackTrace: stackTrace);
}
}
runZonedGuarded(
() async {
await SentryFlutter.init(
(options) => options.dsn = 'https://[email protected]/example',
);
runApp(const MyApp());
},
_reportError,
);
}
```
Alternatively, you can pass the DSN to Flutter using the **dart-define** tag:
```bash
--dart-define SENTRY_DSN=https://[email protected]/example
```
### 4. Integration With StreamChat Applications
Override the default `logHandlerFunction` to send errors to Sentry.
```dart
void sampleAppLogHandler(LogRecord record) async {
if (kDebugMode) StreamChatClient.defaultLogHandler(record);
// Report errors to Sentry
if (record.error != null || record.stackTrace != null) {
await Sentry.captureException(
record.error,
stackTrace: record.stackTrace,
);
}
}
StreamChatClient buildStreamChatClient(
String apiKey, {
Level logLevel = Level.SEVERE,
}) {
return StreamChatClient(
apiKey,
logLevel: logLevel,
logHandlerFunction: sampleAppLogHandler, // Pass the overridden logHandlerFunction
);
}
```
### 5. Capture Errors Programmatically
Besides the automatic error reporting that Sentry generates by importing and initializing the SDK,
you can use the API to manually report errors to Sentry:
```dart
await Sentry.captureException(exception, stackTrace: stackTrace);
```
For more information, see the [Sentry API](https://pub.dev/documentation/sentry_flutter/latest/sentry_flutter/sentry_flutter-library.html) docs on Pub.
### Complete Example
To view a working example, see the [Stream Sample app](https://github.com/GetStream/flutter-samples/tree/main/packages/stream_chat_v1).
### Learn More
Extensive documentation about using the Sentry SDK can be found on [Sentry's site](https://docs.sentry.io/platforms/flutter/).
@@ -1,753 +0,0 @@
---
id: migration_guide_4_0
sidebar_position: 14
title: Migration Guide v4.0
---
**Version 4.0.0** of the Stream Chat Flutter SDK carries significant architectural changes to improve the developer experience by giving you more control and flexibility in how you use our core components and UI widgets.
This guide is intended to enumerate and better explain the changes in the SDK.
If you find any bugs or have any questions, please file an [issue on our GitHub repository](https://github.com/GetStream/stream-chat-flutter/issues). We want to support you as much as we can with this migration.
Code examples:
- See our [Stream Chat Flutter tutorial](https://getstream.io/chat/flutter/tutorial/) for an up-to-date guide using the latest Stream Chat version.
- See the [Stream Flutter Samples repository](https://github.com/GetStream/flutter-samples) with our fully-fledged messaging [sample application](https://github.com/GetStream/flutter-samples/tree/main/packages/stream_chat_v1).
All of our documentation has also been updated to support v4, so all of the guides and examples will have updated code.
### Dependencies
To migrate to v4.0.0, update your `pubspec.yaml` with the correct Stream chat package you're using:
```yaml
dependencies:
stream_chat_flutter: ^4.0.0 # full UI, core and client packages
stream_chat_flutter_core: ^4.0.0 # core and client packages
stream_chat: ^4.0.0 # client package
```
---
## Name Changes
The majority of the Stream Chat widgets and classes have now been renamed to have a “Stream” prefix associated with them. This increases Stream widgets' discoverability and avoids name conflicts when importing.
For example, `MessageListView` is now called `StreamMessageListView`, and `UserAvatar` is renamed to `StreamUserAvatar`.
**The old class names are deprecated and will be removed in the next major release (v5.0.0).**
See the sections below on “deprecated classes” for a complete list of changes. Some of these classes/widgets have undergone functional changes as well, that will be explore in the following sections.
## Removed Functionality/Widgets
This section highlights functionality removed.
### Removed Methods And Classes
In version 4 we removed the following deprecated methods and classes:
* `Channel.banUser`
* `Channel.unbanUser`
* `ClientState.user`
* `ClientState.userStream`
* `MessageWidget.allRead`
* `MessageWidget.readList`
* `StreamChat.user`
* `StreamChat.userStream`
* `StreamChatCore.user`
* `StreamChatCore.userStream`
These were marked as deprecated in v3.
### Video Compression
The automatic video compression when uploading a video has been removed. You can integrate this yourself by manipulating attachments using a [custom attachment uploader](https://getstream.io/chat/docs/flutter-dart/file_uploads/?language=dart).
### Slidable Channel List Item
The default slidable channel preview behavior has been removed. We have created a [guide](slidable_channel_list_preview.mdx) showing you how you can easily add this functionality yourself.
![Slidable demo](../assets/slidable_demo.jpg)
### Pin Permission
`pinPermissions` is no longer needed in the **MessageListView** widget. The permissions are automatically fetched for each Stream project. To enable users to pin the message, make sure the pin permissions are granted for different types of users on your [Stream application dashboard](https://dashboard.getstream.io/).
---
## Deprecated Classes
This section covers all the deprecated classes and widgets in the Stream chat packages. Some of these have also undergone functional changes, for example, **MessageInput** and **ChannelsBloc**. These are discussed in more detail below.
The majority of the Stream widgets and classes have now been renamed to have a **"Stream"** prefix associated with them.
Changes:
- `AttachmentTitle` in favor of `StreamAttachmentTitle`
- `AttachmentUploadStateBuilder` in favor of `StreamAttachmentsUploadStateBuilder`
- `AttachmentWidget` in favor of `StreamAttachmentWidget`
- `AvatarThemeData` in favor of `StreamAvatarThemeData`
- `ChannelAvatar` in favor of `StreamChannelAvatar`
- `ChannelBottomSheet` in favor of `StreamChannelInfoBottomSheet`
- `ChannelHeader` in favor of `StreamChannelHeader`
- `ChannelHeaderTheme` in favor of `StreamChannelHeaderTheme`
- `ChannelHeaderThemeData` in favor of `StreamChannelHeaderThemeData`
- `ChannelInfo` in favor of `StreamChannelInfo`
- `ChannelListHeader` in favor of `StreamChannelListHeader`
- `ChannelListHeaderTheme` in favor of `StreamChannelListHeaderTheme`
- `ChannelListHeaderThemeData` in favor of `StreamChannelListHeaderThemeData`
- `ChannelListView` in favor of `StreamChannelListView`
- `ChannelListViewTheme` in favor of `StreamChannelListViewTheme`
- `ChannelListViewThemeData` in favor of `StreamChannelListViewThemeData`
- `ChannelListHeader` in favor of `StreamChannelListHeader`
- `ChannelListView` in favor of `StreamChannelListView`
- `ChannelName` in favor of `StreamChannelName`
- `ChannelPreview` in favor of `StreamChannelListTile`
- `ChannelPreviewTheme` in favor of `StreamChannelPreviewTheme`
- `ChannelPreviewThemeData` in favor of `StreamChannelPreviewThemeData`
- `ChannelName` in favor of `StreamChannelName`
- `ColorTheme` in favor of `StreamColorTheme`
- `CommandsOverlay` in favor of `StreamCommandsOverlay`
- `ConnectionStatusBuilder` in favor of `StreamConnectionStatusBuilder`
- `DateDivider` in favor of `StreamDateDivider`
- `DeletedMessage` in favor of `StreamDeletedMessage`
- `EmojiOverlay` in favor of `StreamEmojiOverlay`
- `FileAttachment` in favor of `StreamFileAttachment`
- `FullScreenMedia` in favor of `StreamFullScreenMedia`
- `GalleryFooter` in favor of `StreamGalleryFooter`
- `GalleryFooterThemeData` in favor of `StreamGalleryFooterThemeData`
- `GalleryHeader` in favor of `StreamGalleryHeader`
- `GalleryHeaderTheme` in favor of `StreamGalleryHeaderTheme`
- `GalleryHeaderThemeData` in favor of `StreamGalleryHeaderThemeData`
- `GiphyAttachment` in favor of `StreamGiphyAttachment`
- `GradientAvatar` in favor of `StreamGradientAvatar`
- `GroupAvatar` in favor of `StreamGroupAvatar`
- `ImageAttachment` in favor of `StreamImageAttachment`
- `ImageGroup` in favor of `StreamImageGroup`
- `InfoTile` in favor of `StreamInfoTile`
- `MediaListView` in favor of `StreamMediaListView`
- `MessageAction` in favor of `StreamMessageAction`
- `MessageActionsModal` in favor `StreamMessageActionsModal`
- `MessageInput` in favor of `StreamMessageInput`
- `MessageInputTheme` in favor of `StreamMessageInputTheme`
- `MessageInputThemeData` in favor of `StreamMessageInputThemeData`
- `MessageInputState` in favor of `StreamMessageInput`
- `MessageListView` in favor of `StreamMessageListView`
- `MessageListViewTheme` in favor of `StreamMessageListViewTheme`
- `MessageListViewThemeData` in favor of `StreamMessageListViewThemeData`
- `MessageSearchListView` in favor of `StreamMessageSearchListView`
- `MessageSearchListViewTheme` in favor of `StreamMessageSearchListViewTheme`
- `MessageSearchListViewThemeData` in favor of `StreamMessageSearchListViewThemeData`
- `MessageReactionsModal` in favor of `StreamMessageReactionsModal`
- `MessageSearchItem` in favor of `StreamMessageSearchItem`
- `MessageSearchListView` in favor of `StreamMessageSearchListView`
- `MessageText` in favor of `StreamMessageText`
- `MessageWidget` in favor of `StreamMessageWidget`
- `MessageThemeData` in favor of `StreamMessageThemeData`
- `MultiOverlay` in favor of `StreamMultiOverlay`
- `OptionListTile` in favor of `StreamOptionListTile`
- `QuotedMessageWidget` in favor of `StreamQuotedMessageWidget`
- `ReactionBubble` in favor of `StreamReactionBubble`
- `ReactionIcon` in favor of `StreamReactionIcon`
- `ReactionPicker` in favor of `StreamReactionPicker`
- `SendingIndicator` in favor of `StreamSendingIndicator`
- `SystemMessage` in favor of `StreamSystemMessage`
- `TextTheme` in favor of `StreamTextTheme`
- `ThreadHeader` in favor of `StreamThreadHeader`
- `TypingIndicator` in favor of `StreamTypingIndicator`
- `UnreadIndicator` in favor of `SteamUnreadIndicator`
- `UploadProgressIndicator` in favor of `StreamUploadProgressIndicator`
- `UrlAttachment` in favor of `StreamUrlAttachment`
- `UserAvatar` in favor of `StreamUserAvatar`
- `UserItem` in favor of `StreamUserItem`
- `UserListView` in favor of `StreamUserListView`
- `UserListViewTheme` in favor of `StreamUserListViewTheme`
- `UserListViewThemeData` in favor of `StreamUserListViewThemeData`
- `UserMentionTile` in favor of `StreamUserMentionTile`
- `UserMentionsOverlay` in favor of `StreamUserMentionsOverlay`
- `VideoAttachment` in favor of `StreamVideoAttachment`
- `VideoService` in favor of `StreamVideoService`
- `VideoThumbnailImage` in favor of `StreamVideoThumbnailImage`
- `VisibleFootnote` in favor of `StreamVisibleFootnote`
## ChannelListView to StreamChannelListView
The `ChannelListView` widget has been deprecated, and it is now recommended to use `StreamChannelListView`.
Version 4 of the Stream Chat Flutter packages introduces a new controller called, `StreamChannelListController`. This controller manages the content for a channel list; it lets you perform tasks such as:
- Load initial data.
- Use channel events handlers.
- Load more data using `loadMore`.
- Replace the previously loaded channels.
- Return/Create a new channel and start watching it.
- Pause and Resume all subscriptions added to this composite.
For more information see the [`StreamChannelListView` documentation](../stream_chat_flutter/stream_channel_list_view.mdx).
### ChannelsBloc to StreamChannelListController
The `ChannelsBloc` widget should be replaced with `StreamChannelListController`. This controller provides all the functionality needed to query and manipulate channel data previously accessible through `ChannelsBloc`.
For more information see the [`StreamChannelListController` documentation](../stream_chat_flutter_core/stream_channel_list_controller.mdx).
### StreamChannelListView Examples
Let's explore some examples of the functional differences when using the new `StreamChannelListView`.
The **StreamChannelListController** provides various methods, such as:
- **deleteChannel**
- **loadMore**
- **muteChannel**
- **deleteChannel**
For a complete list with additional information, see the code documentation.
#### Basic Use
The following code demonstrates the old way of creating a **ChannelListPage**, that displays a list of channels:
```dart
class ChannelListPage extends StatelessWidget {
const ChannelListPage({
Key? key,
}) : super(key: key);
@override
// ignore: prefer_expression_function_bodies
Widget build(BuildContext context) {
return Scaffold(
body: ChannelsBloc(
child: ChannelListView(
filter: Filter.in_(
'members',
[StreamChat.of(context).currentUser!.id],
),
sort: const [SortOption('last_message_at')],
limit: 20,
channelWidget: const ChannelPage(),
),
),
);
}
}
```
In **v4** this can now be achieved with the following:
```dart
class ChannelListPage extends StatefulWidget {
const ChannelListPage({
Key? key,
required this.client,
}) : super(key: key);
final StreamChatClient client;
@override
State<ChannelListPage> createState() => _ChannelListPageState();
}
class _ChannelListPageState extends State<ChannelListPage> {
late final _controller = StreamChannelListController(
client: widget.client,
filter: Filter.in_(
'members',
[StreamChat.of(context).currentUser!.id],
),
sort: const [SortOption('last_message_at')],
);
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) => Scaffold(
body: RefreshIndicator(
onRefresh: _controller.refresh,
child: StreamChannelListView(
controller: _controller,
onChannelTap: (channel) => Navigator.push(
context,
MaterialPageRoute(
builder: (_) => StreamChannel(
channel: channel,
child: const ChannelPage(),
),
),
),
),
),
);
}
```
As you can see, the **ChannelsBloc** has been replaced with a **StreamChannelListController**, where the **filter**, **limit**, and **sort** arguments can be set. The above code also demonstrates how to refresh the channel list by calling `_controller.refresh()`.
## MessageSearchListView to StreamMessageSearchListView
The `MessageSearchListView` widget has been deprecated, and it is now recommended to use `StreamMessageSearchListView`.
Version 4 of the Stream Chat Flutter packages introduces a new controller called, `StreamMessageSearchListController`. This controller manages the content when searching for a message; it lets you perform tasks such as:
- Load initial data.
- Set filters and search terms.
- Load more data using `loadMore`.
- Refresh data.
For more information see the [`StreamMessageSearchListView` documentation](../stream_chat_flutter/stream_message_search_list_view.mdx).
### MessageSearchBloc to StreamMessageSearchListController
The `MessageSearchBloc` widget should be replaced with a `StreamMessageSearchListController`. This controller provides all the functionality needed to query and manipulate message search data previously accessible through `MessageSearchBloc`.
For more information see the [`StreamMessageSearchListController` documentation](../stream_chat_flutter_core/stream_message_search_list_controller.mdx).
### StreamMessageSearchListView Example
The following code demonstrates the old way of searching for messages:
```dart
class SearchExample extends StatelessWidget {
const SearchExample({
Key? key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return MessageSearchBloc(
child: MessageSearchListView(
showErrorTile: true,
messageQuery: 'message query',
filters: Filter.in_('members', const ['user-id']),
sortOptions: const [
SortOption(
'created_at',
direction: SortOption.ASC,
),
],
pullToRefresh: false,
limit: 30,
emptyBuilder: (context) => const Text('Nothing to show'),
itemBuilder: (context, messageResponse) {
/// Return widget
}
onItemTap: (messageResponse) {
/// Handle on tap
}
),
);
}
}
```
In **v4**, this can now be achieved with the following:
```dart
class SearchExample extends StatefulWidget {
const SearchExample({
Key? key,
}) : super(key: key);
@override
State<SearchExample> createState() => _SearchExampleState();
}
class _SearchExampleState extends State<SearchExample> {
late final StreamMessageSearchListController _messageSearchListController =
StreamMessageSearchListController(
client: StreamChat.of(context).client,
filter: Filter.in_('members', [StreamChat.of(context).currentUser!.id]),
limit: 5,
searchQuery: '',
sort: [
const SortOption(
'created_at',
direction: SortOption.ASC,
),
],
);
search() {
_messageSearchListController.searchQuery = 'search-value';
_messageSearchListController.doInitialLoad();
}
@override
dispose() {
_messageSearchListController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return StreamMessageSearchListView(
controller: _messageSearchListController,
emptyBuilder: (context) => const Text('Nothing to show'),
itemBuilder: (
context,
messageResponses,
index,
defaultWidget,
) {
return defaultWidget.copyWith(); // modify default widget
});
}
}
```
## UserListView to StreamUserListView
The `UserListView` widget has been deprecated, and it is now recommended to use `StreamUserListView`.
Version 4 of the Stream Chat Flutter packages introduces a new controller called, `StreamUserListController`. This controller manages the content when retrieving Stream users; it let's you perform tasks, such as:
- Load data.
- Set filters.
- Refresh data.
For more information see the [`StreamUserListView` documentation](../stream_chat_flutter/stream_user_list_view.mdx).
### UsersBloc to StreamUserListController
The `UsersBloc` widget should be replaced with a `StreamUserListController`. This controller provides all the functionality needed to query and manipulate user data previously accessible through `UsersBloc`.
For more information see the [`StreamUserListController` documentation](../stream_chat_flutter_core/stream_user_list_controller.mdx).
### StreamUserListView Example
The following code demonstrates the old way of displaying all users:
```dart
class UsersExample extends StatelessWidget {
const UsersExample({
Key? key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return UsersBloc(
child: UserListView(
groupAlphabetically: true,
onUserTap: (user, _) {
/// Handle on tap
},
limit: 25,
filter: Filter.and([
Filter.autoComplete('name', 'some-name'),
Filter.notEqual('id', StreamChat.of(context).currentUser!.id),
]),
sort: const [
SortOption(
'name',
direction: 1,
),
],
),
);
}
}
```
In **v4**, this can now be achieved with the following:
```dart
class UsersExample extends StatefulWidget {
const UsersExample({
Key? key,
}) : super(key: key);
@override
State<UsersExample> createState() => _UsersExampleState();
}
class _UsersExampleState extends State<UsersExample> {
late final userListController = StreamUserListController(
client: StreamChat.of(context).client,
limit: 25,
filter: Filter.and([
Filter.notEqual('id', StreamChat.of(context).currentUser!.id),
]),
sort: [
const SortOption(
'name',
direction: 1,
),
],
);
void _load() {
userListController.filter = Filter.and([
Filter.autoComplete('name', 'some-name'),
Filter.notEqual('id', StreamChat.of(context).currentUser!.id),
]);
userListController.doInitialLoad();
}
@override
dispose() {
userListController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return StreamUserListView(
controller: userListController,
onUserTap: (user) {
/// Handle on tap
},
emptyBuilder: (context) => const Text('Nothing to show'),
itemBuilder: (
context,
users,
index,
defaultWidget,
) {
return defaultWidget.copyWith(); // modify default widget
},
);
}
}
```
## MessageInput to StreamMessageInput
The `MessageInput` widget has been deprecated, and it is now recommended to use `StreamMessageInput`.
Version 4 of the Stream Chat Flutter packages introduces a new controller called, `MessageInputController`. This controller maintains the state of the message input and exposes various methods to allow you to customize and manipulate the underlying **Message** value.
Creating a separate controller allows easier control over the message input content by moving logic out of the deprecated `MessageInput` and into the controller. This controller can then be created, managed, and exposed in whatever way you like.
The widget is also separated into smaller components: `StreamCountDownButton`, `StreamAttachmentPicker`, etc.
> ❗The `MessageInputController` is exposed by the **stream_chat_flutter_core** package. This allows you to use the controller even if you're not using the UI components.
As a result of this extra control, it is no longer needed for the new `StreamMessageInput` widget to expose these `MessageInput` arguments:
- `parentMessage`: parent message in case of a thread
- `editMessage`: message to edit
- `initialMessage`: message to start with
- `quotedMessage`: message to quote/reply
- `onQuotedMessageCleared`: callback for clearing quoted message
- `textEditingController`: the text controller of the text field
The following arguments are newly introduced to the `StreamMessageInput`, and are not available on the old `MessageInput`:
- `messageInputController`: the controller for the message input
- `attachmentsPickerBuilder`: builder for bottom sheet when attachment picker is opened
- `sendButtonBuilder`: builder for creating send button
- `validator`: a callback function that validates the message
- `restorationId`: restoration ID to save and restore the state of the MessageInput
- `enableSafeArea`: wraps the **StreamMessageInput** widget with a **SafeArea** widget
- `elevation`: elevation of the **StreamMessageInput** widget
- `shadow`: **Shadow** for the **StreamMessageInput** widget
For more information see the [`StreamMessageInput` documentation](../stream_chat_flutter_core/stream_message_input_controller.mdx).
### StreamMessageInput Examples
Let's explore some examples of the functional differences when using the new `StreamMessageInput`.
#### Basic Use
Unless you want to programmatically manipulate the value of the message input, then there is no difference in how you would use the message input widget.
The following code demonstrates the old way of creating a **ChannelPage** widget that displays a chat screen:
```dart
class ChannelPage extends StatelessWidget {
const ChannelPage({
Key? key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: const ChannelHeader(),
body: Column(
children: const <Widget>[
Expanded(
child: MessageListView(),
),
MessageInput(),
],
),
);
}
}
```
In **v4** this is the same, the only difference being that all the Stream widgets are now prefixed with **Stream**. For example, **MessageListView** becomes **StreamMessageListView**, and so forth.
```dart
class ChannelPage extends StatelessWidget {
const ChannelPage({
Key? key,
}) : super(key: key);
@override
Widget build(BuildContext context) => Scaffold(
appBar: const StreamChannelHeader(),
body: Column(
children: const <Widget>[
Expanded(
child: StreamMessageListView(),
),
StreamMessageInput(),
],
),
);
}
```
However, you can optionally pass in a **MessageInputController** in the **StreamMessageInput**, which gives extra control over the message input value.
#### Thread Page
The following code demonstrates the old way of creating a thread page:
```dart
class ThreadPage extends StatelessWidget {
const ThreadPage({
Key? key,
this.parent,
}) : super(key: key);
final Message? parent;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: ThreadHeader(
parent: parent!,
),
body: Column(
children: <Widget>[
Expanded(
child: MessageListView(
parentMessage: parent,
),
),
MessageInput(
parentMessage: parent,
),
],
),
);
}
}
```
In **v4** the only difference is the **Stream** prefix and the way that the parent message is passed to the message input:
```dart
class ThreadPage extends StatelessWidget {
const ThreadPage({
Key? key,
this.parent,
}) : super(key: key);
final Message? parent;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: StreamThreadHeader(
parent: parent!,
),
body: Column(
children: <Widget>[
Expanded(
child: StreamMessageListView(
parentMessage: parent,
),
),
StreamMessageInput(
messageInputController: MessageInputController(
message: Message(parentId: parent!.id),
),
),
],
),
);
}
}
```
To send a thread message, you need to specify the message's parent ID for which you're creating a thread.
#### Reply/Quote Message
The following code demonstrates the old way of replying to a message:
```dart
...
void _reply(Message message) {
setState(() => _quotedMessage = message);
}
...
MessageInput
quotedMessage: _quotedMessage,
onQuotedMessageCleared: () {
setState(() => _quotedMessage = null);
},
),
```
To reply to a message in **v4**:
```dart
...
void _reply(Message message) {
_messageInputController.quotedMessage = message;
}
...
StreamMessageInput(
messageInputController: _messageInputController,
),
```
The controller makes it much simpler to dynamically modify the message input.
## Stream Chat Flutter Core
Various changes have been made to the Core package, most notably, the indroduction of all of the controllers mentioned above.
These controllers replace the business logic implementations (Bloc). Please note that this is not related to the well-known Flutter Bloc package, but instead refers to the naming we used for our business logic components.
In this version we're introducing controllers in place of their bloc counterparts:
**StreamChannelListController** in favor of **ChannelsBloc**
**StreamMessageSearchListController** in favor of **MessageSearchBloc**
**StreamUserListController** in favor of **UsersBloc**
The Bloc components are deprecated in v4.0.0 but can still be used. They will be removed in the next major release (v5.0.0).
Additionally, we also now have the **StreamMessageInputController**, as discussed above. This can be used outside of our UI package as well.
Finally, the following Core builders are also deprecated as their functionality can be replaced using their controller counterparts:
- ChannelListCore
- MessageSearchListCore
- UserListCore
@@ -1,174 +0,0 @@
---
id: migration_guide_5_0
sidebar_position: 15
title: Migration Guide v5.0
---
**Version 5.0.0** of the Stream Chat Flutter SDK UI package has been overhauled to support larger screen sizes better and provide native feeling web and desktop platform interactions that feel intuitive and expected.
These newly introduced changes are platform-dependent and will not affect your current Android and iOS builds.
This guide enumerates and better explains the SDK changes introduced in v5.
If you find any bugs or have any questions, please file an [issue on our GitHub repository](https://github.com/GetStream/stream-chat-flutter/issues). We want to support you as much as we can with this migration.
Code examples:
- See our [Stream Chat Flutter tutorial](https://getstream.io/chat/flutter/tutorial/) for an up-to-date guide using the latest Stream Chat version.
- See the [Stream Flutter Samples repository](https://github.com/GetStream/flutter-samples) with our fully-fledged messaging [sample application](https://github.com/GetStream/flutter-samples/tree/main/packages/stream_chat_v1).
Our documentation has also been updated to support v5, so all guides and examples will have updated code.
### Dependencies
To migrate to v5.0.0, update your `pubspec.yaml` with the correct Stream chat package you're using:
```yaml
dependencies:
stream_chat_flutter: ^5.0.0 # full UI, core and client packages
stream_chat_flutter_core: ^5.0.0 # core and client packages
stream_chat: ^5.0.0 # client package
```
---
## Desktop and Web Support: What Changed?
This section highlights our efforts on Desktop (macOS, Windows, and Linux) and Web support.
### Setup
See the [setup guide](../stream_chat_flutter/setup.mdx) for platform specific instructions.
### Supporting Larger Screens
We've added support for larger screens and have made changes to the UI to support larger screen sizes better.
- Widgets are constrained to a maximum size, for example, appropriate message sizing for larger screens.
- UI changes to use larger screen real estate. For example, reactions are added to the bottom of a message on desktop and web.
Below is an example running on macOS, with a split-screen view showing channels on the left and messages on the right.
![MacOS split](../assets/mac_os_split.png)
### Native Platform Interactions
The user experience of interacting with a desktop application differs from a mobile counterpart. There are several factors to consider for an application to feel native and intuitive on the platform it is running, for example:
- Input controls: touch, keyboard, and mouse interactions
- Native file system or gallery access (as well as sharing functionality)
- Shortcuts
- Dialogs
By default, Stream Chat Flutter will use the correct input controls and visual elements for the target platform. For example, touch and swipe controls will be the default on mobile, while on web and desktop these will be disabled and interactions with the mouse and keyboard will be preferred.
On desktop and web it's also possible to add attachments by simply dragging them into the message input box.
### All UI/Behaviour Changes for Desktop and Web
- Right-click context menus for messages and full-screen attachments.
- Upload and download attachments using the native desktop file system.
- Press the "enter" key to send a message.
- If you are quoting a message and have not yet typed any text, you can press the "esc" key to remove the quoted message.
- A dedicated "X" button for removing a quoted message with your mouse.
- Drag and drop attachment files to `StreamMessageInput`.
- New `StreamMessageInput.draggingBorder` property to customize the border color of the message input when dropping a file.
- Message reactions bubbles differ per platform.
- Hovering over a message reaction will show the users that have reacted to the message.
- Desktop attachment sharing UI.
- Selectable message text with mouse input.
- Gallery navigation controls with keyboard shortcuts (left and right arrow keys).
- Appropriate message sizing for large screens.
- Right-click context menu for `StreamMessageListView` items.
- `StreamMessageListView` items not swipeable on desktop & web.
- Video support for Windows & Linux through `dart_vlc`.
- Video support for macOS through `video_player_macos`.
- Replace bottom sheets with dialogs where appropriate.
## What's New?
We improved the overall user experience of the Stream Chat Flutter SDK and added new features to make it easier to customize the SDK to your needs.
We've also fixed several bugs and improved the overall stability of the SDK.
### StreamChatConfiguration
The `StreamChatConfiguration` class is a new inherited widget that allows you to configure the Stream Chat Flutter SDK.
It provides a few configuration options. For example, it lets you specify if you want to `enforceUniqueReactions` or not and allows you to set the `reactionIcons` to use in your app.
You can retrieve the current configuration using `StreamChatConfiguration.of(context)`, as long as there is a `StreamChat` or `StreamChatConfiguration` widget higher up the widget tree. You can provide a custom `StreamChatConfigurationData` directly to `StreamChat` or wrap a section of the widget tree with a `StreamChatConfiguration`.
For additional information, see [#1125](https://github.com/GetStream/stream-chat-flutter/issues/1125). The `defaultUserImage`, `placeholderUserImage`, `reactionIcons`, and `enforceUniqueReactions` have been refactored out of `StreamChatThemeData` and into the new`StreamChatConfigurationData` class.
### StreamMemberListView and StreamMemberGridView
The `StreamMemberListView` and `StreamMemberGridView` widgets are new widgets that allow you to display a list of members in a channel.
Check out the dedicated [documentation](../stream_chat_flutter/stream_member_list_view.mdx) for more information.
### Attachment Picker
As part of the v5 release, we've refactored the `AttachmentPicker` to be more flexible and customizable. This allows you to use the `AttachmentPicker` in various ways and customize the UI to your liking.
Check out the dedicated [guide](./customize_attachment_picker_modal.mdx) for more information.
### Other Changes
The following was also introduced:
- Added support for additional text field params in`StreamMessageInput`: `maxLines`, `minLines`, `textInputAction`, `keyboardType`, and `textCapitalization`.
- Added `showStreamAttachmentPickerModalBottomSheet` to show the attachment picker modal bottom sheet.
- Added `onQuotedMessageCleared` to `StreamMessageInput`
- `selected` and `selectedTileColor` to `StreamChannelListTile`
- Added `AttachmentUploadStateBuilder.inProgressBuilder` to `AttachmentUploadStateBuilder`
- Added `AttachmentUploadStateBuilder.successBuilder` to `AttachmentUploadStateBuilder`
- Added `AttachmentUploadStateBuilder.failedBuilder` to `AttachmentUploadStateBuilder`
- Added `StreamAutocomplete` widget for auto-complete triggers in `StreamMessageInput`.
- Added `StreamMessageInput.customAutocompleteTriggers` to allow users to define their custom triggers.
New translations:
- `couldNotReadBytesFromFileError`
- `downloadLabel`
- `toggleMuteUnmuteAction`
- `toggleMuteUnmuteGroupQuestion`
- `toggleMuteUnmuteGroupText`
- `toggleMuteUnmuteUserQuestion`
- `toggleMuteUnmuteUserText`
## Deprecated
The following components have been deprecated in v5.0.0:
- Deprecated `showConfirmationDialog` in favor of `showConfirmationBottomSheet`
- Deprecated `showInfoDialog` in favor of `showInfoBottomSheet`
- Deprecated `wrapAttachmentWidget` in favor of the `WrapAttachmentWidget` class
## Breaking changes
The following components have been removed in v5.0.0:
- `StreamImageAttachment.size` has been removed in favor of `StreamImageAttachment.constraints`.
- `StreamFileAttachment.size` has been removed in favor of `StreamFileAttachment.constraints`.
- `StreamGiphyAttachment.size` has been removed in favor of `StreamGiphyAttachment.constraints`.
- `StreamVideoAttachment.size` has been removed in favor of `StreamVideoAttachment.constraints`.
- `StreamVideoThumbnailImage.width` and `StreamVideoThumbnailImage.height` have been removed in favor of `StreamVideoThumbnailImage.constraints`.
To fix these deprecations in your code, you can use a `BoxConstraints.tight` passing the desired fixed size as a parameter.
```dart
/// BEFORE
StreamImageAttachment(
size: size,
)
/// AFTER
StreamImageAttachment(
constraints: BoxConstraints.tight(size),
)
```
- Removed `StreamMessageInput.customOverlays` in favor of `StreamMessageInput.customAutocompleteTriggers`. Read the guide on [Adding Custom Autocomplete Triggers](./autocomplete_triggers.mdx) to learn how to migrate your code.
- Removed the default emoji overlay picker. Read the guide on [Adding Custom Autocomplete Triggers](./autocomplete_triggers.mdx) to learn how to migrate your code.
@@ -1,212 +0,0 @@
---
id: slidable_channel_list_preview
sidebar_position: 14
title: Slidable Channel List Preview
---
Slidable Channel List Preview
### Introduction
The default slidable behavior within the channel list has been removed in v4 of the Stream Chat Flutter SDK.
This guide will show you how you can easily add this functionality yourself.
Please see our [full v4 migration guide](migration_guide_4_0.mdx) if you're migrating from an earlier version of the Stream Chat Flutter SDK.
![Slidable demo](../assets/slidable_demo.jpg)
### Prerequisites
This guide assumes you are familiar with the Stream Chat SDK.
If you're new to Stream Chat Flutter, we recommend looking at our [getting started tutorial](https://getstream.io/chat/flutter/tutorial/).
**Dependencies:**
```dart
dependencies:
flutter:
sdk: flutter
stream_chat_flutter: ^4.0.0
flutter_slidable: ^1.2.0
```
⚠️ Note: The examples shown in this guide use the above packages and versions.
### Example Code - Custom Stream Channel Item Builder
In this example, you are doing a few important things in the ChannelListPage widget. You're:
- Using the **flutter_slidable** package to easily add slide functionality.
- Passing in the `itemBuilder` argument for the **StreamChannelListView** widget. This gives access to the current **BuildContext**, **Channel**, and **StreamChannelListTile**, and allows you to create, or customize, the stream channel list tiles.
- Returning a Slidable widget with two CustomSlidableAction widgets - to delete a channel and show more options. These widgets come from the flutter_slidable package.
- Adding `onPressed` behaviour to call `showConfirmationBottomSheet` and `showChannelInfoModalBottomSheet`. These methods come from the **stream_chat_flutter** package. They have a few different on-tap callbacks you can supply, for example, `onViewInfoTap`. Alternatively, you can create custom dialogs from scratch.
- Using the **StreamChannelListController** to perform actions, such as, `deleteChannel`.
```dart
import 'package:flutter/material.dart';
import 'package:flutter_slidable/flutter_slidable.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
void main() async {
final client = StreamChatClient(
's2dxdhpxd94g',
);
await client.connectUser(
User(id: 'super-band-9'),
'''eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoic3VwZXItYmFuZC05In0.0L6lGoeLwkz0aZRUcpZKsvaXtNEDHBcezVTZ0oPq40A''',
);
runApp(
MyApp(
client: client,
),
);
}
class MyApp extends StatelessWidget {
const MyApp({
Key? key,
required this.client,
}) : super(key: key);
final StreamChatClient client;
@override
Widget build(BuildContext context) {
return MaterialApp(
builder: (context, child) => StreamChat(
client: client,
child: child,
),
home: ChannelListPage(
client: client,
),
);
}
}
class ChannelListPage extends StatefulWidget {
const ChannelListPage({
Key? key,
required this.client,
}) : super(key: key);
final StreamChatClient client;
@override
State<ChannelListPage> createState() => _ChannelListPageState();
}
class _ChannelListPageState extends State<ChannelListPage> {
late final _controller = StreamChannelListController(
client: widget.client,
filter: Filter.in_(
'members',
[StreamChat.of(context).currentUser!.id],
),
sort: const [SortOption('last_message_at')],
);
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) => Scaffold(
body: SlidableAutoCloseBehavior(
child: RefreshIndicator(
onRefresh: _controller.refresh,
child: StreamChannelListView(
controller: _controller,
itemBuilder: (context, channel, tile) {
final chatTheme = StreamChatTheme.of(context);
final backgroundColor = chatTheme.colorTheme.inputBg;
final canDeleteChannel = channel.ownCapabilities
.contains(PermissionType.deleteChannel);
return Slidable(
groupTag: 'channels-actions',
endActionPane: ActionPane(
extentRatio: canDeleteChannel ? 0.40 : 0.20,
motion: const BehindMotion(),
children: [
CustomSlidableAction(
onPressed: (_) {
showChannelInfoModalBottomSheet(
context: context,
channel: channel,
onViewInfoTap: () {
Navigator.pop(context);
// Navigate to info screen
},
);
},
backgroundColor: backgroundColor,
child: const Icon(Icons.more_horiz),
),
if (canDeleteChannel)
CustomSlidableAction(
backgroundColor: backgroundColor,
child: StreamSvgIcon.delete(
color: chatTheme.colorTheme.accentError,
),
onPressed: (_) async {
final res = await showConfirmationBottomSheet(
context,
title: 'Delete Conversation',
question:
'Are you sure you want to delete this conversation?',
okText: 'Delete',
cancelText: 'Cancel',
icon: StreamSvgIcon.delete(
color: chatTheme.colorTheme.accentError,
),
);
if (res == true) {
await _controller.deleteChannel(channel);
}
},
),
],
),
child: tile,
);
},
onChannelTap: (channel) => Navigator.push(
context,
MaterialPageRoute(
builder: (_) => StreamChannel(
channel: channel,
child: const ChannelPage(),
),
),
),
),
),
),
);
}
class ChannelPage extends StatelessWidget {
const ChannelPage({
Key? key,
}) : super(key: key);
@override
Widget build(BuildContext context) => Scaffold(
appBar: const StreamChannelHeader(),
body: Column(
children: const <Widget>[
Expanded(
child: StreamMessageListView(),
),
StreamMessageInput(),
],
),
);
}
```
The above is the complete sample, and all you need for a basic implementation.
@@ -1,194 +0,0 @@
---
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])
])
```
@@ -1,429 +0,0 @@
---
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.
This guide makes use of Firebase's [Authenticate with Stream Chat](https://firebase.google.com/products/extensions/stream-auth-chat) extension. See [here](https://getstream.io/blog/stream-firebase-extensions/) for additional information on Stream's Firebase extensions.
The Firebase Stream Auth extension manages the Firebase cloud function code needed to create Stream Chat frontend tokens.
Alternatively, you could implement the Firebase cloud functions yourself by making use of our Stream's [NodeJS client](https://getstream.io/chat/docs/node/?language=javascript).
Take a look at the [extension implementation](https://github.com/GetStream/stream-firebase-extensions/tree/main/auth-chat) for additional details. We've also written a [blog post](https://getstream.io/blog/serverless-auth-flutter-firebase/) detailing how you can set this up manually by writing your own cloud functions.
Stream supports several different [backend clients](https://getstream.io/chat/sdk/#backend-clients) to integrate with your server. This guide only shows an automated way to integrate Stream Chat authentication using Firebase and Flutter.
### Flutter Firebase Setup
If you're new to Flutter Firebase, see the [add Firebase to your Flutter app](https://firebase.google.com/docs/flutter/setup?platform=ios) docs for Firebase setup and installation instructions.
You will need to add the [Flutter Firebase Authentication](https://pub.dev/packages/firebase_auth), [Flutter Firebase Cloud Functions](https://pub.dev/packages/cloud_functions), and [Flutter Firebase Core](https://pub.dev/packages/firebase_core) dependencies to your Flutter app.
Add the following dependencies in `pubspec.yaml`:
```yaml
firebase_auth: ^3.3.19
cloud_functions: ^3.2.16
firebase_core: ^1.17.1
```
:::note
This guide uses the above versions. Different versions may have breaking, or other, changes.
:::
### Install Firebase Extensions - Stream Chat Cloud Functions
To follow this guide, you will need to install the Stream Chat Auth Firebase extension in your Firebase project.
This extension automatically provisions your Firebase project with the necessary cloud functions to create and delete Stream user accounts and to generate and revoke user tokens.
1. Go to: https://firebase.google.com/products/extensions/stream-auth-chat
2. Press the **Install in console** button
3. Select the correct Firebase project
4. Follow the installation instructions
:::note
Enabling cloud functions requires you to enable billing (Blaze plan) for your project. You won't be charged unless you reach a certain usage threshold, and you will also be prompted to set up cost limits.
:::
During the installation process you'll be presented with a screen similar to this:
![Firebase extensions installation](../assets/install_stream_chat_firebase_extension.jpg)
The extensions sets up the following cloud functions:
- createStreamUser
- deleteStreamUser
- getStreamUserToken
- revokeStreamUserToken
Depending on your current project configuration, you may also be promoted to enable **Authentication** and **Secret Manager**. For the extension to work these will need to be enabled, by pressing the **Enable** buttons.
![Firebase enable Authentication and Secret Manager](../assets/firebase_cloud_secrets_management.jpg)
In the final step of the installation you will need to provide your Stream app's API Key and Secret.
![Firebase enable Authentication and Secret Manager](../assets/stream_firebase_extension_configuration.jpg)
1. After entering your Stream Secret, press **Create secret**
2. Press **Install extension**
You can retrieve your app key and secret from your [Stream Project Dashboard](https://dashboard.getstream.io/).
![Stream Chat API Key and Secret](../assets/stream_chat_app_access_keys.jpg)
The extension will take a while to be installed, you'll be able to see the installation progress on the **Extensions** tab on Firebase.
Once installed, you'll be able to view information on how the Stream extension works and options to reconfigure it.
![Stream Chat Firebase extension usage](../assets/firebase_stream_chat_extension_usage.jpg)
This extension automatically creates and deletes a user on Stream when a Firebase user is created or deleted.
It also allows you to generate, and revoke, a Stream frontend token for an authenticated Firebase user:
- **ext-auth-chat-getStreamUserToken**
- **ext-auth-chat-revokeStreamUserToken**
### Flutter Code
The following is the complete Flutter code needed to run a basic example using Firebase Auth and Cloud Functions with your newly installed Stream Chat Firebase extension.
```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';
import 'package:flutter_firebase_extensions/firebase_options.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return const MaterialApp(
home: Scaffold(
body: Auth(),
),
);
}
}
class Auth extends StatefulWidget {
const Auth({Key? key}) : super(key: key);
@override
State<Auth> createState() => _AuthState();
}
class _AuthState extends State<Auth> {
late FirebaseAuth auth;
late FirebaseFunctions functions;
@override
void initState() {
super.initState();
auth = FirebaseAuth.instance;
functions = FirebaseFunctions.instanceFor(region: 'us-central1');
}
final email = '[email protected]';
final password = 'password';
/// Create User with Firebase and return Stream Token.
Future<String?> _createAccountAndGetToken() async {
try {
await auth.createUserWithEmailAndPassword(
email: email, password: password);
print('Firebase account created');
return _getToken();
} on FirebaseAuthException catch (error) {
print(error.code);
print(error.message);
}
return null;
}
/// Sign in with Firebase and retrieve Stream chat token.
Future<String?> _signInAndGetToken() async {
try {
await auth.signInWithEmailAndPassword(email: email, password: password);
print('Firebase signed in');
return _getToken();
} on FirebaseAuthException catch (error) {
print(error.code);
print(error.message);
}
return null;
}
/// Sign out of Firebase and revoke Stream chat token.
Future<void> _signOutAndRevokeToken() async {
await _revokeToken();
await auth.signOut();
print('Firebase Signed Out');
}
/// Gets a Stream user token for current authenticated user
///
/// Need to be authenticated to Firebase to call this function.
Future<String?> _getToken() async {
try {
final result = await functions
.httpsCallable('ext-auth-chat-getStreamUserToken')
.call();
print('Stream user token retrieved: ${result.data}');
return result.data;
} on FirebaseFunctionsException catch (error) {
print(error.code);
print(error.details);
print(error.message);
}
return null;
}
/// Revoke Stream user token for current authenticated user.
///
/// Need to be authenticated to Firebase to call this function.
Future<void> _revokeToken() async {
try {
await FirebaseFunctions.instance
.httpsCallable('ext-auth-chat-revokeStreamUserToken')
.call();
print('Stream user token revoked');
} on FirebaseFunctionsException catch (error) {
print(error.code);
print(error.details);
print(error.message);
}
}
@override
Widget build(BuildContext context) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
AuthenticationState(
streamUser: auth.authStateChanges(),
),
ElevatedButton(
onPressed: _createAccountAndGetToken,
child: const Text('Create account'),
),
ElevatedButton(
onPressed: _signInAndGetToken,
child: const Text('Sign in'),
),
ElevatedButton(
onPressed: _signOutAndRevokeToken,
child: const 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)
? const Text('Authenticated')
: const Text('Not Authenticated');
}
return const Text('Not Authenticated');
},
);
}
}
```
Running the above will show a screen similar to this:
![Firebase auth demo UI output](../assets/authentication_demo_app.jpg)
#### Flutter Firebase Authentication
The `Auth` widget handles all of the authentication logic. It initializes a `FirebaseAuth.instance` and uses that
in the `_createAccountAndGetToken`, `_signInAndGetToken` and `_signOutAndRevokeToken` methods. There is a button to invoke each of these methods.
This example uses a hardcoded **email** and **password** and demonstrates basic authentication.
The `AuthenticationState` widget listens to `auth.authStateChanges()` to display a message
indicating if a user is authenticated.
:::note
Ensure that you've enabled the email and password authentication provider within the Firebase Auth console.
:::
#### Flutter 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 a function is automatically triggered to create a Stream user.
- **External event**: For example, directly calling a cloud function from your Flutter application - creating/revoking a Stream user token.
To call external cloud functions from Flutter, you will need to use the `cloud_functions` package.
In the code sample given above, in the `initState` method, you're also creating a Firebase function instance:
```dart
functions = FirebaseFunctions.instanceFor(region: 'us-central1');
```
Be sure to set the region to be the same as what you configured your Firebase Chat Auth Extension to use.
#### Create a User and Get the User's Stream Token
Let's explore the code to create a new user and retrieve the frontend token.
The `_createAccountAndGetToken` method does the following:
1. Creates a user on Firebase using the demo credentials
2. An **internal** cloud function is automatically triggered on Firebase to create a new user within your Stream backend app
3. Call the `_getToken` method
Let's explore the `_getToken` method:
```dart
/// Gets a Stream user token for the currently authenticated user
///
/// Need to be authenticated to Firebase to call this function.
Future<String?> _getToken() async {
try {
final result = await functions
.httpsCallable('ext-auth-chat-getStreamUserToken')
.call();
print('Stream user token retrieved: ${result.data}');
return result.data;
} on FirebaseFunctionsException catch (error) {
print(error.code);
print(error.details);
print(error.message);
}
return null;
}
```
:::note
The user needs to be authenticated to call this cloud function. Otherwise, the function will throw
a **failed-precondition** error.
:::
This method calls the `ext-auth-chat-getStreamUserToken` cloud function, which was created automatically when the Stream Firebase extension was installed.
This cloud function will look at the calling user and if they are authenticated to Firebase it will create and return a new frontend user token.
As you can see, calling a cloud function is easy and will also send all the necessary Firebase user authentication information (such as the user 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 in 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)
#### Login and Get the User's Stream Token
Lets explore the process of user login and token generation within the `_signInAndGetToken` method:
```dart
/// Sign in with Firebase and retrieve Stream chat token.
Future<String?> _signInAndGetToken() async {
try {
await auth.signInWithEmailAndPassword(email: email, password: password);
print('Firebase signed in');
return _getToken();
} on FirebaseAuthException catch (error) {
print(error.code);
print(error.message);
}
return null;
}
```
This process is similar to account creation. Here you sign in using Firebase, and if successful proceed to create a Stream frontend token using the `_getToken` method - which we already explored.
#### Sign Out and Revoke Stream User Token
You probably also want to revoke the Stream user token if you sign out from Firebase.
Let's explore the `_signOutAndRevokeToken` method:
1. This calls, and awaits, the `_revokeToken` method
2. Signs out of Firebase
In the `_revokeToken` method you execute the **ext-auth-chat-revokeStreamUserToken** cloud function:
```dart
/// Revoke Stream user token for current authenticated user.
///
/// Need to be authenticated to Firebase to call this function.
Future<void> _revokeToken() async {
try {
await FirebaseFunctions.instance
.httpsCallable('ext-auth-chat-revokeStreamUserToken')
.call();
print('Stream user token revoked');
} on FirebaseFunctionsException catch (error) {
print(error.code);
print(error.details);
print(error.message);
}
}
```
:::note
You need to call the cloud function before signing out off Firebase. That is why we `await` the result of `_revokeToken` within `_signOutAndRevokeToken`.
:::
#### Delete Firebase and Stream User
When deleting a Firebase user account, the **ext-auth-chat-deleteStreamUser** cloud function is automatically triggered. This is not an external cloud function; it can only be triggered when an
account is deleted on Firebase itself.
### Conclusion
In this guide, you have seen how to quickly and securely create Stream Chat tokens using
Firebase Authentication and Cloud Functions.