How to Implement Chat Feature in Flutter Video Call App
Tags: flutter, dart
Published: 2024-04-24 09:28:54

---
title: How to Implement Chat Feature in Flutter Video Call App
published: true
date: 2024-04-23 09:25:44 UTC
tags: flutter, dart
canonical_url: https://www.videosdk.live/blog/implement-chat-feature-in-flutter-video-call-app
---

## 📌 Introduction
 ![How to Implement Chat Feature in Flutter Video Call App?](https://assets.videosdk.live/static-assets/ghost/2024/04/Chat-Feature-in-Flutter-Video-Call-App.jpg)

Integrating a chat feature into your [Flutter video call app](https://www.videosdk.live/blog/video-calling-in-flutter) enhances collaboration and communication among participants. With the VideoSDK, you can seamlessly implement real-time messaging capabilities. This chat feature utilizes the Publish-Subscribe (PubSub) mechanism, ensuring efficient and scalable message distribution across the meeting. Whether it's sharing important information, asking questions, or simply chatting, participants can engage with each other effortlessly during the video call sessions.

**Benefits of Implement Chat Feature in Flutter Video Call App:**

1. **Enhanced Collaboration:** Chat enables participants to communicate ideas, share files, and ask questions, fostering collaboration and teamwork.
2. **Real-time Feedback:** Users can provide immediate feedback, ask questions, or clarify doubts during video calls, improving communication efficiency.
3. **Documentation:** Chat transcripts serve as a record of discussions and decisions made during meetings, facilitating post-meeting review and follow-up.
4. **Increased Engagement:** Chat encourages active participation from all participants, even those who might be hesitant to speak up during video calls.

In this guide, we'll navigate the process of seamlessly integrating chat capabilities into your current VideoSDK-driven video calling application. From establishing the chat environment to managing chat interactions within your video call interfaces, we'll cover all the essential steps to augment your app's functionality and user experience.

## 🚀 Getting Started with VideoSDK

To integrate the Chat Feature, we must use the capabilities that VideoSDK offers. Before diving into the implementation steps, let's ensure you complete the necessary prerequisites.

### Create a VideoSDK Account

Go to your [VideoSDK dashboard](https://www.videosdk.live/signup?utm_source=organicseo&utm_medium=devto&utm_campaign=chat_feature_flutter) and sign up if you don't have an account. This account gives you access to the required Video SDK token, which acts as an authentication key that allows your application to interact with VideoSDK functionality.

### Generate your Auth Token

Visit your VideoSDK dashboard and navigate to the "API Key" section to generate your auth token. This token is crucial in authorizing your application to use VideoSDK features.

For a more visual understanding of the account creation and token generation process, consider referring to the [provided tutorial](https://docs.videosdk.live/flutter/guide/video-and-audio-calling-api-sdk/authentication-and-tokens#1-generating-token-from-dashboard).

### Prerequisites[​](https://docs.videosdk.live/flutter/guide/video-and-audio-calling-api-sdk/quick-start#prerequisites)

Before proceeding, ensure that your development environment meets the following requirements:

- VideoSDK Developer Account (if you do not have one, follow [**VideoSDK Dashboard**](https://www.videosdk.live/signup?utm_source=organicseo&utm_medium=devto&utm_campaign=chat_feature_flutter))
- The basic understanding of Flutter.
- **[Flutter VideoSDK](https://pub.dev/packages/videosdk)**
- Have Flutter installed on your device.

## 🛠️ Install VideoSDK[​](https://docs.videosdk.live/flutter/guide/video-and-audio-calling-api-sdk/quick-start#install-video-sdk)

Install the VideoSDK using the below-mentioned flutter command. Make sure you are in your Flutter app directory before you run this command.

```dart
$ flutter pub add videosdk

//run this command to add http library to perform network call to generate roomId
$ flutter pub add http
```

### VideoSDK Compatibility
<!--kg-card-begin: html-->

| Android and iOS app | Web | Desktop app | Safari browser |
| --- | --- | --- | --- |
| <center> ✅ </center> | <center> ✅ </center> | <center> ✅ </center> | <center> ❌ </center> |

<!--kg-card-end: html-->
### Structure of the project[​](https://docs.videosdk.live/flutter/guide/video-and-audio-calling-api-sdk/quick-start#structure-of-the-project)

Your project structure should look like this.

```dart
    root
    ├── android
    ├── ios
    ├── lib
         ├── api_call.dart
         ├── join_screen.dart
         ├── main.dart
         ├── meeting_controls.dart
         ├── meeting_screen.dart
         ├── participant_tile.dart
```

We are going to create flutter widgets (JoinScreen, MeetingScreen, MeetingControls, and ParticipantTile).

### App Structure[​](https://docs.videosdk.live/flutter/guide/video-and-audio-calling-api-sdk/quick-start#app-structure)

The app widget will contain `JoinScreen` and `MeetingScreen` widget. `MeetingScreen` will have `MeetingControls` and `ParticipantTile` widget.

![How to Implement Chat Feature in Flutter Video Call App?](https://cdn.videosdk.live/website-resources/docs-resources/flutter_quick_start_arch.png)

### Configure Project

####   
For Android[​](https://docs.videosdk.live/flutter/guide/video-and-audio-calling-api-sdk/quick-start#for-android)

- Update `/android/app/src/main/AndroidManifest.xml` for the permissions we will be using to implement the audio and video features.

```dart
<uses-feature android:name="android.hardware.camera" />
<uses-feature android:name="android.hardware.camera.autofocus" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<uses-permission android:name="android.permission.WAKE_LOCK" />
```

_AndroidManifest.xml_

- Also, you will need to set your build settings to Java 8 because the official WebRTC jar now uses static methods in `EglBase` an interface. Just add this to your app-level `/android/app/build.gradle`.

```dart
android {
    //...
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
}
```

- If necessary, in the same `build.gradle` you will need to increase `minSdkVersion` of `defaultConfig` up to `23` (currently, the default Flutter generator sets it to `16`).
- If necessary, in the same `build.gradle` you will need to increase `compileSdkVersion` and `targetSdkVersion` up to `33` (currently, the default Flutter generator sets it to `30`).

#### For iOS[​](https://docs.videosdk.live/flutter/guide/video-and-audio-calling-api-sdk/quick-start#for-ios)

- Add the following entries which allow your app to access the camera and microphone of your `/ios/Runner/Info.plist` file :

```dart
<key>NSCameraUsageDescription</key>
<string>$(PRODUCT_NAME) Camera Usage!</string>
<key>NSMicrophoneUsageDescription</key>
<string>$(PRODUCT_NAME) Microphone Usage!</string>
```

- Uncomment the following line to define a global platform for your project in `/ios/Podfile` :

```dart
# platform :ios, '12.0'
```

#### For MacOS[​](https://docs.videosdk.live/flutter/guide/video-and-audio-calling-api-sdk/quick-start#for-macos)

- Add the following entries to your `/macos/Runner/Info.plist` file that allows your app to access the camera and microphone:

```dart
<key>NSCameraUsageDescription</key>
<string>$(PRODUCT_NAME) Camera Usage!</string>
<key>NSMicrophoneUsageDescription</key>
<string>$(PRODUCT_NAME) Microphone Usage!</string>
```

- Add the following entries to your `/macos/Runner/DebugProfile.entitlements` file that allows your app to access the camera, microphone, and open outgoing network connections:

```dart
<key>com.apple.security.network.client</key>
<true/>
<key>com.apple.security.device.camera</key>
<true/>
<key>com.apple.security.device.microphone</key>
<true/>
```

- Add the following entries to your `/macos/Runner/Release.entitlements` file that allows your app to access the camera, microphone, and open outgoing network connections:

```dart
<key>com.apple.security.network.server</key>
<true/>
<key>com.apple.security.network.client</key>
<true/>
<key>com.apple.security.device.camera</key>
<true/>
<key>com.apple.security.device.microphone</key>
<true/>
```

## 🎥 Essential Steps to Implement Video Calling Functionality

After successfully integrating VideoSDK into your Flutter app, you have laid the foundation for real-time video communication. Now, let's take it a step further by incorporating a text chat feature. This will allow users to send messages during a call, providing a convenient way to share quick text messages, links, or information that complements the video call.

### Step 1: Get started with `api_call.dart`[​](https://docs.videosdk.live/flutter/guide/video-and-audio-calling-api-sdk/quick-start#step-1-get-started-with-api_calldart)

Before jumping to anything else, you will write a function to generate a unique meetingId. You will require an authentication token, you can generate it either by using [videosdk-rtc-api-server-examples](https://github.com/videosdk-live/videosdk-rtc-api-server-examples) or by generating it from the [VideoSDK Dashboard](https://app.videosdk.live/api-keys) for development.

```dart
import 'dart:convert';
import 'package:http/http.dart' as http;

//Auth token we will use to generate a meeting and connect to it
String token = "<Generated-from-dashboard>";

// API call to create meeting
Future<String> createMeeting() async {
  final http.Response httpResponse = await http.post(
    Uri.parse("https://api.videosdk.live/v2/rooms"),
    headers: {'Authorization': token},
  );

//Destructuring the roomId from the response
  return json.decode(httpResponse.body)['roomId'];
}
```

_api_call.dart_

### Step 2: Creating the JoinScreen[​](https://docs.videosdk.live/flutter/guide/video-and-audio-calling-api-sdk/quick-start#step-2--creating-the-joinscreen)

Let's create `join_screen.dart` file in `lib` directory and create JoinScreen `StatelessWidget`.

The JoinScreen will consist of:

- **Create Meeting Button** : This button will create a new meeting for you.
- **Meeting ID TextField** : This text field will contain the meeting ID, you want to join.
- **Join Meeting Button** : This button will join the meeting, which you have provided.

```dart
import 'package:flutter/material.dart';
import 'api_call.dart';
import 'meeting_screen.dart';

class JoinScreen extends StatelessWidget {
  final _meetingIdController = TextEditingController();

  JoinScreen({super.key});

  void onCreateButtonPressed(BuildContext context) async {
    // call api to create meeting and then navigate to MeetingScreen with meetingId,token
    await createMeeting().then((meetingId) {
      if (!context.mounted) return;
      Navigator.of(context).push(
        MaterialPageRoute(
          builder: (context) => MeetingScreen(
            meetingId: meetingId,
            token: token,
          ),
        ),
      );
    });
  }

  void onJoinButtonPressed(BuildContext context) {
    String meetingId = _meetingIdController.text;
    var re = RegExp("\\w{4}\\-\\w{4}\\-\\w{4}");
    // check meeting id is not null or invaild
    // if meeting id is vaild then navigate to MeetingScreen with meetingId,token
    if (meetingId.isNotEmpty && re.hasMatch(meetingId)) {
      _meetingIdController.clear();
      Navigator.of(context).push(
        MaterialPageRoute(
          builder: (context) => MeetingScreen(
            meetingId: meetingId,
            token: token,
          ),
        ),
      );
    } else {
      ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
        content: Text("Please enter valid meeting id"),
      ));
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('VideoSDK QuickStart'),
      ),
      body: Padding(
        padding: const EdgeInsets.all(12.0),
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            ElevatedButton(
              onPressed: () => onCreateButtonPressed(context),
              child: const Text('Create Meeting'),
            ),
            Container(
              margin: const EdgeInsets.fromLTRB(0, 8.0, 0, 8.0),
              child: TextField(
                decoration: const InputDecoration(
                  hintText: 'Meeting Id',
                  border: OutlineInputBorder(),
                ),
                controller: _meetingIdController,
              ),
            ),
            ElevatedButton(
              onPressed: () => onJoinButtonPressed(context),
              child: const Text('Join Meeting'),
            ),
          ],
        ),
      ),
    );
  }
}
```

_join_screen.dart_

- Update the home screen of the app in the `main.dart`

```dart
import 'package:flutter/material.dart';
import 'join_screen.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'VideoSDK QuickStart',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: JoinScreen(),
    );
  }
}
```

_main.dart_

### Step 3: Creating the MeetingControls[​](https://docs.videosdk.live/flutter/guide/video-and-audio-calling-api-sdk/quick-start#step-3--creating-the-meetingcontrols)

Let's create `meeting_controls.dart` file and create MeetingControls `StatelessWidget`.

The MeetingControls will consist of:

- **Leave Button** : This button will leave the meeting.
- **Toggle Mic Button** : This button will unmute or mute the mic.
- **Toggle Camera Button** : This button will enable or disable the camera.

MeetingControls will accept 3 functions in the constructor.

- **onLeaveButtonPressed** : invoked when the Leave button is pressed.
- **onToggleMicButtonPressed** : invoked when the toggle mic button pressed.
- **onToggleCameraButtonPressed** : invoked when the toggle Camera button pressed.

```dart
import 'package:flutter/material.dart';

class MeetingControls extends StatelessWidget {
  final void Function() onToggleMicButtonPressed;
  final void Function() onToggleCameraButtonPressed;
  final void Function() onLeaveButtonPressed;

  const MeetingControls(
      {super.key,
      required this.onToggleMicButtonPressed,
      required this.onToggleCameraButtonPressed,
      required this.onLeaveButtonPressed});

  @override
  Widget build(BuildContext context) {
    return Row(
      mainAxisAlignment: MainAxisAlignment.spaceEvenly,
      children: [
        ElevatedButton(
            onPressed: onLeaveButtonPressed, child: const Text('Leave')),
        ElevatedButton(
            onPressed: onToggleMicButtonPressed, child: const Text('Toggle Mic')),
        ElevatedButton(
            onPressed: onToggleCameraButtonPressed,
            child: const Text('Toggle WebCam')),
      ],
    );
  }
}
```

_meeting_controls.dart_

### Step 4: Creating ParticipantTile[​](https://docs.videosdk.live/flutter/guide/video-and-audio-calling-api-sdk/quick-start#step-4--creating-participanttile)

Let's create `participant_tile.dart` file and create ParticipantTile `StatefulWidget`.

The ParticipantTile will consist of:

- **RTCVideoView** : This will show the participant's video stream.

ParticipantTile will accept `Participant` in constructor

- **participant:** participant of the meeting.

```dart
import 'package:flutter/material.dart';
import 'package:videosdk/videosdk.dart';

class ParticipantTile extends StatefulWidget {
  final Participant participant;
  const ParticipantTile({super.key, required this.participant});

  @override
  State<ParticipantTile> createState() => _ParticipantTileState();
}

class _ParticipantTileState extends State<ParticipantTile> {
  Stream? videoStream;

  @override
  void initState() {
    // initial video stream for the participant
    widget.participant.streams.forEach((key, Stream stream) {
      setState(() {
        if (stream.kind == 'video') {
          videoStream = stream;
        }
      });
    });
    _initStreamListeners();
    super.initState();
  }

  _initStreamListeners() {
    widget.participant.on(Events.streamEnabled, (Stream stream) {
      if (stream.kind == 'video') {
        setState(() => videoStream = stream);
      }
    });

    widget.participant.on(Events.streamDisabled, (Stream stream) {
      if (stream.kind == 'video') {
        setState(() => videoStream = null);
      }
    });
  }

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(8.0),
      child: videoStream != null
          ? RTCVideoView(
              videoStream?.renderer as RTCVideoRenderer,
              objectFit: RTCVideoViewObjectFit.RTCVideoViewObjectFitCover,
            )
          : Container(
              color: Colors.grey.shade800,
              child: const Center(
                child: Icon(
                  Icons.person,
                  size: 100,
                ),
              ),
            ),
    );
  }
}
```

_participant_tile.dart_

### Step 5: Creating the MeetingScreen[​](https://docs.videosdk.live/flutter/guide/video-and-audio-calling-api-sdk/quick-start#step-5--creating-the-meetingscreen)

Let's create `meeting_screen.dart` file and create MeetingScreen `StatefulWidget`.

MeetingScreen will accept meetingId and token in the constructor.

- **meetingID:** meetingId, you want to join
- **token** : VideoSDK Auth token.

```dart
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:videosdk/videosdk.dart';
import './participant_tile.dart';

class MeetingScreen extends StatefulWidget {
  final String meetingId;
  final String token;

  const MeetingScreen(
      {super.key, required this.meetingId, required this.token});

  @override
  State<MeetingScreen> createState() => _MeetingScreenState();
}

class _MeetingScreenState extends State<MeetingScreen> {
  late Room _room;
  var micEnabled = true;
  var camEnabled = true;

  Map<String, Participant> participants = {};

  @override
  void initState() {
    // create room
    _room = VideoSDK.createRoom(
      roomId: widget.meetingId,
      token: widget.token,
      displayName: "John Doe",
      micEnabled: micEnabled,
      camEnabled: camEnabled
    );

    setMeetingEventListener();

    // Join room
    _room.join();

    super.initState();
  }

  // listening to meeting events
  void setMeetingEventListener() {
    _room.on(Events.roomJoined, () {
      setState(() {
        participants.putIfAbsent(
            _room.localParticipant.id, () => _room.localParticipant);
      });
    });

    _room.on(
      Events.participantJoined,
      (Participant participant) {
        setState(
          () => participants.putIfAbsent(participant.id, () => participant),
        );
      },
    );

    _room.on(Events.participantLeft, (String participantId) {
      if (participants.containsKey(participantId)) {
        setState(
          () => participants.remove(participantId),
        );
      }
    });

    _room.on(Events.roomLeft, () {
      participants.clear();
      Navigator.popUntil(context, ModalRoute.withName('/'));
    });
  }

  // onbackButton pressed leave the room
  Future<bool> _onWillPop() async {
    _room.leave();
    return true;
  }

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return WillPopScope(
      onWillPop: () => _onWillPop(),
      child: Scaffold(
        appBar: AppBar(
          title: const Text('VideoSDK QuickStart'),
        ),
        body: Padding(
          padding: const EdgeInsets.all(8.0),
          child: Column(
            children: [
              Text(widget.meetingId),
              //render all participant
              Expanded(
                child: Padding(
                  padding: const EdgeInsets.all(8.0),
                  child: GridView.builder(
                    gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
                      crossAxisCount: 2,
                      crossAxisSpacing: 10,
                      mainAxisSpacing: 10,
                      mainAxisExtent: 300,
                    ),
                    itemBuilder: (context, index) {
                      return ParticipantTile(
                        key: Key(participants.values.elementAt(index).id),
                          participant: participants.values.elementAt(index));
                    },
                    itemCount: participants.length,
                  ),
                ),
              ),
              MeetingControls(
                onToggleMicButtonPressed: () {
                  micEnabled ? _room.muteMic() : _room.unmuteMic();
                  micEnabled = !micEnabled;
                },
                onToggleCameraButtonPressed: () {
                  camEnabled ? _room.disableCam() : _room.enableCam();
                  camEnabled = !camEnabled;
                },
                onLeaveButtonPressed: () {
                  _room.leave()
                },
              ),
            ],
          ),
        ),
      ),
      home: JoinScreen(),
    );
  }
}
```

_meeting_screen.dart_

> **CAUTION**  
> If you get `webrtc/webrtc.h file not found` error at a runtime in iOS, then check the solution [here](https://docs.videosdk.live/flutter/guide/video-and-audio-calling-api-sdk/known-issues#issue--1).

> **TIP** :  
> You can checkout the complete [quick start example here](https://github.com/videosdk-live/quickstart/tree/main/flutter-rtc).

## Integrate Chat Feature

For communication or any kind of messaging between the participants, VideoSDK provides `pubSub` that uses the Publish-Subscribe mechanism and can be used to develop functionalities. For example, participants could use it to send chat messages to each other, share files or other media, or even trigger actions like muting or unmuting audio or video.

Now we will see, how we can use PubSub to implement Chat functionality. If you are not familiar with the PubSub mechanism, you can [follow this guide](https://docs.videosdk.live/flutter/guide/video-and-audio-calling-api-sdk/collaboration-in-meeting/pubsub).

### Group Chat

The first step in creating a group chat is choosing the topic that all the participants will publish and subscribe to send and receive the messages. We will be using `CHAT` this as the topic for this one. So let us create a message input and send button to publish the messages using the `pubSub` from the `Room` object.

```dart
import 'package:flutter/material.dart';
import 'package:videosdk/videosdk.dart';

class ChatView extends StatefulWidget {
  final Room room;
  ...
}

class _ChatViewState extends State<ChatView> {

  final msgTextController = TextEditingController();

  @override
  void initState() {
    ...

    // Subscribing 'CHAT' Topic
    widget.room.pubSub
      .subscribe("CHAT", messageHandler)
      .then((value) => setState((() => messages = value)));
  }

  //Handler which will be called when new mesasge is received
  void messageHandler(PubSubMessage message) {
    setState(() => messages!.messages.add(message));
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children:[
        Row(
          children: [
            Expanded(
              child: TextField(
                style: TextStyle(
                  fontSize:16,
                  fontWeight: FontWeight.w500,
                ),
                controller: msgTextController,
                onChanged: (value) => setState(() {
                  msgTextController.text;
                }),
                decoration: const InputDecoration(
                  hintText: "Write your message",
                  border: InputBorder.none,
                ),
              ),
            ),
            ElevatedButton(
              onPressed:(){
                if(!msgTextController.text.trim().isEmpty){
                  widget.room.pubSub
                        .publish(
                          "CHAT",
                          msgTextController.text,
                          const PubSubPublishOptions(
                              persist: true),
                        )
                        .then(
                            (value) => msgTextController.clear())
                }
              },
              child: const Text("Send Message"),
            ),
          ],
        ),
      ]
    );
  }

}
```

The final step in the group chat would be to display the messages others send. For this will use the `messages` and display all the messages by subscribing to the topic `CHAT`.

```dart
import 'package:flutter/material.dart';
import 'package:videosdk/videosdk.dart';

class ChatView extends StatefulWidget {
  final Room room;
  ...
}

class _ChatViewState extends State<ChatView> {

  // PubSubMessages
  PubSubMessages? messages;

  @override
  void initState() {
    ...

    // Subscribing 'CHAT' Topic
    widget.room.pubSub
      .subscribe("CHAT", messageHandler)
      .then((value) => setState((() => messages = value)));
  }

  //Handler which will be called when new mesasge is received
  void messageHandler(PubSubMessage message) {
    setState(() => messages!.messages.add(message));
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children:[
        Expanded(
          child: messages == null
              ? const Center(child: CircularProgressIndicator())
              : SingleChildScrollView(
                  reverse: true,
                  child: Column(
                    children: messages!.messages
                        .map(
                          (message) => Text(
                            message.message
                          ),
                        )
                        .toList(),
                  ),
                ),
        ),

        ...
        //Send Message code Here
      ]
    );
  }

  @override
  void dispose() {
    // Unsubscribe
    widget.room.pubSub.unsubscribe("CHAT", messageHandler);
    super.dispose();
  }
}
```

Now let us open this `ChatView` widget on a button click from our meeting screen.

```dart
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:videosdk/videosdk.dart';
import './participant_tile.dart';
import './ChatView.dart';

class MeetingScreen extends StatefulWidget {
  final String meetingId;
  final String token;

  const MeetingScreen(
      {super.key, required this.meetingId, required this.token});

  @override
  State<MeetingScreen> createState() => _MeetingScreenState();
}

class _MeetingScreenState extends State<MeetingScreen> {
   
  // Other code here... 
  
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return WillPopScope(
      onWillPop: () => _onWillPop(),
      child: Scaffold(
        appBar: AppBar(
          title: const Text('VideoSDK QuickStart'),
        ),
        body: Padding(
          padding: const EdgeInsets.all(8.0),
          child: Column(
            children: [
              // Other meeting widgets ...
              
              ElevatedButton(
                onPressed: (){
                	showModalBottomSheet(
                    context: context,
                    isScrollControlled: true,
                    builder: (context) => ChatView(
                        key: const Key("ChatScreen"),
                        meeting: widget.room),
                    ));
                },
                child: const Text('Open')),
            ],
          ),
        ),
      ),
      home: JoinScreen(),
    );
  }
}
```

### Private Chat

In the above example, if you want to convert into a private chat between two participants, then all you have to do is pass `sendOnly` parameter in `PubSubPublishOptions`.

```dart
import 'package:flutter/material.dart';
import 'package:videosdk/videosdk.dart';

class ChatView extends StatefulWidget {
  final Room room;
  ...
}

class _ChatViewState extends State<ChatView> {

  //...

  @override
  Widget build(BuildContext context) {
    return Column(
      children:[
        Row(
          children: [
            Expanded(
              child: TextField(
                style: TextStyle(
                  fontSize:16,
                  fontWeight: FontWeight.w500,
                ),
                controller: msgTextController,
                onChanged: (value) => setState(() {
                  msgTextController.text;
                }),
                decoration: const InputDecoration(
                  hintText: "Write your message",
                  border: InputBorder.none,
                ),
              ),
            ),
            ElevatedButton(
              onPressed:(){
                if(!msgTextController.text.trim().isEmpty){
                  // Pass the participantId of the participant to whom you want to send the message.
                  widget.room.pubSub
                        .publish(
                          "CHAT",
                          msgTextController.text,
                          const PubSubPublishOptions(
                            persist: true, sendOnly: ["xyz"]),
                        )
                        .then(
                            (value) => msgTextController.clear())
                }
              },
              child: const Text("Send Message"),
            ),
          ],
        ),
      ]
    );
  }

}
```

### Displaying the Latest Message Notification

You may want to show the notification to the user when a new message arrives. So let's continue our example and add an alert for the new images.

```dart
import 'package:flutter/material.dart';
import 'package:videosdk/videosdk.dart';

class ChatView extends StatefulWidget {
  final Room room;
  ...
}

class _ChatViewState extends State<ChatView> {

  @override
  void initState() {
    ...
  }

  //Handler which will be called when new mesasge is received
  void messageHandler(PubSubMessage message) {
    //Show snackbar on new message
    if(context.mounted){
      ScaffoldMessenger.of(context).showSnackBar(SnackBar(
        content: Text(
          message.message,
          overflow: TextOverflow.fade,
        ),
      ));
    }
    setState(() => messages!.messages.add(message));
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children:[
        ...
      ]
    );
  }

  @override
  void dispose() {
    ...
  }
}
```

### Downloading Chat Messages

All the messages from the PubSub which where published with `persist : true` and can be downloaded as a `.csv` file. This file will be available in the VideoSDK dashboard as well as through the [Sessions API](https://docs.videosdk.live/api-reference/realtime-communication/fetch-session-using-sessionid).

## 🔚 Conclusion

Integrating text chat into your Flutter video call app enhances the communication experience for users. It enables them to combine real-time voice and video with asynchronous text messaging, accommodating various communication preferences and scenarios. With the functionality provided by [VideoSDK](https://www.videosdk.live/) and the guidance offered in this tutorial, you can develop a user-friendly video-calling platform that enables users to connect in innovative ways.

Unlock the full potential of VideoSDK today and craft seamless video experiences! **[Sign up](https://www.videosdk.live/signup?utm_source=organicseo&utm_medium=devto&utm_campaign=chat_feature_flutter)** now to receive 10,000 free minutes and take your video app to new heights.